mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
use js expressions instead of mathjs #2002
This commit is contained in:
parent
096db54f11
commit
ec3e626693
11
expressions.md
Normal file
11
expressions.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Expressions
|
||||||
|
|
||||||
|
## Select segments by expression
|
||||||
|
|
||||||
|
LosslessCut has support for normal JavaScript expressions. You will be given a variable `segment` and can create an expression that returns `true` or `false`. For example to select all segments with a duration of less than 5 seconds use this expression:
|
||||||
|
|
||||||
|
```js
|
||||||
|
segment.duration < 5
|
||||||
|
```
|
||||||
|
|
||||||
|
See more examples in-app.
|
@ -131,7 +131,6 @@
|
|||||||
"i18next-fs-backend": "^2.1.1",
|
"i18next-fs-backend": "^2.1.1",
|
||||||
"json5": "^2.2.2",
|
"json5": "^2.2.2",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
"mathjs": "^12.4.2",
|
|
||||||
"mime-types": "^2.1.14",
|
"mime-types": "^2.1.14",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
|
@ -15,7 +15,7 @@ import CopyClipboardButton from '../components/CopyClipboardButton';
|
|||||||
import { isWindows, showItemInFolder } from '../util';
|
import { isWindows, showItemInFolder } from '../util';
|
||||||
import { ParseTimecode, SegmentBase } from '../types';
|
import { ParseTimecode, SegmentBase } from '../types';
|
||||||
|
|
||||||
const { dialog } = window.require('@electron/remote');
|
const { dialog, shell } = window.require('@electron/remote');
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
const ReactSwal = withReactContent(Swal);
|
||||||
|
|
||||||
@ -538,12 +538,13 @@ export async function selectSegmentsByLabelDialog(currentName: string) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectSegmentsByExprDialog(inputValidator: (v: string) => string | undefined) {
|
export async function selectSegmentsByExprDialog(inputValidator: (v: string) => Promise<string | undefined>) {
|
||||||
const examples = {
|
const examples = {
|
||||||
duration: { name: i18n.t('Segment duration less than 5 seconds'), code: 'segment.duration < 5' },
|
duration: { name: i18n.t('Segment duration less than 5 seconds'), code: 'segment.duration < 5' },
|
||||||
start: { name: i18n.t('Segment starts after 00:60'), code: 'segment.start > 60' },
|
start: { name: i18n.t('Segment starts after 00:60'), code: 'segment.start > 60' },
|
||||||
label: { name: i18n.t('Segment label'), code: "equalText(segment.label, 'My label')" },
|
label: { name: i18n.t('Segment label (exact)'), code: "segment.label === 'My label'" },
|
||||||
tag: { name: i18n.t('Segment tag value'), code: "equalText(segment.tags.myTag, 'tag value')" },
|
regexp: { name: i18n.t('Segment label (regexp)'), code: '/^My label/.test(segment.label)' },
|
||||||
|
tag: { name: i18n.t('Segment tag value'), code: "segment.tags.myTag === 'tag value'" },
|
||||||
};
|
};
|
||||||
|
|
||||||
function addExample(type: string) {
|
function addExample(type: string) {
|
||||||
@ -557,14 +558,10 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) =>
|
|||||||
html: (
|
html: (
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<div style={{ marginBottom: '1em' }}>
|
<div style={{ marginBottom: '1em' }}>
|
||||||
{i18n.t('Enter an expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. For available syntax, see {{url}}.', { url: 'https://mathjs.org/' })}
|
<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. <button type="button" className="button-unstyled" style={{ fontWeight: 'bold' }} onClick={() => shell.openExternal('https://github.com/mifi/lossless-cut/blob/master/expressions.md')}>View available syntax.</button></Trans>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div><b>{i18n.t('Variables')}:</b></div>
|
<div style={{ marginBottom: '1em' }}><b>{i18n.t('Variables')}:</b> segment.label, segment.start, segment.end, segment.duration, segment.tags.*</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '1em' }}>
|
|
||||||
segment.label, segment.start, segment.end, segment.duration
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div><b>{i18n.t('Examples')}:</b></div>
|
<div><b>{i18n.t('Examples')}:</b></div>
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { useStateWithHistory } from 'react-use/lib/useStateWithHistory';
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import pMap from 'p-map';
|
import pMap from 'p-map';
|
||||||
import invariant from 'tiny-invariant';
|
import invariant from 'tiny-invariant';
|
||||||
import { evaluate } from 'mathjs';
|
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
|
|
||||||
import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg';
|
import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg';
|
||||||
@ -15,6 +14,8 @@ import { createSegment, findSegmentsAtCursor, sortSegments, invertSegments, comb
|
|||||||
import * as ffmpegParameters from '../ffmpeg-parameters';
|
import * as ffmpegParameters from '../ffmpeg-parameters';
|
||||||
import { maxSegmentsAllowed } from '../util/constants';
|
import { maxSegmentsAllowed } from '../util/constants';
|
||||||
import { ParseTimecode, SegmentBase, SegmentToExport, StateSegment, UpdateSegAtIndex } from '../types';
|
import { ParseTimecode, SegmentBase, SegmentToExport, StateSegment, UpdateSegAtIndex } from '../types';
|
||||||
|
import safeishEval from '../worker/eval';
|
||||||
|
import { ScopeSegment } from '../../../../types';
|
||||||
|
|
||||||
const { ffmpeg: { blackDetect, silenceDetect } } = window.require('@electron/remote').require('./index.js');
|
const { ffmpeg: { blackDetect, silenceDetect } } = window.require('@electron/remote').require('./index.js');
|
||||||
|
|
||||||
@ -472,40 +473,34 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt
|
|||||||
}, [currentCutSeg, cutSegments, enableSegments]);
|
}, [currentCutSeg, cutSegments, enableSegments]);
|
||||||
|
|
||||||
const onSelectSegmentsByExpr = useCallback(async () => {
|
const onSelectSegmentsByExpr = useCallback(async () => {
|
||||||
function matchSegment(seg: StateSegment, expr: string) {
|
async function matchSegment(seg: StateSegment, expr: string) {
|
||||||
const start = getSegApparentStart(seg);
|
const start = getSegApparentStart(seg);
|
||||||
const end = getSegApparentEnd(seg);
|
const end = getSegApparentEnd(seg);
|
||||||
// must clone tags because scope is mutable (editable by expression)
|
// must clone tags because scope is mutable (editable by expression)
|
||||||
const scopeSegment: { label: string, start: number, end: number, duration: number, tags: Record<string, string> } = { label: seg.name, start, end, duration: end - start, tags: { ...seg.tags } };
|
const scopeSegment: ScopeSegment = { label: seg.name, start, end, duration: end - start, tags: { ...seg.tags } };
|
||||||
return evaluate(expr, { segment: scopeSegment }) === true;
|
return (await safeishEval(expr, { segment: scopeSegment })) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSegmentsToEnable = (expr: string) => cutSegments.filter((seg) => {
|
const getSegmentsToEnable = async (expr: string) => (await pMap(cutSegments, async (seg) => (
|
||||||
try {
|
((await matchSegment(seg, expr)) ? [seg] : [])
|
||||||
return matchSegment(seg, expr);
|
), { concurrency: 5 })).flat();
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof TypeError) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const value = await selectSegmentsByExprDialog((v: string) => {
|
const value = await selectSegmentsByExprDialog(async (v: string) => {
|
||||||
try {
|
try {
|
||||||
const segments = getSegmentsToEnable(v);
|
if (v.trim().length === 0) return i18n.t('Please enter a JavaScript expression.');
|
||||||
if (segments.length === 0) return i18n.t('No segments matched');
|
const segments = await getSegmentsToEnable(v);
|
||||||
|
if (segments.length === 0) return i18n.t('No segments match this expression.');
|
||||||
return undefined;
|
return undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
return err.message;
|
return i18n.t('Expression failed: {{errorMessage}}', { errorMessage: err.message });
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
const segmentsToEnable = getSegmentsToEnable(value);
|
const segmentsToEnable = await getSegmentsToEnable(value);
|
||||||
enableSegments(segmentsToEnable);
|
enableSegments(segmentsToEnable);
|
||||||
}, [cutSegments, enableSegments, getSegApparentEnd]);
|
}, [cutSegments, enableSegments, getSegApparentEnd]);
|
||||||
|
|
||||||
|
51
src/renderer/src/worker/eval.ts
Normal file
51
src/renderer/src/worker/eval.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const workerUrl = new URL('evalWorker.js', import.meta.url);
|
||||||
|
|
||||||
|
// https://v3.vitejs.dev/guide/features.html#web-workers
|
||||||
|
// todo terminate() and recreate in case of error?
|
||||||
|
const worker = new Worker(workerUrl);
|
||||||
|
|
||||||
|
let lastRequestId = 0;
|
||||||
|
|
||||||
|
export default async function safeishEval(code: string, context: unknown) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
lastRequestId += 1;
|
||||||
|
const id = lastRequestId;
|
||||||
|
|
||||||
|
// console.log({ lastRequestId, code, context })
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
worker.removeEventListener('message', onMessage);
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
worker.removeEventListener('messageerror', onMessageerror);
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
worker.removeEventListener('error', onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage({ data: { id: responseId, error, data } }) {
|
||||||
|
// console.log('message', { responseId, error, data })
|
||||||
|
|
||||||
|
if (responseId === id) {
|
||||||
|
cleanup();
|
||||||
|
if (error) reject(new Error(error));
|
||||||
|
else resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessageerror() {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('safeishEval messageerror'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(err: ErrorEvent) {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error(`safeishEval error: ${err.message}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.addEventListener('message', onMessage);
|
||||||
|
worker.addEventListener('messageerror', onMessageerror);
|
||||||
|
worker.addEventListener('error', onError);
|
||||||
|
|
||||||
|
worker.postMessage({ id, code, context: JSON.stringify(context) });
|
||||||
|
});
|
||||||
|
}
|
138
src/renderer/src/worker/evalWorker.ts
Normal file
138
src/renderer/src/worker/evalWorker.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
||||||
|
const myGlobal = this;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/10796616/6519037
|
||||||
|
// https://github.com/Zirak/SO-ChatBot/blob/master/source/eval.js
|
||||||
|
// https://github.com/Zirak/SO-ChatBot/blob/master/source/codeWorker.js
|
||||||
|
|
||||||
|
const wl = {
|
||||||
|
self: 1,
|
||||||
|
onmessage: 1,
|
||||||
|
postMessage: 1,
|
||||||
|
global: 1,
|
||||||
|
wl: 1,
|
||||||
|
eval: 1,
|
||||||
|
Array: 1,
|
||||||
|
Boolean: 1,
|
||||||
|
Date: 1,
|
||||||
|
Function: 1,
|
||||||
|
Number: 1,
|
||||||
|
Object: 1,
|
||||||
|
RegExp: 1,
|
||||||
|
String: 1,
|
||||||
|
Error: 1,
|
||||||
|
EvalError: 1,
|
||||||
|
RangeError: 1,
|
||||||
|
ReferenceError: 1,
|
||||||
|
SyntaxError: 1,
|
||||||
|
TypeError: 1,
|
||||||
|
URIError: 1,
|
||||||
|
decodeURI: 1,
|
||||||
|
decodeURIComponent: 1,
|
||||||
|
encodeURI: 1,
|
||||||
|
encodeURIComponent: 1,
|
||||||
|
isFinite: 1,
|
||||||
|
isNaN: 1,
|
||||||
|
parseFloat: 1,
|
||||||
|
parseInt: 1,
|
||||||
|
Infinity: 1,
|
||||||
|
JSON: 1,
|
||||||
|
Math: 1,
|
||||||
|
NaN: 1,
|
||||||
|
undefined: 1,
|
||||||
|
|
||||||
|
// Chrome errors if you attempt to write over either of these properties, so put them in the whitelist
|
||||||
|
// https://github.com/owl-factory/lantern/blob/addda28034d5d30a7ea720646aa56fefa8f05cf4/archive/src/nodes/sandbox/workers/sandboxed-code.worker.ts#L47
|
||||||
|
TEMPORARY: 1,
|
||||||
|
PERSISTENT: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-arrow-callback, func-names
|
||||||
|
Object.getOwnPropertyNames(myGlobal).forEach(function (prop) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (!wl.hasOwnProperty(prop)) {
|
||||||
|
Object.defineProperty(myGlobal, prop, {
|
||||||
|
// eslint-disable-next-line func-names, object-shorthand
|
||||||
|
get: function () {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw `Security Exception: cannot access ${prop}`;
|
||||||
|
},
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-proto, prefer-arrow-callback, func-names
|
||||||
|
Object.getOwnPropertyNames(myGlobal.__proto__).forEach(function (prop) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (!wl.hasOwnProperty(prop)) {
|
||||||
|
// eslint-disable-next-line no-proto
|
||||||
|
Object.defineProperty(myGlobal.__proto__, prop, {
|
||||||
|
// eslint-disable-next-line func-names, object-shorthand
|
||||||
|
get: function () {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw `Security Exception: cannot access ${prop}`;
|
||||||
|
},
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Array(5000000000).join("adasdadadasd") instantly crashing some browser tabs
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.defineProperty(Array.prototype, 'join', {
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
// eslint-disable-next-line wrap-iife, func-names
|
||||||
|
value: function (old) {
|
||||||
|
// eslint-disable-next-line func-names
|
||||||
|
return function (arg) {
|
||||||
|
// @ts-expect-error dunno how to fix
|
||||||
|
if (this.length > 500 || (arg && arg.length > 500)) {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw 'Exception: too many items';
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/prefer-reflect-apply, prefer-rest-params
|
||||||
|
// @ts-expect-error dunno how to fix
|
||||||
|
return old.apply(this, arg);
|
||||||
|
};
|
||||||
|
}(Array.prototype.join),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
https://github.com/Zirak/SO-ChatBot/blob/accbfb4b8738781afaf4f080a6bb0337e13f7c25/source/codeWorker.js#L87
|
||||||
|
|
||||||
|
DOM specification doesn't define an enumerable `fetch` function object on
|
||||||
|
the global object so we add the property here, and the following code will
|
||||||
|
blacklist it. (`fetch` descends from `GlobalFetch`, and is thus present in
|
||||||
|
worker code as well)
|
||||||
|
Just in case someone runs the bot on some old browser where `fetch` is not
|
||||||
|
defined anyways, this will have no effect.
|
||||||
|
Reason for blacklisting fetch: well, same as XHR.
|
||||||
|
*/
|
||||||
|
// @ts-expect-error expected
|
||||||
|
myGlobal.fetch = undefined;
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line wrap-iife, func-names
|
||||||
|
(function () {
|
||||||
|
onmessage = (event) => {
|
||||||
|
// eslint-disable-next-line strict, lines-around-directive
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { code, id, context: contextStr } = event.data;
|
||||||
|
const context = { ...JSON.parse(contextStr) };
|
||||||
|
|
||||||
|
try {
|
||||||
|
// https://stackoverflow.com/questions/8403108/calling-eval-in-particular-context
|
||||||
|
// eslint-disable-next-line unicorn/new-for-builtins, no-new-func
|
||||||
|
const result = Function(`\nwith (this) { return (${code}); }`).call(context);
|
||||||
|
postMessage({ id, data: result });
|
||||||
|
} catch (e) {
|
||||||
|
postMessage({ id, error: `${e}` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
9
types.ts
9
types.ts
@ -111,3 +111,12 @@ export interface ApiKeyboardActionRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Html5ifyMode = 'fastest' | 'fast-audio-remux' | 'fast-audio' | 'fast' | 'slow' | 'slow-audio' | 'slowest';
|
export type Html5ifyMode = 'fastest' | 'fast-audio-remux' | 'fast-audio' | 'fast' | 'slow' | 'slow-audio' | 'slowest';
|
||||||
|
|
||||||
|
// This is the contract with the user, see https://github.com/mifi/lossless-cut/blob/master/expressions.md
|
||||||
|
export interface ScopeSegment {
|
||||||
|
label: string,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
duration: number,
|
||||||
|
tags: Record<string, string>,
|
||||||
|
}
|
||||||
|
85
yarn.lock
85
yarn.lock
@ -545,15 +545,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/runtime@npm:^7.24.4":
|
|
||||||
version: 7.24.5
|
|
||||||
resolution: "@babel/runtime@npm:7.24.5"
|
|
||||||
dependencies:
|
|
||||||
regenerator-runtime: "npm:^0.14.0"
|
|
||||||
checksum: e0f4f4d4503f7338749d1dd92361ad132d683bde64e6b61d6c855e100dcd01592295fcfdcc960c946b85ef7908dc2f501080da58447c05812cf3cd80c599bb62
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@babel/template@npm:^7.20.7":
|
"@babel/template@npm:^7.20.7":
|
||||||
version: 7.20.7
|
version: 7.20.7
|
||||||
resolution: "@babel/template@npm:7.20.7"
|
resolution: "@babel/template@npm:7.20.7"
|
||||||
@ -3907,13 +3898,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"complex.js@npm:^2.1.1":
|
|
||||||
version: 2.1.1
|
|
||||||
resolution: "complex.js@npm:2.1.1"
|
|
||||||
checksum: 1905d5204dd8a4d6f591182aca2045986f1ff3c5373e455ccd10c6ee2905bf1d3811a313d38c68f8a8507523202f91e25177387e3adc386c1b5b5ec2f13a6dbb
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"compute-scroll-into-view@npm:^1.0.14, compute-scroll-into-view@npm:^1.0.17":
|
"compute-scroll-into-view@npm:^1.0.14, compute-scroll-into-view@npm:^1.0.17":
|
||||||
version: 1.0.17
|
version: 1.0.17
|
||||||
resolution: "compute-scroll-into-view@npm:1.0.17"
|
resolution: "compute-scroll-into-view@npm:1.0.17"
|
||||||
@ -4254,13 +4238,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"decimal.js@npm:^10.4.3":
|
|
||||||
version: 10.4.3
|
|
||||||
resolution: "decimal.js@npm:10.4.3"
|
|
||||||
checksum: de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"decompress-response@npm:^6.0.0":
|
"decompress-response@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "decompress-response@npm:6.0.0"
|
resolution: "decompress-response@npm:6.0.0"
|
||||||
@ -5218,13 +5195,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"escape-latex@npm:^1.2.0":
|
|
||||||
version: 1.2.0
|
|
||||||
resolution: "escape-latex@npm:1.2.0"
|
|
||||||
checksum: 73a787319f0965ecb8244bb38bf3a3cba872f0b9a5d3da8821140e9f39fe977045dc953a62b1a2bed4d12bfccbe75a7d8ec786412bf00739eaa2f627d0a8e0d6
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"escape-string-regexp@npm:5.0.0":
|
"escape-string-regexp@npm:5.0.0":
|
||||||
version: 5.0.0
|
version: 5.0.0
|
||||||
resolution: "escape-string-regexp@npm:5.0.0"
|
resolution: "escape-string-regexp@npm:5.0.0"
|
||||||
@ -5975,13 +5945,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"fraction.js@npm:4.3.4":
|
|
||||||
version: 4.3.4
|
|
||||||
resolution: "fraction.js@npm:4.3.4"
|
|
||||||
checksum: 3a1e6b268038ffdea625fab6a8d155d7ab644d35d0c99bc59084bfd29fbc714f3a38381b0627751ddb5f188bcde0b3f48c27e80eeb2ecd440825a7d2cd2bf9f1
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"framer-motion@npm:^9.0.3":
|
"framer-motion@npm:^9.0.3":
|
||||||
version: 9.0.3
|
version: 9.0.3
|
||||||
resolution: "framer-motion@npm:9.0.3"
|
resolution: "framer-motion@npm:9.0.3"
|
||||||
@ -7535,13 +7498,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"javascript-natural-sort@npm:^0.7.1":
|
|
||||||
version: 0.7.1
|
|
||||||
resolution: "javascript-natural-sort@npm:0.7.1"
|
|
||||||
checksum: 7bf6eab67871865d347f09a95aa770f9206c1ab0226bcda6fdd9edec340bf41111a7f82abac30556aa16a21cfa3b2b1ca4a362c8b73dd5ce15220e5d31f49d79
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"js-cookie@npm:^2.2.1":
|
"js-cookie@npm:^2.2.1":
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
resolution: "js-cookie@npm:2.2.1"
|
resolution: "js-cookie@npm:2.2.1"
|
||||||
@ -7993,7 +7949,6 @@ __metadata:
|
|||||||
ky: "npm:^0.33.1"
|
ky: "npm:^0.33.1"
|
||||||
lodash: "npm:^4.17.19"
|
lodash: "npm:^4.17.19"
|
||||||
luxon: "npm:^3.3.0"
|
luxon: "npm:^3.3.0"
|
||||||
mathjs: "npm:^12.4.2"
|
|
||||||
mime-types: "npm:^2.1.14"
|
mime-types: "npm:^2.1.14"
|
||||||
mkdirp: "npm:^1.0.3"
|
mkdirp: "npm:^1.0.3"
|
||||||
morgan: "npm:^1.10.0"
|
morgan: "npm:^1.10.0"
|
||||||
@ -8185,25 +8140,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"mathjs@npm:^12.4.2":
|
|
||||||
version: 12.4.2
|
|
||||||
resolution: "mathjs@npm:12.4.2"
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime": "npm:^7.24.4"
|
|
||||||
complex.js: "npm:^2.1.1"
|
|
||||||
decimal.js: "npm:^10.4.3"
|
|
||||||
escape-latex: "npm:^1.2.0"
|
|
||||||
fraction.js: "npm:4.3.4"
|
|
||||||
javascript-natural-sort: "npm:^0.7.1"
|
|
||||||
seedrandom: "npm:^3.0.5"
|
|
||||||
tiny-emitter: "npm:^2.1.0"
|
|
||||||
typed-function: "npm:^4.1.1"
|
|
||||||
bin:
|
|
||||||
mathjs: bin/cli.js
|
|
||||||
checksum: 4b88ac1b137d00b8f3d66f4d1662d3670399390b59623ecf3ab7d587ba18be7b97ce9c5b07e953029ac75f48567d675c99323889ae231eb071ddd84db5dd699c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"mdn-data@npm:2.0.14":
|
"mdn-data@npm:2.0.14":
|
||||||
version: 2.0.14
|
version: 2.0.14
|
||||||
resolution: "mdn-data@npm:2.0.14"
|
resolution: "mdn-data@npm:2.0.14"
|
||||||
@ -10362,13 +10298,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"seedrandom@npm:^3.0.5":
|
|
||||||
version: 3.0.5
|
|
||||||
resolution: "seedrandom@npm:3.0.5"
|
|
||||||
checksum: acad5e516c04289f61c2fb9848f449b95f58362b75406b79ec51e101ec885293fc57e3675d2f39f49716336559d7190f7273415d185fead8cd27b171ebf7d8fb
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"semver-compare@npm:^1.0.0":
|
"semver-compare@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "semver-compare@npm:1.0.0"
|
resolution: "semver-compare@npm:1.0.0"
|
||||||
@ -11280,13 +11209,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tiny-emitter@npm:^2.1.0":
|
|
||||||
version: 2.1.0
|
|
||||||
resolution: "tiny-emitter@npm:2.1.0"
|
|
||||||
checksum: 75633f4de4f47f43af56aff6162f25b87be7efc6f669fda256658f3c3f4a216f23dc0d13200c6fafaaf1b0c7142f0201352fb06aec0b77f68aea96be898f4516
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"tiny-invariant@npm:1.2.0":
|
"tiny-invariant@npm:1.2.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "tiny-invariant@npm:1.2.0"
|
resolution: "tiny-invariant@npm:1.2.0"
|
||||||
@ -11614,13 +11536,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"typed-function@npm:^4.1.1":
|
|
||||||
version: 4.1.1
|
|
||||||
resolution: "typed-function@npm:4.1.1"
|
|
||||||
checksum: 0ef538d5f02e5c40659cccc14b5f2727f0e4181f11d91bb7897327c33cc2893de7e92343b6b32e1bb15e44a215a1e92e27ab2aa1353b100a9a2697abf2989a0c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"typedarray-to-buffer@npm:^3.1.5":
|
"typedarray-to-buffer@npm:^3.1.5":
|
||||||
version: 3.1.5
|
version: 3.1.5
|
||||||
resolution: "typedarray-to-buffer@npm:3.1.5"
|
resolution: "typedarray-to-buffer@npm:3.1.5"
|
||||||
|
Loading…
Reference in New Issue
Block a user