1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-22 10:22:31 +01:00

Merge branch 'master' into mediasource

# Conflicts:
#	src/App.jsx
This commit is contained in:
Mikael Finstad 2024-02-08 00:14:23 +07:00
commit 476ab11f63
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
22 changed files with 456 additions and 325 deletions

View File

@ -35,12 +35,6 @@ For Windows, you may have to install [7z](https://www.7-zip.org/download.html),
npm start
```
### Building for production
See:
- https://www.electron.build/
- https://github.com/mifi/lossless-cut/blob/master/.github/workflows/build.yml
## Building mas-dev (Mac App Store) build locally
This will sign using the development provisioning profile:
@ -62,7 +56,7 @@ NOTE: when MAS (dev) build, Application Support will instead be here:
rm -rf ~/Library/Containers/no.mifi.losslesscut-mac
```
## Windows Store
## Windows Store notes
Windows store version is built as a Desktop Bridge app (with `runFullTrust` capability). This means the app has access to essentially everything the user has access to, and even `internetClient` is redundant.
@ -76,13 +70,21 @@ For per-platform build/signing setup, see [this article](https://mifi.no/blog/au
### Release new version
- Commit changes
- If Mac App Store / Windows Store
- Checkout branch `stores`
- Merge `master` into `stores`
- `npm version ...`
- `git push --follow-tags`
- Wait for build and draft in Github actions
- Open draft in github and add Release notes
- For files `LosslessCut-mac-universal.pkg` and `LosslessCut-win-x64.appx` add prefix `-DO-NOT-DOWNLOAD`
- Release the draft
- If intended as Github, release the draft
- If store-only release, release the draft as **pre-release**
### After release
- If Mac App Store / Windows Store
- Merge `stores` into `master`
- Bump [snap version](https://snapcraft.io/losslesscut/listing)
- `npm run scan-i18n` to get the newest English strings and push so weblate gets them

View File

@ -18,6 +18,7 @@
<url type="bugtracker">https://github.com/mifi/lossless-cut/issues</url>
<url type="donation">https://paypal.me/mifino/usd</url>
<releases>
<release version="3.60.0" date="2024-01-05"/>
<release version="3.59.1" date="2023-12-22"/>
<release version="3.59.0" date="2023-12-22"/>
<release version="3.58.0" date="2023-10-16"/>

View File

@ -3,7 +3,7 @@
"productName": "LosslessCut",
"description": "The swiss army knife of lossless video/audio editing",
"copyright": "Copyright © 2021 ${author}",
"version": "3.59.1",
"version": "3.60.0",
"main": "public/electron.js",
"homepage": "./",
"scripts": {
@ -13,7 +13,7 @@
"icon-gen": "mkdirp icon-build build-resources/appx && node script/icon-gen.mjs",
"download-ffmpeg-darwin-x64": "mkdirp ffmpeg/darwin-x64 && cd ffmpeg/darwin-x64 && wget https://github.com/mifi/ffmpeg-build-script/releases/download/6.0/ffmpeg -O ffmpeg && wget https://github.com/mifi/ffmpeg-build-script/releases/download/6.0/ffprobe -O ffprobe && chmod +x ffmpeg && chmod +x ffprobe",
"download-ffmpeg-darwin-arm64": "mkdirp ffmpeg/darwin-arm64 && cd ffmpeg/darwin-arm64 && wget https://github.com/mifi/ffmpeg-builds/releases/download/6.0/ffmpeg-darwin-arm64-v6.0 -O ffmpeg && wget https://github.com/mifi/ffmpeg-builds/releases/download/6.0/ffprobe-darwin-arm64-v6.0 -O ffprobe && chmod +x ffmpeg && chmod +x ffprobe",
"download-ffmpeg-linux-x64": "mkdirp ffmpeg/linux-x64 && cd ffmpeg/linux-x64 && wget https://github.com/mifi/ffmpeg-builds/releases/download/6.0/ffmpeg-n6.0-12-ga6dc92968a-linux64-gpl-shared-6.0.tar.xz -O ffmpeg-ffprobe.xz && tar -xv -f ffmpeg-ffprobe.xz && mv ffmpeg-n6.0-12-ga6dc92968a-linux64-gpl-shared-6.0 extracted && mkdirp lib && mv extracted/bin/{ffmpeg,ffprobe} extracted/lib/lib*.so* lib",
"download-ffmpeg-linux-x64": "mkdirp ffmpeg/linux-x64 && cd ffmpeg/linux-x64 && wget https://github.com/mifi/ffmpeg-builds/releases/download/6.0/ffmpeg-n6.0-12-ga6dc92968a-linux64-gpl-shared-6.0.tar.xz -O ffmpeg-ffprobe.xz && tar -xv -f ffmpeg-ffprobe.xz && mv ffmpeg-n6.0-12-ga6dc92968a-linux64-gpl-shared-6.0 extracted && mkdirp lib && mv extracted/bin/ffmpeg extracted/bin/ffprobe extracted/lib/lib*.so* lib",
"download-ffmpeg-win32-x64": "mkdirp ffmpeg/win32-x64 && cd ffmpeg/win32-x64 && npx download-cli https://github.com/mifi/ffmpeg-builds/releases/download/6.0/ffmpeg-n6.0-12-ga6dc92968a-win64-gpl-shared-6.0.zip --out . --filename ffmpeg-ffprobe.zip && 7z x ffmpeg-ffprobe.zip && mkdirp lib && cd ffmpeg-n6.0-12-ga6dc92968a-win64-gpl-shared-6.0/bin && npx shx mv ffmpeg.exe ffprobe.exe *.dll ../../lib",
"build": "yarn icon-gen && vite build --outDir vite-dist",
"tsc": "tsc --build",
@ -99,7 +99,7 @@
"use-debounce": "^5.1.0",
"use-trace-update": "^1.3.0",
"uuid": "^8.3.2",
"vite": "^4.1.5",
"vite": "^4.5.2",
"vitest": "^0.28.5",
"wait-on": "^7.0.1"
},

View File

@ -14,7 +14,7 @@ const { stat } = require('fs/promises');
const logger = require('./logger');
const menu = require('./menu');
const configStore = require('./configStore');
const { frontendBuildDir } = require('./util');
const { frontendBuildDir, isLinux, isWindows } = require('./util');
const attachContextMenu = require('./contextMenu');
const HttpServer = require('./httpServer');
@ -33,6 +33,9 @@ unhandled({
});
const appName = 'LosslessCut';
const copyrightYear = 2024;
const appVersion = app.getVersion();
app.name = appName;
@ -40,11 +43,21 @@ const isStoreBuild = process.windowsStore || process.mas;
const showVersion = !isStoreBuild;
const aboutPanelOptions = { applicationName: appName };
/** @type import('electron').AboutPanelOptionsOptions */
const aboutPanelOptions = {
applicationName: appName,
copyright: `Copyright © ${copyrightYear} Mikael Finstad ❤️ 🇳🇴`,
version: '', // not very useful (MacOS only, and same as applicationVersion)
};
// https://github.com/electron/electron/issues/18918
// https://github.com/mifi/lossless-cut/issues/1537
if (isLinux) {
aboutPanelOptions.version = appVersion;
}
if (!showVersion) {
// version will be wrong in Store builds
aboutPanelOptions.applicationVersion = '';
aboutPanelOptions.version = '';
// https://github.com/mifi/lossless-cut/issues/1882
aboutPanelOptions.applicationVersion = `${process.windowsStore ? 'Microsoft Store' : 'App Store'} edition, based on GitHub v${appVersion}`;
}
// https://www.electronjs.org/docs/latest/api/app#appsetaboutpaneloptionsoptions
@ -121,13 +134,13 @@ function createWindow() {
webSecurity: !isDev,
},
backgroundColor: darkMode ? '#333' : '#fff',
autoHideMenuBar: isWindows, // https://github.com/mifi/lossless-cut/issues/543#issuecomment-1872945955
});
remote.enable(mainWindow.webContents);
attachContextMenu(mainWindow);
if (isDev) mainWindow.loadURL('http://localhost:3001');
// Need to useloadFile for special characters https://github.com/mifi/lossless-cut/issues/40
else mainWindow.loadFile(`${frontendBuildDir}/index.html`);

View File

@ -68,7 +68,7 @@ import {
getOutPath, getSuffixedOutPath, handleError, getOutDir,
isStoreBuild, dragPreventer,
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile,
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName, mustDisallowVob, readVideoTs, getImportProjectType,
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName, mustDisallowVob, readVideoTs, readDirRecursively, getImportProjectType,
calcShouldShowWaveform, calcShouldShowKeyframes, mediaSourceQualities,
} from './util';
import { toast, errorToast } from './swal';
@ -88,6 +88,7 @@ import isDev from './isDev';
const electron = window.require('electron');
const { exists } = window.require('fs-extra');
const { lstat } = window.require('fs/promises');
const filePathToUrl = window.require('file-url');
const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path');
@ -1178,8 +1179,8 @@ function App() {
console.log('outSegTemplateOrDefault', outSegTemplateOrDefault);
const { outSegFileNames, outSegError } = generateOutSegFileNames({ segments: segmentsToExport, template: outSegTemplateOrDefault });
if (outSegError != null) {
const { outSegFileNames, outSegProblems } = generateOutSegFileNames({ segments: segmentsToExport, template: outSegTemplateOrDefault });
if (outSegProblems.error != null) {
console.warn('Output segments file name invalid, using default instead', outSegFileNames);
}
@ -1296,6 +1297,7 @@ function App() {
await onExportConfirm();
} else {
setExportConfirmVisible(true);
setStreamsSelectorShown(false);
}
}, [filePath, exportConfirmEnabled, exportConfirmVisible, onExportConfirm]);
@ -1649,7 +1651,6 @@ function App() {
const handleShowStreamsSelectorClick = useCallback(() => {
setStreamsSelectorShown(true);
setExportConfirmVisible(false);
}, []);
const extractAllStreams = useCallback(async () => {
@ -1800,12 +1801,28 @@ function App() {
[lastOpenedPathRef.current] = filePaths;
// https://en.wikibooks.org/wiki/Inside_DVD-Video/Directory_Structure
if (filePaths.length === 1 && /^VIDEO_TS$/i.test(basename(filePaths[0]))) {
if (mustDisallowVob()) return;
filePaths = await readVideoTs(filePaths[0]);
// first check if it is a single directory, and if so, read it recursively
if (filePaths.length === 1) {
const firstFilePath = filePaths[0];
const firstFileStat = await lstat(firstFilePath);
if (firstFileStat.isDirectory()) {
console.log('Reading directory...');
filePaths = await readDirRecursively(firstFilePath);
}
}
// Only allow opening regular files
// eslint-disable-next-line no-restricted-syntax
for (const path of filePaths) {
// eslint-disable-next-line no-await-in-loop
const fileStat = await lstat(path);
if (!fileStat.isFile()) {
errorToast(i18n.t('Cannot open anything else than regular files'));
console.warn('Not a file:', path);
return;
}
}
if (filePaths.length > 1) {
if (alwaysConcatMultipleFiles) {
@ -1820,6 +1837,12 @@ function App() {
// filePaths.length is now 1
const firstFilePath = filePaths[0];
// https://en.wikibooks.org/wiki/Inside_DVD-Video/Directory_Structure
if (/^VIDEO_TS$/i.test(basename(firstFilePath))) {
if (mustDisallowVob()) return;
filePaths = await readVideoTs(firstFilePath);
}
if (workingRef.current) return;
try {
setWorking({ text: i18n.t('Loading file') });
@ -1900,7 +1923,7 @@ function App() {
}, [alwaysConcatMultipleFiles, batchLoadPaths, setWorking, isFileOpened, batchFiles.length, userOpenSingleFile, checkFileOpened, loadEdlFile, enableAskForFileOpenAction, addStreamSourceFile, filePath]);
const openFilesDialog = useCallback(async () => {
const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile', 'multiSelections'], defaultPath: lastOpenedPathRef.current });
const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile', 'openDirectory', 'multiSelections'], defaultPath: lastOpenedPathRef.current });
if (canceled) return;
userOpenFiles(filePaths);
}, [userOpenFiles]);
@ -2578,7 +2601,9 @@ function App() {
/>
</div>
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} style={{ padding: '1em 0' }}>
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} maxWidth={1000}>
{mainStreams && (
<StreamsSelector
mainFilePath={filePath}
@ -2606,15 +2631,13 @@ function App() {
)}
</Sheet>
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
<LastCommandsSheet
visible={lastCommandsVisible}
onTogglePress={toggleLastCommands}
ffmpegCommandLog={ffmpegCommandLog}
/>
<Sheet visible={settingsVisible} onClosePress={toggleSettings} style={{ padding: '1em 0' }}>
<Sheet visible={settingsVisible} onClosePress={toggleSettings}>
<Settings
onTunerRequested={onTunerRequested}
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}

View File

@ -8,7 +8,7 @@ const LastCommandsSheet = memo(({ visible, onTogglePress, ffmpegCommandLog }) =>
const { t } = useTranslation();
return (
<Sheet visible={visible} onClosePress={onTogglePress} style={{ paddingTop: '2em' }}>
<Sheet visible={visible} onClosePress={onTogglePress} style={{ padding: '0 1em' }}>
<h2>{t('Last ffmpeg commands')}</h2>
{ffmpegCommandLog.length > 0 ? (

View File

@ -42,7 +42,7 @@ const Waveforms = memo(({ calculateTimelinePercent, durationSafe, waveforms, zoo
const CommandedTime = memo(({ commandedTimePercent }) => {
const color = 'var(--gray12)';
const commonStyle = { left: commandedTimePercent, position: 'absolute', zIndex: 4, pointerEvents: 'none' };
const commonStyle = { left: commandedTimePercent, position: 'absolute', pointerEvents: 'none' };
return (
<>
<FaCaretDown style={{ ...commonStyle, top: 0, color, fontSize: 14, marginLeft: -7, marginTop: -6 }} />
@ -275,7 +275,7 @@ const Timeline = memo(({
const nextThumbTime = nextThumbnail ? nextThumbnail.time : durationSafe;
const maxWidthPercent = ((nextThumbTime - thumbnail.time) / durationSafe) * 100 * 0.9;
return (
<img key={thumbnail.url} src={thumbnail.url} alt="" style={{ position: 'absolute', left: `${leftPercent}%`, height: '100%', boxSizing: 'border-box', zIndex: 1, maxWidth: `${maxWidthPercent}%`, objectFit: 'cover', border: '1px solid rgba(255, 255, 255, 0.5)', borderBottomRightRadius: 15, borderTopLeftRadius: 15, borderTopRightRadius: 15, pointerEvents: 'none' }} />
<img key={thumbnail.url} src={thumbnail.url} alt="" style={{ position: 'absolute', left: `${leftPercent}%`, height: '100%', boxSizing: 'border-box', maxWidth: `${maxWidthPercent}%`, objectFit: 'cover', border: '1px solid rgba(255, 255, 255, 0.5)', borderBottomRightRadius: 15, borderTopLeftRadius: 15, borderTopRightRadius: 15, pointerEvents: 'none' }} />
);
})}
</div>
@ -285,13 +285,6 @@ const Timeline = memo(({
style={{ height: timelineHeight, width: `${zoom * 100}%`, position: 'relative', backgroundColor: timelineBackground, transition: darkModeTransition }}
ref={timelineWrapperRef}
>
{currentTimePercent !== undefined && (
<motion.div transition={{ type: 'spring', damping: 70, stiffness: 800 }} animate={{ left: currentTimePercent }} style={{ position: 'absolute', bottom: 0, top: 0, zIndex: 3, backgroundColor: 'var(--gray12)', width: currentTimeWidth, pointerEvents: 'none' }} />
)}
{commandedTimePercent !== undefined && (
<CommandedTime commandedTimePercent={commandedTimePercent} />
)}
{apparentCutSegments.map((seg, i) => {
if (seg.start === 0 && seg.end === 0) return null; // No video loaded
@ -325,10 +318,17 @@ const Timeline = memo(({
{shouldShowKeyframes && !areKeyframesTooClose && neighbouringKeyFrames.map((f) => (
<div key={f.time} style={{ position: 'absolute', top: 0, bottom: 0, left: `${(f.time / durationSafe) * 100}%`, marginLeft: -1, width: 1, background: 'var(--gray11)', pointerEvents: 'none' }} />
))}
{currentTimePercent !== undefined && (
<motion.div transition={{ type: 'spring', damping: 70, stiffness: 800 }} animate={{ left: currentTimePercent }} style={{ position: 'absolute', bottom: 0, top: 0, backgroundColor: 'var(--gray12)', width: currentTimeWidth, pointerEvents: 'none' }} />
)}
{commandedTimePercent !== undefined && (
<CommandedTime commandedTimePercent={commandedTimePercent} />
)}
</div>
</div>
<div style={{ position: 'absolute', height: timelineHeight, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none', zIndex: 2 }}>
<div style={{ position: 'absolute', height: timelineHeight, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>
<div style={{ background: 'rgba(0,0,0,0.4)', borderRadius: 3, padding: '2px 4px', color: 'rgba(255, 255, 255, 0.8)' }}>
{formatTimeAndFrames(displayTime)}{isZoomed ? ` ${displayTimePercent}` : ''}
</div>

View File

@ -356,11 +356,25 @@ const ExportConfirm = memo(({
</>
)}
{!needSmartCut && (
{!needSmartCut && (() => {
const avoidNegativeTsWarn = (() => {
if (willMerge) {
if (avoidNegativeTs !== 'make_non_negative') {
return t('When merging, it\'s generally recommended to set this to "make_non_negative"');
}
return undefined;
}
if (!['make_zero', 'auto'].includes(avoidNegativeTs)) {
return t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' });
}
return undefined;
})();
return (
<tr>
<td>
&quot;avoid_negative_ts&quot;
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <div style={warningStyle}>{t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' })}</div>}
{`"${'avoid_negative_ts'}"`}
{avoidNegativeTsWarn != null && <div style={warningStyle}>{avoidNegativeTsWarn}</div>}
</td>
<td>
<Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}>
@ -371,14 +385,15 @@ const ExportConfirm = memo(({
</Select>
</td>
<td>
{!['make_zero', 'auto'].includes(avoidNegativeTs) ? (
{avoidNegativeTsWarn != null ? (
<WarningSignIcon verticalAlign="middle" color="warning" />
) : (
<HelpIcon onClick={onAvoidNegativeTsHelpPress} />
)}
</td>
</tr>
)}
);
})()}
<tr>
<td>
@ -397,7 +412,7 @@ const ExportConfirm = memo(({
</div>
</motion.div>
<div style={{ zIndex: 11, position: 'fixed', right: 0, bottom: 0, display: 'flex', alignItems: 'center', margin: 5 }}>
<div style={{ position: 'fixed', right: 0, bottom: 0, display: 'flex', alignItems: 'center', margin: 5 }}>
<motion.div
initial={{ opacity: 0, translateX: 50 }}
animate={{ opacity: 1, translateX: 0 }}

View File

@ -4,7 +4,6 @@
right: 0;
top: 0;
bottom: 0;
z-index: 10;
background: var(--whiteA11);
color: var(--gray12);
backdrop-filter: blur(30px);

View File

@ -7,9 +7,11 @@ import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import uniq from 'lodash/uniq';
import useUserSettings from '../hooks/useUserSettings';
import Swal from '../swal';
import SetCutpointButton from './SetCutpointButton';
import SegmentCutpointButton from './SegmentCutpointButton';
import { getModifier } from '../hooks/useTimelineScroll';
const renderKeys = (keys) => keys.map((key, i) => (
@ -105,13 +107,15 @@ const CreateBinding = memo(({
);
});
const rowStyle = { display: 'flex', alignItems: 'center', margin: '6px 0' };
const rowStyle = { display: 'flex', alignItems: 'center', margin: '.2em 0', borderBottom: '1px solid rgba(0,0,0,0.1)', paddingBottom: '.5em' };
const KeyboardShortcuts = memo(({
keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg, mainActions,
}) => {
const { t } = useTranslation();
const { mouseWheelZoomModifierKey } = useUserSettings();
const { actionsMap, extraLinesPerCategory } = useMemo(() => {
const playbackCategory = t('Playback');
const selectivePlaybackCategory = t('Playback/preview segments only');
@ -136,8 +140,9 @@ const KeyboardShortcuts = memo(({
<div key="2" style={{ ...rowStyle, alignItems: 'center' }}>
<Text>{t('Pan timeline')}</Text>
<div style={{ flexGrow: 1 }} />
{getModifier(mouseWheelZoomModifierKey).map((v) => <kbd key={v} style={{ marginRight: '.7em' }}>{v}</kbd>)}
<FaMouse style={{ marginRight: 3 }} />
<Text>{t('Mouse scroll/wheel left/right')}</Text>
<Text>{t('Mouse scroll/wheel up/down')}</Text>
</div>,
],
},
@ -649,7 +654,7 @@ const KeyboardShortcuts = memo(({
return (
<>
<div style={{ color: 'black' }}>
<div style={{ color: 'black', marginBottom: '1em' }}>
<div>
<SearchInput value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search" width="100%" />
</div>
@ -691,7 +696,7 @@ const KeyboardShortcuts = memo(({
);
})}
{extraLinesPerCategory[category]}
{extraLinesPerCategory[category] && <div style={{ marginTop: '.8em' }}>{extraLinesPerCategory[category]}</div>}
</div>
))}
</div>

View File

@ -23,13 +23,13 @@ const formatVariable = (variable) => `\${${variable}}`;
const extVar = formatVariable('EXT');
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe }) => {
const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings();
const [text, setText] = useState(outSegTemplate);
const [debouncedText] = useDebounce(text, 500);
const [validText, setValidText] = useState();
const [error, setError] = useState();
const [outSegProblems, setOutSegProblems] = useState({ error: undefined, sameAsInputFileNameWarning: false });
const [outSegFileNames, setOutSegFileNames] = useState();
const [shown, setShown] = useState();
const inputRef = useRef();
@ -42,22 +42,16 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
if (debouncedText == null) return;
try {
const { outSegFileNames: newOutSegFileNames, outSegError } = generateOutSegFileNames({ template: debouncedText });
setOutSegFileNames(newOutSegFileNames);
if (outSegError) {
setError(outSegError);
setValidText();
return;
}
setValidText(debouncedText);
setError();
const outSegs = generateOutSegFileNames({ template: debouncedText });
setOutSegFileNames(outSegs.outSegFileNames);
setOutSegProblems(outSegs.outSegProblems);
setValidText(outSegs.outSegProblems.error == null ? debouncedText : undefined);
} catch (err) {
console.error(err);
setValidText();
setError(err.message);
setOutSegProblems({ error: err.message });
}
}, [debouncedText, generateOutSegFileNames, getOutSegError, t]);
}, [debouncedText, generateOutSegFileNames, t]);
// eslint-disable-next-line no-template-curly-in-string
const isMissingExtension = validText != null && !validText.endsWith(extVar);
@ -80,8 +74,8 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
}, [setOutSegTemplate]);
const onHideClick = useCallback(() => {
if (error == null) setShown(false);
}, [error]);
if (outSegProblems.error == null) setShown(false);
}, [outSegProblems.error]);
const onShowClick = useCallback(() => {
if (!shown) setShown(true);
@ -89,7 +83,7 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
const onTextChange = useCallback((e) => setText(e.target.value), []);
const needToShow = shown || error != null;
const needToShow = shown || outSegProblems.error != null || outSegProblems.sameAsInputFileNameWarning;
const onVariableClick = useCallback((variable) => {
const input = inputRef.current;
@ -132,7 +126,18 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
))}
</div>
{error != null && <div style={{ marginBottom: '1em' }}><ErrorIcon color="var(--red9)" size={14} verticalAlign="baseline" /> {error}</div>}
{outSegProblems.error != null && (
<div style={{ marginBottom: '1em' }}>
<ErrorIcon color="var(--red9)" size={14} verticalAlign="baseline" /> {outSegProblems.error}
</div>
)}
{outSegProblems.error == null && outSegProblems.sameAsInputFileNameWarning && (
<div style={{ marginBottom: '1em' }}>
<WarningSignIcon verticalAlign="middle" color="var(--amber9)" />{' '}
{i18n.t('Output file name is the same as the source file name. This increases the risk of accidentally overwriting or deleting source files!')}
</div>
)}
{isMissingExtension && (
<div style={{ marginBottom: '1em' }}>

View File

@ -78,11 +78,11 @@ const Settings = memo(({
return (
<>
<div style={{ margin: '0 2em' }}>
<div style={{ padding: '1.5em 2em' }}>
<div>{t('Hover mouse over buttons in the main interface to see which function they have')}</div>
</div>
<table style={{ marginTop: 20 }} className={styles.settings}>
<table className={styles.settings}>
<thead>
<tr className={styles.header}>
<th>{t('Settings')}</th>
@ -321,8 +321,8 @@ const Settings = memo(({
<KeyCell>{t('Mouse wheel zoom modifier key')}</KeyCell>
<td>
<Select value={mouseWheelZoomModifierKey} onChange={(e) => setMouseWheelZoomModifierKey(e.target.value)}>
{Object.entries(getModifierKeyNames()).map(([key, value]) => (
<option key={key} value={key}>{value}</option>
{Object.entries(getModifierKeyNames()).map(([key, values]) => (
<option key={key} value={key}>{values.join(' / ')}</option>
))}
</Select>
</td>

View File

@ -1,27 +1,33 @@
import React, { memo } from 'react';
import { IoIosCloseCircleOutline } from 'react-icons/io';
import { motion, AnimatePresence } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import styles from './Sheet.module.css';
const Sheet = memo(({ visible, onClosePress, style, children }) => (
const Sheet = memo(({ visible, onClosePress, children, maxWidth = 800, style }) => {
const { t } = useTranslation();
return (
<AnimatePresence>
{visible && (
<motion.div
initial={{ scale: 0, opacity: 0.5 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0, opacity: 0 }}
style={style}
className={styles.sheet}
>
<IoIosCloseCircleOutline role="button" onClick={onClosePress} size={30} style={{ position: 'fixed', right: 0, top: 0, padding: 20, zIndex: 1, cursor: 'pointer' }} />
<div style={{ overflowY: 'scroll', height: '100%' }}>
<div style={{ margin: 'auto', maxWidth, height: '100%', position: 'relative' }}>
<div style={{ overflowY: 'scroll', height: '100%', ...style }}>
{children}
</div>
<IoIosCloseCircleOutline role="button" onClick={onClosePress} title={t('Close')} size={30} style={{ position: 'absolute', padding: '1em', right: 0, top: 0, cursor: 'pointer' }} />
</div>
</motion.div>
)}
</AnimatePresence>
));
);
});
export default Sheet;

View File

@ -1,11 +1,9 @@
.sheet {
padding: 1em 2em;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 10;
background: var(--whiteA11);
color: var(--gray12);
backdrop-filter: blur(30px);

View File

@ -1,27 +1,49 @@
import React, { memo } from 'react';
import React, { memo, useState, useCallback } from 'react';
import { Button } from 'evergreen-ui';
import { useTranslation } from 'react-i18next';
const ValueTuner = memo(({ style, title, value, setValue, onFinished, resolution = 1000, min = 0, max = 1, resetToDefault }) => {
import Switch from './Switch';
const ValueTuner = memo(({ style, title, value, setValue, onFinished, resolution = 1000, min: minIn = 0, max: maxIn = 1, resetToDefault }) => {
const { t } = useTranslation();
const [min, setMin] = useState(minIn);
const [max, setMax] = useState(maxIn);
function onChange(e) {
e.target.blur();
setValue(Math.min(Math.max(min, ((e.target.value / resolution) * (max - min)) + min)), max);
}
const isZoomed = !(min === minIn && max === maxIn);
const toggleZoom = useCallback(() => {
if (isZoomed) {
setMin(minIn);
setMax(maxIn);
} else {
const zoomWindow = (maxIn - minIn) / 100;
setMin(Math.max(minIn, value - zoomWindow));
setMax(Math.min(maxIn, value + zoomWindow));
}
}, [isZoomed, maxIn, minIn, value]);
return (
<div style={{ background: 'var(--gray1)', color: 'var(--gray12)', position: 'absolute', bottom: 0, zIndex: 10, padding: 10, margin: 10, borderRadius: 10, ...style }}>
<div style={{ display: 'flex', alignItems: 'center', flexBasis: 400 }}>
<div style={{ marginBottom: '.5em' }}>{title}</div>
<div style={{ marginLeft: 10, fontWeight: 'bold' }}>{value.toFixed(2)}</div>
<div style={{ flexGrow: 1, flexBasis: 10 }} />
<Button height={20} onClick={resetToDefault}>{t('Default')}</Button>
<Button height={20} intent="success" onClick={onFinished}>{t('Done')}</Button>
<div style={{ background: 'var(--gray1)', color: 'var(--gray12)', position: 'absolute', bottom: 0, padding: 10, margin: 10, borderRadius: 10, ...style }}>
<div style={{ display: 'flex', alignItems: 'center', flexBasis: 400, marginBottom: '.2em' }}>
<div>{title}</div>
<div style={{ marginLeft: '.5em', fontWeight: 'bold', marginRight: '.5em', textDecoration: 'underline' }}>{value.toFixed(4)}</div>
<Switch checked={isZoomed} onCheckedChange={toggleZoom} style={{ flexShrink: 0 }} /><span style={{ marginLeft: '.3em' }}>{t('Precise')}</span>
</div>
<div style={{ display: 'flex' }}>
<input style={{ flexGrow: 1 }} type="range" min="0" max="1000" step="1" value={((value - min) / (max - min)) * resolution} onChange={onChange} />
<div style={{ marginBottom: '.3em' }}>
<input style={{ width: '100%' }} type="range" min="0" max="1000" step="1" value={((value - min) / (max - min)) * resolution} onChange={onChange} />
</div>
<div style={{ textAlign: 'right' }}>
<Button height={20} onClick={resetToDefault}>{t('Default')}</Button>
<Button height={20} intent="success" onClick={onFinished}>{t('Done')}</Button>
</div>
</div>
);

View File

@ -14,6 +14,8 @@ const ValueTuners = memo(({ type, onFinished }) => {
title: t('Timeline trackpad/wheel sensitivity'),
value: wheelSensitivity,
setValue: setWheelSensitivity,
min: 0,
max: 4,
default: 0.2,
},
keyboardNormalSeekSpeed: {

View File

@ -9,7 +9,7 @@ import loadingLottie from '../7077-magic-flow.json';
const Working = memo(({ text, cutProgress, onAbortClick }) => (
<div style={{ position: 'absolute', zIndex: 1, bottom: 0, top: 0, left: 0, right: 0, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div style={{ position: 'absolute', bottom: 0, top: 0, left: 0, right: 0, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<motion.div
style={{ background: primaryColor, boxShadow: `${primaryColor} 0px 0px 20px 25px`, borderRadius: 60, paddingBottom: 5, color: 'white', fontSize: 14, display: 'flex', flexDirection: 'column', alignItems: 'center' }}
initial={{ opacity: 0, scale: 0 }}

View File

@ -11,12 +11,14 @@ export const keyMap = {
};
export const getModifierKeyNames = () => ({
ctrl: t('Ctrl'),
shift: t('Shift'),
alt: t('Alt'),
meta: t('⌘ Cmd / ⊞ Win'),
ctrl: [t('Ctrl')],
shift: [t('Shift')],
alt: [t('Alt')],
meta: [t('⌘ Cmd'), t('⊞ Win')],
});
export const getModifier = (key) => getModifierKeyNames()[key];
function useTimelineScroll({ wheelSensitivity, mouseWheelZoomModifierKey, invertTimelineScroll, zoomRel, seekRel }) {
const onWheel = useCallback((e) => {
const { pixelX, pixelY } = normalizeWheel(e);

View File

@ -11,7 +11,7 @@ import { ffmpegExtractWindow } from './util/constants';
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
const fsExtra = window.require('fs-extra');
const { stat, readdir, utimes, unlink } = window.require('fs/promises');
const { stat, lstat, readdir, utimes, unlink } = window.require('fs/promises');
const os = window.require('os');
const { ipcRenderer } = window.require('electron');
const remote = window.require('@electron/remote');
@ -90,7 +90,7 @@ export async function getPathReadAccessError(pathIn) {
}
export async function dirExists(dirPath) {
return (await pathExists(dirPath)) && (await fsExtra.lstat(dirPath)).isDirectory();
return (await pathExists(dirPath)) && (await lstat(dirPath)).isDirectory();
}
// const testFailFsOperation = isDev;
@ -402,6 +402,22 @@ export async function readVideoTs(videoTsPath) {
return ret;
}
export async function readDirRecursively(dirPath) {
const files = await readdir(dirPath, { recursive: true });
const ret = (await pMap(files, async (path) => {
if (['.DS_Store'].includes(basename(path))) return [];
const absPath = join(dirPath, path);
const fileStat = await lstat(absPath); // readdir also returns directories...
if (!fileStat.isFile()) return [];
return [absPath];
}, { concurrency: 5 })).flat();
if (ret.length === 0) throw new Error('No files found in folder');
return ret;
}
export function getImportProjectType(filePath) {
if (filePath.endsWith('Summary.txt')) return 'dv-analyzer-summary-txt';
const edlFormatForExtension = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml', fcpxml: 'fcpxml' };

View File

@ -9,10 +9,11 @@ import { getSegmentTags, formatSegNum } from '../segments';
export const segNumVariable = 'SEG_NUM';
export const segSuffixVariable = 'SEG_SUFFIX';
const { parse: parsePath, sep: pathSep, join: pathJoin, normalize: pathNormalize } = window.require('path');
const { parse: parsePath, sep: pathSep, join: pathJoin, normalize: pathNormalize, basename } = window.require('path');
function getOutSegError({ fileNames, filePath, outputDir, safeOutputFileName }) {
function getOutSegProblems({ fileNames, filePath, outputDir, safeOutputFileName }) {
let error;
let sameAsInputFileNameWarning = false;
// eslint-disable-next-line no-restricted-syntax
for (const fileName of fileNames) {
@ -49,6 +50,10 @@ function getOutSegError({ fileNames, filePath, outputDir, safeOutputFileName })
const shouldCheckPathLength = isWindows || isDev;
const shouldCheckFileEnd = isWindows || isDev;
if (basename(filePath) === fileName) {
sameAsInputFileNameWarning = true; // just an extra warning in case sameAsInputPath doesn't work
}
if (fileName.length === 0) {
error = i18n.t('At least one resulting file name has no length');
break;
@ -73,11 +78,14 @@ function getOutSegError({ fileNames, filePath, outputDir, safeOutputFileName })
}
}
if (error != null) return error;
if (error == null && hasDuplicates(fileNames)) {
error = i18n.t('Output file name template results in duplicate file names (you are trying to export multiple files with the same name). You can fix this for example by adding the "{{segNumVariable}}" variable.', { segNumVariable });
}
if (hasDuplicates(fileNames)) return i18n.t('Output file name template results in duplicate file names (you are trying to export multiple files with the same name). You can fix this for example by adding the "{{segNumVariable}}" variable.', { segNumVariable });
return undefined;
return {
error,
sameAsInputFileNameWarning,
};
}
// This is used as a fallback and so it has to always generate unique file names
@ -155,10 +163,10 @@ export function generateOutSegFileNames({ segments, template: desiredTemplate, f
let outSegFileNames = generate({ template: desiredTemplate, forceSafeOutputFileName: false });
const outSegError = getOutSegError({ fileNames: outSegFileNames, filePath, outputDir, safeOutputFileName });
if (outSegError != null) {
const outSegProblems = getOutSegProblems({ fileNames: outSegFileNames, filePath, outputDir, safeOutputFileName });
if (outSegProblems.error != null) {
outSegFileNames = generate({ template: defaultOutSegTemplate, forceSafeOutputFileName: true });
}
return { outSegFileNames, outSegError };
return { outSegFileNames, outSegProblems };
}

View File

@ -8,5 +8,6 @@ export default defineConfig({
base: '',
build: {
chunkSizeWarningLimit: 3e6,
sourcemap: true,
},
});

383
yarn.lock
View File

@ -569,16 +569,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-arm64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/android-arm64@npm:0.17.19"
"@esbuild/android-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/android-arm64@npm:0.17.8"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
"@esbuild/android-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/android-arm64@npm:0.17.8"
"@esbuild/android-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/android-arm64@npm:0.18.20"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
@ -590,16 +590,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-arm@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/android-arm@npm:0.17.19"
"@esbuild/android-arm@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/android-arm@npm:0.17.8"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
"@esbuild/android-arm@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/android-arm@npm:0.17.8"
"@esbuild/android-arm@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/android-arm@npm:0.18.20"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
@ -611,16 +611,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/android-x64@npm:0.17.19"
"@esbuild/android-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/android-x64@npm:0.17.8"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
"@esbuild/android-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/android-x64@npm:0.17.8"
"@esbuild/android-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/android-x64@npm:0.18.20"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
@ -632,16 +632,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/darwin-arm64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/darwin-arm64@npm:0.17.19"
"@esbuild/darwin-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/darwin-arm64@npm:0.17.8"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@esbuild/darwin-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/darwin-arm64@npm:0.17.8"
"@esbuild/darwin-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/darwin-arm64@npm:0.18.20"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
@ -653,16 +653,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/darwin-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/darwin-x64@npm:0.17.19"
"@esbuild/darwin-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/darwin-x64@npm:0.17.8"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@esbuild/darwin-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/darwin-x64@npm:0.17.8"
"@esbuild/darwin-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/darwin-x64@npm:0.18.20"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
@ -674,16 +674,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/freebsd-arm64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/freebsd-arm64@npm:0.17.19"
"@esbuild/freebsd-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/freebsd-arm64@npm:0.17.8"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
"@esbuild/freebsd-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/freebsd-arm64@npm:0.17.8"
"@esbuild/freebsd-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/freebsd-arm64@npm:0.18.20"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
@ -695,16 +695,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/freebsd-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/freebsd-x64@npm:0.17.19"
"@esbuild/freebsd-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/freebsd-x64@npm:0.17.8"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
"@esbuild/freebsd-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/freebsd-x64@npm:0.17.8"
"@esbuild/freebsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/freebsd-x64@npm:0.18.20"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
@ -716,16 +716,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-arm64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-arm64@npm:0.17.19"
"@esbuild/linux-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-arm64@npm:0.17.8"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@esbuild/linux-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-arm64@npm:0.17.8"
"@esbuild/linux-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-arm64@npm:0.18.20"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
@ -737,16 +737,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-arm@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-arm@npm:0.17.19"
"@esbuild/linux-arm@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-arm@npm:0.17.8"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@esbuild/linux-arm@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-arm@npm:0.17.8"
"@esbuild/linux-arm@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-arm@npm:0.18.20"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
@ -758,16 +758,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-ia32@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-ia32@npm:0.17.19"
"@esbuild/linux-ia32@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-ia32@npm:0.17.8"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
"@esbuild/linux-ia32@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-ia32@npm:0.17.8"
"@esbuild/linux-ia32@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-ia32@npm:0.18.20"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
@ -779,16 +779,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-loong64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-loong64@npm:0.17.19"
"@esbuild/linux-loong64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-loong64@npm:0.17.8"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
"@esbuild/linux-loong64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-loong64@npm:0.17.8"
"@esbuild/linux-loong64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-loong64@npm:0.18.20"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
@ -800,16 +800,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-mips64el@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-mips64el@npm:0.17.19"
"@esbuild/linux-mips64el@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-mips64el@npm:0.17.8"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
"@esbuild/linux-mips64el@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-mips64el@npm:0.17.8"
"@esbuild/linux-mips64el@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-mips64el@npm:0.18.20"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
@ -821,16 +821,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-ppc64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-ppc64@npm:0.17.19"
"@esbuild/linux-ppc64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-ppc64@npm:0.17.8"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
"@esbuild/linux-ppc64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-ppc64@npm:0.17.8"
"@esbuild/linux-ppc64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-ppc64@npm:0.18.20"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
@ -842,16 +842,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-riscv64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-riscv64@npm:0.17.19"
"@esbuild/linux-riscv64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-riscv64@npm:0.17.8"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
"@esbuild/linux-riscv64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-riscv64@npm:0.17.8"
"@esbuild/linux-riscv64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-riscv64@npm:0.18.20"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
@ -863,16 +863,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-s390x@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-s390x@npm:0.17.19"
"@esbuild/linux-s390x@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-s390x@npm:0.17.8"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
"@esbuild/linux-s390x@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-s390x@npm:0.17.8"
"@esbuild/linux-s390x@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-s390x@npm:0.18.20"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
@ -884,16 +884,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/linux-x64@npm:0.17.19"
"@esbuild/linux-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-x64@npm:0.17.8"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@esbuild/linux-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/linux-x64@npm:0.17.8"
"@esbuild/linux-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-x64@npm:0.18.20"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
@ -905,16 +905,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/netbsd-x64@npm:0.17.19"
"@esbuild/netbsd-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/netbsd-x64@npm:0.17.8"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/netbsd-x64@npm:0.17.8"
"@esbuild/netbsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/netbsd-x64@npm:0.18.20"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
@ -926,16 +926,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/openbsd-x64@npm:0.17.19"
"@esbuild/openbsd-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/openbsd-x64@npm:0.17.8"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/openbsd-x64@npm:0.17.8"
"@esbuild/openbsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/openbsd-x64@npm:0.18.20"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
@ -947,16 +947,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/sunos-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/sunos-x64@npm:0.17.19"
"@esbuild/sunos-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/sunos-x64@npm:0.17.8"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
"@esbuild/sunos-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/sunos-x64@npm:0.17.8"
"@esbuild/sunos-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/sunos-x64@npm:0.18.20"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
@ -968,16 +968,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-arm64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/win32-arm64@npm:0.17.19"
"@esbuild/win32-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/win32-arm64@npm:0.17.8"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@esbuild/win32-arm64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/win32-arm64@npm:0.17.8"
"@esbuild/win32-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/win32-arm64@npm:0.18.20"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
@ -989,16 +989,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-ia32@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/win32-ia32@npm:0.17.19"
"@esbuild/win32-ia32@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/win32-ia32@npm:0.17.8"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@esbuild/win32-ia32@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/win32-ia32@npm:0.17.8"
"@esbuild/win32-ia32@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/win32-ia32@npm:0.18.20"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
@ -1010,16 +1010,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-x64@npm:0.17.19":
version: 0.17.19
resolution: "@esbuild/win32-x64@npm:0.17.19"
"@esbuild/win32-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/win32-x64@npm:0.17.8"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@esbuild/win32-x64@npm:0.17.8":
version: 0.17.8
resolution: "@esbuild/win32-x64@npm:0.17.8"
"@esbuild/win32-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/win32-x64@npm:0.18.20"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@ -4232,32 +4232,32 @@ __metadata:
languageName: node
linkType: hard
"esbuild@npm:^0.17.5":
version: 0.17.19
resolution: "esbuild@npm:0.17.19"
"esbuild@npm:^0.18.10":
version: 0.18.20
resolution: "esbuild@npm:0.18.20"
dependencies:
"@esbuild/android-arm": "npm:0.17.19"
"@esbuild/android-arm64": "npm:0.17.19"
"@esbuild/android-x64": "npm:0.17.19"
"@esbuild/darwin-arm64": "npm:0.17.19"
"@esbuild/darwin-x64": "npm:0.17.19"
"@esbuild/freebsd-arm64": "npm:0.17.19"
"@esbuild/freebsd-x64": "npm:0.17.19"
"@esbuild/linux-arm": "npm:0.17.19"
"@esbuild/linux-arm64": "npm:0.17.19"
"@esbuild/linux-ia32": "npm:0.17.19"
"@esbuild/linux-loong64": "npm:0.17.19"
"@esbuild/linux-mips64el": "npm:0.17.19"
"@esbuild/linux-ppc64": "npm:0.17.19"
"@esbuild/linux-riscv64": "npm:0.17.19"
"@esbuild/linux-s390x": "npm:0.17.19"
"@esbuild/linux-x64": "npm:0.17.19"
"@esbuild/netbsd-x64": "npm:0.17.19"
"@esbuild/openbsd-x64": "npm:0.17.19"
"@esbuild/sunos-x64": "npm:0.17.19"
"@esbuild/win32-arm64": "npm:0.17.19"
"@esbuild/win32-ia32": "npm:0.17.19"
"@esbuild/win32-x64": "npm:0.17.19"
"@esbuild/android-arm": "npm:0.18.20"
"@esbuild/android-arm64": "npm:0.18.20"
"@esbuild/android-x64": "npm:0.18.20"
"@esbuild/darwin-arm64": "npm:0.18.20"
"@esbuild/darwin-x64": "npm:0.18.20"
"@esbuild/freebsd-arm64": "npm:0.18.20"
"@esbuild/freebsd-x64": "npm:0.18.20"
"@esbuild/linux-arm": "npm:0.18.20"
"@esbuild/linux-arm64": "npm:0.18.20"
"@esbuild/linux-ia32": "npm:0.18.20"
"@esbuild/linux-loong64": "npm:0.18.20"
"@esbuild/linux-mips64el": "npm:0.18.20"
"@esbuild/linux-ppc64": "npm:0.18.20"
"@esbuild/linux-riscv64": "npm:0.18.20"
"@esbuild/linux-s390x": "npm:0.18.20"
"@esbuild/linux-x64": "npm:0.18.20"
"@esbuild/netbsd-x64": "npm:0.18.20"
"@esbuild/openbsd-x64": "npm:0.18.20"
"@esbuild/sunos-x64": "npm:0.18.20"
"@esbuild/win32-arm64": "npm:0.18.20"
"@esbuild/win32-ia32": "npm:0.18.20"
"@esbuild/win32-x64": "npm:0.18.20"
dependenciesMeta:
"@esbuild/android-arm":
optional: true
@ -4305,7 +4305,7 @@ __metadata:
optional: true
bin:
esbuild: bin/esbuild
checksum: 86ada7cad6d37a3445858fee31ca39fc6c0436c7c00b2e07b9ce308235be67f36aefe0dda25da9ab08653fde496d1e759d6ad891ce9479f9e1fb4964c8f2a0fa
checksum: 1f723ec71c3aa196473bf3298316eedc3f62d523924652dfeb60701b609792f918fc60db84b420d1d8ba9bfa7d69de2fc1d3157ba47c028bdae5d507a26a3c64
languageName: node
linkType: hard
@ -5010,23 +5010,13 @@ __metadata:
languageName: node
linkType: hard
"follow-redirects@npm:^1.14.0":
version: 1.14.8
resolution: "follow-redirects@npm:1.14.8"
"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.9":
version: 1.15.4
resolution: "follow-redirects@npm:1.15.4"
peerDependenciesMeta:
debug:
optional: true
checksum: 94b8cca8123c719273788174728fea5789267ab077784eae095e41fba0b5944b9fabda6b98164d15f0166914489b4d3896e3e181dd2d67b9f1b3969eb14f7c0f
languageName: node
linkType: hard
"follow-redirects@npm:^1.14.9":
version: 1.15.2
resolution: "follow-redirects@npm:1.15.2"
peerDependenciesMeta:
debug:
optional: true
checksum: 8be0d39919770054812537d376850ccde0b4762b0501c440bd08724971a078123b55f57704f2984e0664fecc0c86adea85add63295804d9dce401cd9604c91d3
checksum: 2e8f5f259a6b02dfa8dc199e08431848a7c3beed32eb4c19945966164a52c89f07b86c3afcc32ebe4279cf0a960520e45a63013d6350309c5ec90133c5d9351a
languageName: node
linkType: hard
@ -6885,7 +6875,7 @@ __metadata:
use-debounce: "npm:^5.1.0"
use-trace-update: "npm:^1.3.0"
uuid: "npm:^8.3.2"
vite: "npm:^4.1.5"
vite: "npm:^4.5.2"
vitest: "npm:^0.28.5"
wait-on: "npm:^7.0.1"
winston: "npm:^3.8.1"
@ -7400,6 +7390,15 @@ __metadata:
languageName: node
linkType: hard
"nanoid@npm:^3.3.7":
version: 3.3.7
resolution: "nanoid@npm:3.3.7"
bin:
nanoid: bin/nanoid.cjs
checksum: ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679
languageName: node
linkType: hard
"napi-build-utils@npm:^1.0.1":
version: 1.0.2
resolution: "napi-build-utils@npm:1.0.2"
@ -8059,7 +8058,7 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:^8.4.21, postcss@npm:^8.4.23":
"postcss@npm:^8.4.21":
version: 8.4.31
resolution: "postcss@npm:8.4.31"
dependencies:
@ -8070,6 +8069,17 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:^8.4.27":
version: 8.4.33
resolution: "postcss@npm:8.4.33"
dependencies:
nanoid: "npm:^3.3.7"
picocolors: "npm:^1.0.0"
source-map-js: "npm:^1.0.2"
checksum: e22a4594c255f26117f38419fb494d7ecab0f596cd409f7aadc8a6173abf180ed7ea970cd13fd366ab12b5840be901d2a09b25197700c2ebcb5a8077326bf519
languageName: node
linkType: hard
"prebuild-install@npm:^7.0.0":
version: 7.1.0
resolution: "prebuild-install@npm:7.1.0"
@ -8841,9 +8851,9 @@ __metadata:
languageName: node
linkType: hard
"rollup@npm:^3.21.0":
version: 3.23.1
resolution: "rollup@npm:3.23.1"
"rollup@npm:^3.27.1":
version: 3.29.4
resolution: "rollup@npm:3.29.4"
dependencies:
fsevents: "npm:~2.3.2"
dependenciesMeta:
@ -8851,7 +8861,7 @@ __metadata:
optional: true
bin:
rollup: dist/bin/rollup
checksum: 242fb599cfdf0ddb76279ddfd543243b86ce6b71652031ac52e4b6c3141e437384b12cb2c82dc69185133da2defab10c5980d3d147e4e154977455404b11d17a
checksum: 9e39d54e23731a4c4067e9c02910cdf7479a0f9a7584796e2dc6efaa34bb1e5e015c062c87d1e64d96038baca76cefd47681ff22604fae5827147f54123dc6d0
languageName: node
linkType: hard
@ -10536,17 +10546,18 @@ __metadata:
languageName: node
linkType: hard
"vite@npm:^4.1.5":
version: 4.3.9
resolution: "vite@npm:4.3.9"
"vite@npm:^4.5.2":
version: 4.5.2
resolution: "vite@npm:4.5.2"
dependencies:
esbuild: "npm:^0.17.5"
esbuild: "npm:^0.18.10"
fsevents: "npm:~2.3.2"
postcss: "npm:^8.4.23"
rollup: "npm:^3.21.0"
postcss: "npm:^8.4.27"
rollup: "npm:^3.27.1"
peerDependencies:
"@types/node": ">= 14"
less: "*"
lightningcss: ^1.21.0
sass: "*"
stylus: "*"
sugarss: "*"
@ -10559,6 +10570,8 @@ __metadata:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
@ -10569,7 +10582,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
checksum: c2f0b392a253318a1d3ffc6873885d4a03c6bda6f717682bd0c82b7a431a67fb1ba08de6e1bf7f3f31bde1615015c5ef4be264f20c6e34c590d8a7c9516e94f4
checksum: 3feb39f8da038fb2b1ad074c19a9579c263c1d7a872c5c6e0269b82d67805bb8c93cf9fc393e852807483ae9a918b1ac2861c72f73ee92fb3935ea68333a2cf7
languageName: node
linkType: hard