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 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 ## Building mas-dev (Mac App Store) build locally
This will sign using the development provisioning profile: 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 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. 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 ### Release new version
- Commit changes - If Mac App Store / Windows Store
- Checkout branch `stores`
- Merge `master` into `stores`
- `npm version ...` - `npm version ...`
- `git push --follow-tags` - `git push --follow-tags`
- Wait for build and draft in Github actions - Wait for build and draft in Github actions
- Open draft in github and add Release notes - Open draft in github and add Release notes
- For files `LosslessCut-mac-universal.pkg` and `LosslessCut-win-x64.appx` add prefix `-DO-NOT-DOWNLOAD` - 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) - Bump [snap version](https://snapcraft.io/losslesscut/listing)
- `npm run scan-i18n` to get the newest English strings and push so weblate gets them - `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="bugtracker">https://github.com/mifi/lossless-cut/issues</url>
<url type="donation">https://paypal.me/mifino/usd</url> <url type="donation">https://paypal.me/mifino/usd</url>
<releases> <releases>
<release version="3.60.0" date="2024-01-05"/>
<release version="3.59.1" date="2023-12-22"/> <release version="3.59.1" date="2023-12-22"/>
<release version="3.59.0" date="2023-12-22"/> <release version="3.59.0" date="2023-12-22"/>
<release version="3.58.0" date="2023-10-16"/> <release version="3.58.0" date="2023-10-16"/>

View File

@ -3,7 +3,7 @@
"productName": "LosslessCut", "productName": "LosslessCut",
"description": "The swiss army knife of lossless video/audio editing", "description": "The swiss army knife of lossless video/audio editing",
"copyright": "Copyright © 2021 ${author}", "copyright": "Copyright © 2021 ${author}",
"version": "3.59.1", "version": "3.60.0",
"main": "public/electron.js", "main": "public/electron.js",
"homepage": "./", "homepage": "./",
"scripts": { "scripts": {
@ -13,7 +13,7 @@
"icon-gen": "mkdirp icon-build build-resources/appx && node script/icon-gen.mjs", "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-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-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", "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", "build": "yarn icon-gen && vite build --outDir vite-dist",
"tsc": "tsc --build", "tsc": "tsc --build",
@ -99,7 +99,7 @@
"use-debounce": "^5.1.0", "use-debounce": "^5.1.0",
"use-trace-update": "^1.3.0", "use-trace-update": "^1.3.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"vite": "^4.1.5", "vite": "^4.5.2",
"vitest": "^0.28.5", "vitest": "^0.28.5",
"wait-on": "^7.0.1" "wait-on": "^7.0.1"
}, },

View File

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

View File

@ -68,7 +68,7 @@ import {
getOutPath, getSuffixedOutPath, handleError, getOutDir, getOutPath, getSuffixedOutPath, handleError, getOutDir,
isStoreBuild, dragPreventer, isStoreBuild, dragPreventer,
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile, 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, calcShouldShowWaveform, calcShouldShowKeyframes, mediaSourceQualities,
} from './util'; } from './util';
import { toast, errorToast } from './swal'; import { toast, errorToast } from './swal';
@ -88,6 +88,7 @@ import isDev from './isDev';
const electron = window.require('electron'); const electron = window.require('electron');
const { exists } = window.require('fs-extra'); const { exists } = window.require('fs-extra');
const { lstat } = window.require('fs/promises');
const filePathToUrl = window.require('file-url'); const filePathToUrl = window.require('file-url');
const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path'); const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path');
@ -1178,8 +1179,8 @@ function App() {
console.log('outSegTemplateOrDefault', outSegTemplateOrDefault); console.log('outSegTemplateOrDefault', outSegTemplateOrDefault);
const { outSegFileNames, outSegError } = generateOutSegFileNames({ segments: segmentsToExport, template: outSegTemplateOrDefault }); const { outSegFileNames, outSegProblems } = generateOutSegFileNames({ segments: segmentsToExport, template: outSegTemplateOrDefault });
if (outSegError != null) { if (outSegProblems.error != null) {
console.warn('Output segments file name invalid, using default instead', outSegFileNames); console.warn('Output segments file name invalid, using default instead', outSegFileNames);
} }
@ -1296,6 +1297,7 @@ function App() {
await onExportConfirm(); await onExportConfirm();
} else { } else {
setExportConfirmVisible(true); setExportConfirmVisible(true);
setStreamsSelectorShown(false);
} }
}, [filePath, exportConfirmEnabled, exportConfirmVisible, onExportConfirm]); }, [filePath, exportConfirmEnabled, exportConfirmVisible, onExportConfirm]);
@ -1649,7 +1651,6 @@ function App() {
const handleShowStreamsSelectorClick = useCallback(() => { const handleShowStreamsSelectorClick = useCallback(() => {
setStreamsSelectorShown(true); setStreamsSelectorShown(true);
setExportConfirmVisible(false);
}, []); }, []);
const extractAllStreams = useCallback(async () => { const extractAllStreams = useCallback(async () => {
@ -1800,12 +1801,28 @@ function App() {
[lastOpenedPathRef.current] = filePaths; [lastOpenedPathRef.current] = filePaths;
// https://en.wikibooks.org/wiki/Inside_DVD-Video/Directory_Structure // first check if it is a single directory, and if so, read it recursively
if (filePaths.length === 1 && /^VIDEO_TS$/i.test(basename(filePaths[0]))) { if (filePaths.length === 1) {
if (mustDisallowVob()) return; const firstFilePath = filePaths[0];
filePaths = await readVideoTs(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 (filePaths.length > 1) {
if (alwaysConcatMultipleFiles) { if (alwaysConcatMultipleFiles) {
@ -1820,6 +1837,12 @@ function App() {
// filePaths.length is now 1 // filePaths.length is now 1
const firstFilePath = filePaths[0]; 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; if (workingRef.current) return;
try { try {
setWorking({ text: i18n.t('Loading file') }); setWorking({ text: i18n.t('Loading file') });
@ -1900,7 +1923,7 @@ function App() {
}, [alwaysConcatMultipleFiles, batchLoadPaths, setWorking, isFileOpened, batchFiles.length, userOpenSingleFile, checkFileOpened, loadEdlFile, enableAskForFileOpenAction, addStreamSourceFile, filePath]); }, [alwaysConcatMultipleFiles, batchLoadPaths, setWorking, isFileOpened, batchFiles.length, userOpenSingleFile, checkFileOpened, loadEdlFile, enableAskForFileOpenAction, addStreamSourceFile, filePath]);
const openFilesDialog = useCallback(async () => { 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; if (canceled) return;
userOpenFiles(filePaths); userOpenFiles(filePaths);
}, [userOpenFiles]); }, [userOpenFiles]);
@ -2578,7 +2601,9 @@ function App() {
/> />
</div> </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 && ( {mainStreams && (
<StreamsSelector <StreamsSelector
mainFilePath={filePath} mainFilePath={filePath}
@ -2606,15 +2631,13 @@ function App() {
)} )}
</Sheet> </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 <LastCommandsSheet
visible={lastCommandsVisible} visible={lastCommandsVisible}
onTogglePress={toggleLastCommands} onTogglePress={toggleLastCommands}
ffmpegCommandLog={ffmpegCommandLog} ffmpegCommandLog={ffmpegCommandLog}
/> />
<Sheet visible={settingsVisible} onClosePress={toggleSettings} style={{ padding: '1em 0' }}> <Sheet visible={settingsVisible} onClosePress={toggleSettings}>
<Settings <Settings
onTunerRequested={onTunerRequested} onTunerRequested={onTunerRequested}
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts} onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}

View File

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

View File

@ -42,7 +42,7 @@ const Waveforms = memo(({ calculateTimelinePercent, durationSafe, waveforms, zoo
const CommandedTime = memo(({ commandedTimePercent }) => { const CommandedTime = memo(({ commandedTimePercent }) => {
const color = 'var(--gray12)'; const color = 'var(--gray12)';
const commonStyle = { left: commandedTimePercent, position: 'absolute', zIndex: 4, pointerEvents: 'none' }; const commonStyle = { left: commandedTimePercent, position: 'absolute', pointerEvents: 'none' };
return ( return (
<> <>
<FaCaretDown style={{ ...commonStyle, top: 0, color, fontSize: 14, marginLeft: -7, marginTop: -6 }} /> <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 nextThumbTime = nextThumbnail ? nextThumbnail.time : durationSafe;
const maxWidthPercent = ((nextThumbTime - thumbnail.time) / durationSafe) * 100 * 0.9; const maxWidthPercent = ((nextThumbTime - thumbnail.time) / durationSafe) * 100 * 0.9;
return ( 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> </div>
@ -285,13 +285,6 @@ const Timeline = memo(({
style={{ height: timelineHeight, width: `${zoom * 100}%`, position: 'relative', backgroundColor: timelineBackground, transition: darkModeTransition }} style={{ height: timelineHeight, width: `${zoom * 100}%`, position: 'relative', backgroundColor: timelineBackground, transition: darkModeTransition }}
ref={timelineWrapperRef} 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) => { {apparentCutSegments.map((seg, i) => {
if (seg.start === 0 && seg.end === 0) return null; // No video loaded if (seg.start === 0 && seg.end === 0) return null; // No video loaded
@ -325,10 +318,17 @@ const Timeline = memo(({
{shouldShowKeyframes && !areKeyframesTooClose && neighbouringKeyFrames.map((f) => ( {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' }} /> <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> </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)' }}> <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}` : ''} {formatTimeAndFrames(displayTime)}{isZoomed ? ` ${displayTimePercent}` : ''}
</div> </div>

View File

@ -356,29 +356,44 @@ const ExportConfirm = memo(({
</> </>
)} )}
{!needSmartCut && ( {!needSmartCut && (() => {
<tr> const avoidNegativeTsWarn = (() => {
<td> if (willMerge) {
&quot;avoid_negative_ts&quot; if (avoidNegativeTs !== 'make_non_negative') {
{!['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>} return t('When merging, it\'s generally recommended to set this to "make_non_negative"');
</td> }
<td> return undefined;
<Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}> }
<option value="auto">auto</option> if (!['make_zero', 'auto'].includes(avoidNegativeTs)) {
<option value="make_zero">make_zero</option> return t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' });
<option value="make_non_negative">make_non_negative</option> }
<option value="disabled">disabled</option> return undefined;
</Select> })();
</td>
<td> return (
{!['make_zero', 'auto'].includes(avoidNegativeTs) ? ( <tr>
<WarningSignIcon verticalAlign="middle" color="warning" /> <td>
) : ( {`"${'avoid_negative_ts'}"`}
<HelpIcon onClick={onAvoidNegativeTsHelpPress} /> {avoidNegativeTsWarn != null && <div style={warningStyle}>{avoidNegativeTsWarn}</div>}
)} </td>
</td> <td>
</tr> <Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}>
)} <option value="auto">auto</option>
<option value="make_zero">make_zero</option>
<option value="make_non_negative">make_non_negative</option>
<option value="disabled">disabled</option>
</Select>
</td>
<td>
{avoidNegativeTsWarn != null ? (
<WarningSignIcon verticalAlign="middle" color="warning" />
) : (
<HelpIcon onClick={onAvoidNegativeTsHelpPress} />
)}
</td>
</tr>
);
})()}
<tr> <tr>
<td> <td>
@ -397,7 +412,7 @@ const ExportConfirm = memo(({
</div> </div>
</motion.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 <motion.div
initial={{ opacity: 0, translateX: 50 }} initial={{ opacity: 0, translateX: 50 }}
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}

View File

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

View File

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

View File

@ -23,13 +23,13 @@ const formatVariable = (variable) => `\${${variable}}`;
const extVar = formatVariable('EXT'); 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 { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings();
const [text, setText] = useState(outSegTemplate); const [text, setText] = useState(outSegTemplate);
const [debouncedText] = useDebounce(text, 500); const [debouncedText] = useDebounce(text, 500);
const [validText, setValidText] = useState(); const [validText, setValidText] = useState();
const [error, setError] = useState(); const [outSegProblems, setOutSegProblems] = useState({ error: undefined, sameAsInputFileNameWarning: false });
const [outSegFileNames, setOutSegFileNames] = useState(); const [outSegFileNames, setOutSegFileNames] = useState();
const [shown, setShown] = useState(); const [shown, setShown] = useState();
const inputRef = useRef(); const inputRef = useRef();
@ -42,22 +42,16 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
if (debouncedText == null) return; if (debouncedText == null) return;
try { try {
const { outSegFileNames: newOutSegFileNames, outSegError } = generateOutSegFileNames({ template: debouncedText }); const outSegs = generateOutSegFileNames({ template: debouncedText });
setOutSegFileNames(newOutSegFileNames); setOutSegFileNames(outSegs.outSegFileNames);
if (outSegError) { setOutSegProblems(outSegs.outSegProblems);
setError(outSegError); setValidText(outSegs.outSegProblems.error == null ? debouncedText : undefined);
setValidText();
return;
}
setValidText(debouncedText);
setError();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
setValidText(); setValidText();
setError(err.message); setOutSegProblems({ error: err.message });
} }
}, [debouncedText, generateOutSegFileNames, getOutSegError, t]); }, [debouncedText, generateOutSegFileNames, t]);
// eslint-disable-next-line no-template-curly-in-string // eslint-disable-next-line no-template-curly-in-string
const isMissingExtension = validText != null && !validText.endsWith(extVar); const isMissingExtension = validText != null && !validText.endsWith(extVar);
@ -80,8 +74,8 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
}, [setOutSegTemplate]); }, [setOutSegTemplate]);
const onHideClick = useCallback(() => { const onHideClick = useCallback(() => {
if (error == null) setShown(false); if (outSegProblems.error == null) setShown(false);
}, [error]); }, [outSegProblems.error]);
const onShowClick = useCallback(() => { const onShowClick = useCallback(() => {
if (!shown) setShown(true); if (!shown) setShown(true);
@ -89,7 +83,7 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
const onTextChange = useCallback((e) => setText(e.target.value), []); 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 onVariableClick = useCallback((variable) => {
const input = inputRef.current; const input = inputRef.current;
@ -132,7 +126,18 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
))} ))}
</div> </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 && ( {isMissingExtension && (
<div style={{ marginBottom: '1em' }}> <div style={{ marginBottom: '1em' }}>

View File

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

View File

@ -1,27 +1,33 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { IoIosCloseCircleOutline } from 'react-icons/io'; import { IoIosCloseCircleOutline } from 'react-icons/io';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import styles from './Sheet.module.css'; import styles from './Sheet.module.css';
const Sheet = memo(({ visible, onClosePress, style, children }) => ( const Sheet = memo(({ visible, onClosePress, children, maxWidth = 800, style }) => {
<AnimatePresence> const { t } = useTranslation();
{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%' }}> return (
{children} <AnimatePresence>
</div> {visible && (
</motion.div> <motion.div
)} initial={{ scale: 0, opacity: 0.5 }}
</AnimatePresence> animate={{ scale: 1, opacity: 1 }}
)); exit={{ scale: 0, opacity: 0 }}
className={styles.sheet}
>
<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; export default Sheet;

View File

@ -1,11 +1,9 @@
.sheet { .sheet {
padding: 1em 2em;
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
z-index: 10;
background: var(--whiteA11); background: var(--whiteA11);
color: var(--gray12); color: var(--gray12);
backdrop-filter: blur(30px); 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 { Button } from 'evergreen-ui';
import { useTranslation } from 'react-i18next'; 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 { t } = useTranslation();
const [min, setMin] = useState(minIn);
const [max, setMax] = useState(maxIn);
function onChange(e) { function onChange(e) {
e.target.blur(); e.target.blur();
setValue(Math.min(Math.max(min, ((e.target.value / resolution) * (max - min)) + min)), max); 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 ( return (
<div style={{ background: 'var(--gray1)', color: 'var(--gray12)', position: 'absolute', bottom: 0, zIndex: 10, padding: 10, margin: 10, borderRadius: 10, ...style }}> <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 }}> <div style={{ display: 'flex', alignItems: 'center', flexBasis: 400, marginBottom: '.2em' }}>
<div style={{ marginBottom: '.5em' }}>{title}</div> <div>{title}</div>
<div style={{ marginLeft: 10, fontWeight: 'bold' }}>{value.toFixed(2)}</div> <div style={{ marginLeft: '.5em', fontWeight: 'bold', marginRight: '.5em', textDecoration: 'underline' }}>{value.toFixed(4)}</div>
<div style={{ flexGrow: 1, flexBasis: 10 }} /> <Switch checked={isZoomed} onCheckedChange={toggleZoom} style={{ flexShrink: 0 }} /><span style={{ marginLeft: '.3em' }}>{t('Precise')}</span>
<Button height={20} onClick={resetToDefault}>{t('Default')}</Button>
<Button height={20} intent="success" onClick={onFinished}>{t('Done')}</Button>
</div> </div>
<div style={{ display: 'flex' }}> <div style={{ marginBottom: '.3em' }}>
<input style={{ flexGrow: 1 }} type="range" min="0" max="1000" step="1" value={((value - min) / (max - min)) * resolution} onChange={onChange} /> <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>
</div> </div>
); );

View File

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

View File

@ -9,7 +9,7 @@ import loadingLottie from '../7077-magic-flow.json';
const Working = memo(({ text, cutProgress, onAbortClick }) => ( 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 <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' }} 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 }} initial={{ opacity: 0, scale: 0 }}

View File

@ -11,12 +11,14 @@ export const keyMap = {
}; };
export const getModifierKeyNames = () => ({ export const getModifierKeyNames = () => ({
ctrl: t('Ctrl'), ctrl: [t('Ctrl')],
shift: t('Shift'), shift: [t('Shift')],
alt: t('Alt'), alt: [t('Alt')],
meta: t('⌘ Cmd / ⊞ Win'), meta: [t('⌘ Cmd'), t('⊞ Win')],
}); });
export const getModifier = (key) => getModifierKeyNames()[key];
function useTimelineScroll({ wheelSensitivity, mouseWheelZoomModifierKey, invertTimelineScroll, zoomRel, seekRel }) { function useTimelineScroll({ wheelSensitivity, mouseWheelZoomModifierKey, invertTimelineScroll, zoomRel, seekRel }) {
const onWheel = useCallback((e) => { const onWheel = useCallback((e) => {
const { pixelX, pixelY } = normalizeWheel(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 { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
const fsExtra = window.require('fs-extra'); 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 os = window.require('os');
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require('electron');
const remote = window.require('@electron/remote'); const remote = window.require('@electron/remote');
@ -90,7 +90,7 @@ export async function getPathReadAccessError(pathIn) {
} }
export async function dirExists(dirPath) { 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; // const testFailFsOperation = isDev;
@ -402,6 +402,22 @@ export async function readVideoTs(videoTsPath) {
return ret; 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) { export function getImportProjectType(filePath) {
if (filePath.endsWith('Summary.txt')) return 'dv-analyzer-summary-txt'; if (filePath.endsWith('Summary.txt')) return 'dv-analyzer-summary-txt';
const edlFormatForExtension = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml', fcpxml: 'fcpxml' }; 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 segNumVariable = 'SEG_NUM';
export const segSuffixVariable = 'SEG_SUFFIX'; 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 error;
let sameAsInputFileNameWarning = false;
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
for (const fileName of fileNames) { for (const fileName of fileNames) {
@ -49,6 +50,10 @@ function getOutSegError({ fileNames, filePath, outputDir, safeOutputFileName })
const shouldCheckPathLength = isWindows || isDev; const shouldCheckPathLength = isWindows || isDev;
const shouldCheckFileEnd = 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) { if (fileName.length === 0) {
error = i18n.t('At least one resulting file name has no length'); error = i18n.t('At least one resulting file name has no length');
break; 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 {
error,
return undefined; sameAsInputFileNameWarning,
};
} }
// This is used as a fallback and so it has to always generate unique file names // 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 }); let outSegFileNames = generate({ template: desiredTemplate, forceSafeOutputFileName: false });
const outSegError = getOutSegError({ fileNames: outSegFileNames, filePath, outputDir, safeOutputFileName }); const outSegProblems = getOutSegProblems({ fileNames: outSegFileNames, filePath, outputDir, safeOutputFileName });
if (outSegError != null) { if (outSegProblems.error != null) {
outSegFileNames = generate({ template: defaultOutSegTemplate, forceSafeOutputFileName: true }); outSegFileNames = generate({ template: defaultOutSegTemplate, forceSafeOutputFileName: true });
} }
return { outSegFileNames, outSegError }; return { outSegFileNames, outSegProblems };
} }

View File

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

383
yarn.lock
View File

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