mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
improve dark mode #1969
This commit is contained in:
parent
0f3e2eb100
commit
122d79c300
@ -41,6 +41,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource/open-sans": "^4.5.14",
|
"@fontsource/open-sans": "^4.5.14",
|
||||||
"@radix-ui/colors": "^0.1.8",
|
"@radix-ui/colors": "^0.1.8",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-switch": "^1.0.1",
|
"@radix-ui/react-switch": "^1.0.1",
|
||||||
"@tsconfig/node18": "^18.2.2",
|
"@tsconfig/node18": "^18.2.2",
|
||||||
"@tsconfig/strictest": "^2.0.2",
|
"@tsconfig/strictest": "^2.0.2",
|
||||||
@ -101,6 +102,7 @@
|
|||||||
"react-syntax-highlighter": "^15.4.3",
|
"react-syntax-highlighter": "^15.4.3",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
|
"sass": "^1.77.2",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"scroll-into-view-if-needed": "^2.2.28",
|
"scroll-into-view-if-needed": "^2.2.28",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
|
@ -1338,6 +1338,7 @@ function App() {
|
|||||||
if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.'));
|
if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.'));
|
||||||
|
|
||||||
const revealPath = willMerge ? mergedOutFilePath : outFiles[0];
|
const revealPath = willMerge ? mergedOutFilePath : outFiles[0];
|
||||||
|
invariant(revealPath != null);
|
||||||
if (!hideAllNotifications) openExportFinishedToast({ filePath: revealPath, warnings, notices });
|
if (!hideAllNotifications) openExportFinishedToast({ filePath: revealPath, warnings, notices });
|
||||||
|
|
||||||
if (cleanupChoices.cleanupAfterExport) await cleanupFilesWithDialog();
|
if (cleanupChoices.cleanupAfterExport) await cleanupFilesWithDialog();
|
||||||
@ -2260,6 +2261,8 @@ function App() {
|
|||||||
setLastCommandsVisible(false);
|
setLastCommandsVisible(false);
|
||||||
setSettingsVisible(false);
|
setSettingsVisible(false);
|
||||||
setStreamsSelectorShown(false);
|
setStreamsSelectorShown(false);
|
||||||
|
setConcatDialogVisible(false);
|
||||||
|
setKeyboardShortcutsVisible(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2506,300 +2509,304 @@ function App() {
|
|||||||
// throw new Error('Test error boundary');
|
// throw new Error('Test error boundary');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SegColorsContext.Provider value={segColorsContext}>
|
<>
|
||||||
<UserSettingsContext.Provider value={userSettingsContext}>
|
<SegColorsContext.Provider value={segColorsContext}>
|
||||||
<ThemeProvider value={theme}>
|
<UserSettingsContext.Provider value={userSettingsContext}>
|
||||||
<div className={darkMode ? 'dark-theme' : undefined} style={{ display: 'flex', flexDirection: 'column', height: '100vh', color: 'var(--gray12)', background: 'var(--gray1)', transition: darkModeTransition }}>
|
<ThemeProvider value={theme}>
|
||||||
<TopMenu
|
<div className={darkMode ? 'dark-theme' : undefined} style={{ display: 'flex', flexDirection: 'column', height: '100vh', color: 'var(--gray12)', background: 'var(--gray1)', transition: darkModeTransition }}>
|
||||||
filePath={filePath}
|
<TopMenu
|
||||||
fileFormat={fileFormat}
|
filePath={filePath}
|
||||||
copyAnyAudioTrack={copyAnyAudioTrack}
|
fileFormat={fileFormat}
|
||||||
toggleStripAudio={toggleStripAudio}
|
copyAnyAudioTrack={copyAnyAudioTrack}
|
||||||
clearOutDir={clearOutDir}
|
toggleStripAudio={toggleStripAudio}
|
||||||
isCustomFormatSelected={isCustomFormatSelected}
|
clearOutDir={clearOutDir}
|
||||||
renderOutFmt={renderOutFmt}
|
isCustomFormatSelected={isCustomFormatSelected}
|
||||||
toggleSettings={toggleSettings}
|
renderOutFmt={renderOutFmt}
|
||||||
numStreamsToCopy={numStreamsToCopy}
|
toggleSettings={toggleSettings}
|
||||||
numStreamsTotal={numStreamsTotal}
|
numStreamsToCopy={numStreamsToCopy}
|
||||||
setStreamsSelectorShown={setStreamsSelectorShown}
|
numStreamsTotal={numStreamsTotal}
|
||||||
selectedSegments={selectedSegmentsOrInverse}
|
setStreamsSelectorShown={setStreamsSelectorShown}
|
||||||
/>
|
selectedSegments={selectedSegmentsOrInverse}
|
||||||
|
/>
|
||||||
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
|
|
||||||
<AnimatePresence>
|
|
||||||
{showLeftBar && (
|
|
||||||
<BatchFilesList
|
|
||||||
selectedBatchFiles={selectedBatchFiles}
|
|
||||||
filePath={filePath}
|
|
||||||
width={leftBarWidth}
|
|
||||||
batchFiles={batchFiles}
|
|
||||||
setBatchFiles={setBatchFiles}
|
|
||||||
onBatchFileSelect={onBatchFileSelect}
|
|
||||||
batchListRemoveFile={batchListRemoveFile}
|
|
||||||
closeBatch={closeBatch}
|
|
||||||
onMergeFilesClick={concatBatch}
|
|
||||||
onBatchConvertToSupportedFormatClick={convertFormatBatch}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* Middle part (also shown in fullscreen): */}
|
|
||||||
<div style={{ position: 'relative', flexGrow: 1, overflow: 'hidden' }} ref={videoContainerRef}>
|
|
||||||
{!isFileOpened && <NoFileLoaded mifiLink={mifiLink} currentCutSeg={currentCutSeg} onClick={openFilesDialog} darkMode={darkMode} />}
|
|
||||||
|
|
||||||
<div className="no-user-select" style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, visibility: !isFileOpened || !hasVideo || bigWaveformEnabled ? 'hidden' : undefined }} onWheel={onTimelineWheel}>
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
|
||||||
<video
|
|
||||||
className="main-player"
|
|
||||||
tabIndex={-1}
|
|
||||||
muted={playbackVolume === 0 || compatPlayerEnabled}
|
|
||||||
ref={videoRef}
|
|
||||||
style={videoStyle}
|
|
||||||
src={fileUri}
|
|
||||||
onPlay={onSartPlaying}
|
|
||||||
onPause={onStopPlaying}
|
|
||||||
onAbort={onVideoAbort}
|
|
||||||
onDurationChange={onDurationChange}
|
|
||||||
onTimeUpdate={onTimeUpdate}
|
|
||||||
onError={onVideoError}
|
|
||||||
onClick={onVideoClick}
|
|
||||||
onDoubleClick={toggleFullscreenVideo}
|
|
||||||
onFocusCapture={onVideoFocus}
|
|
||||||
>
|
|
||||||
{renderSubtitles()}
|
|
||||||
</video>
|
|
||||||
|
|
||||||
{filePath != null && compatPlayerEnabled && <MediaSourcePlayer rotate={effectiveRotation} filePath={filePath} videoStream={activeVideoStream} audioStream={activeAudioStream} playerTime={playerTime ?? 0} commandedTime={commandedTime} playing={playing} eventId={compatPlayerEventId} masterVideoRef={videoRef} mediaSourceQuality={mediaSourceQuality} playbackVolume={playbackVolume} />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{bigWaveformEnabled && <BigWaveform waveforms={waveforms} relevantTime={relevantTime} playing={playing} durationSafe={durationSafe} zoom={zoomUnrounded} seekRel={seekRel} />}
|
|
||||||
|
|
||||||
{compatPlayerEnabled && (
|
|
||||||
<div style={{ position: 'absolute', top: 0, right: 0, left: 0, marginTop: '1em', marginLeft: '1em', color: 'white', opacity: 0.7, display: 'flex', alignItems: 'center', pointerEvents: 'none' }}>
|
|
||||||
{isRotationSet ? (
|
|
||||||
<>
|
|
||||||
<MdRotate90DegreesCcw size={26} style={{ marginRight: 5 }} />
|
|
||||||
{t('Rotation preview')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{t('FFmpeg-assisted playback')}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!compatPlayerRequired && <FaWindowClose role="button" style={{ cursor: 'pointer', pointerEvents: 'initial', verticalAlign: 'middle', padding: 10 }} onClick={() => setHideMediaSourcePlayer(true)} />}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isFileOpened && (
|
|
||||||
<div className="no-user-select" style={{ position: 'absolute', right: 0, bottom: 0, marginBottom: 10, display: 'flex', alignItems: 'center' }}>
|
|
||||||
<VolumeControl playbackVolume={playbackVolume} setPlaybackVolume={setPlaybackVolume} />
|
|
||||||
|
|
||||||
{shouldShowPlaybackStreamSelector && <PlaybackStreamSelector subtitleStreams={subtitleStreams} videoStreams={videoStreams} audioStreams={audioStreams} activeSubtitleStreamIndex={activeSubtitleStreamIndex} activeVideoStreamIndex={activeVideoStreamIndex} activeAudioStreamIndex={activeAudioStreamIndex} onActiveSubtitleChange={onActiveSubtitleChange} onActiveVideoStreamChange={onActiveVideoStreamChange} onActiveAudioStreamChange={onActiveAudioStreamChange} />}
|
|
||||||
|
|
||||||
{compatPlayerEnabled && <div style={{ color: 'white', opacity: 0.7, padding: '.5em' }} role="button" onClick={() => incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}</div>}
|
|
||||||
|
|
||||||
{!showRightBar && (
|
|
||||||
<FaAngleLeft
|
|
||||||
title={t('Show sidebar')}
|
|
||||||
size={30}
|
|
||||||
role="button"
|
|
||||||
style={{ marginRight: 10, color: 'var(--gray12)', opacity: 0.7 }}
|
|
||||||
onClick={toggleSegmentsList}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{working && <Working text={working.text} cutProgress={cutProgress} onAbortClick={handleAbortWorkingClick} />}
|
{showLeftBar && (
|
||||||
|
<BatchFilesList
|
||||||
|
selectedBatchFiles={selectedBatchFiles}
|
||||||
|
filePath={filePath}
|
||||||
|
width={leftBarWidth}
|
||||||
|
batchFiles={batchFiles}
|
||||||
|
setBatchFiles={setBatchFiles}
|
||||||
|
onBatchFileSelect={onBatchFileSelect}
|
||||||
|
batchListRemoveFile={batchListRemoveFile}
|
||||||
|
closeBatch={closeBatch}
|
||||||
|
onMergeFilesClick={concatBatch}
|
||||||
|
onBatchConvertToSupportedFormatClick={convertFormatBatch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible(undefined)} />}
|
{/* Middle part (also shown in fullscreen): */}
|
||||||
|
<div style={{ position: 'relative', flexGrow: 1, overflow: 'hidden' }} ref={videoContainerRef}>
|
||||||
|
{!isFileOpened && <NoFileLoaded mifiLink={mifiLink} currentCutSeg={currentCutSeg} onClick={openFilesDialog} darkMode={darkMode} />}
|
||||||
|
|
||||||
|
<div className="no-user-select" style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, visibility: !isFileOpened || !hasVideo || bigWaveformEnabled ? 'hidden' : undefined }} onWheel={onTimelineWheel}>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||||
|
<video
|
||||||
|
className="main-player"
|
||||||
|
tabIndex={-1}
|
||||||
|
muted={playbackVolume === 0 || compatPlayerEnabled}
|
||||||
|
ref={videoRef}
|
||||||
|
style={videoStyle}
|
||||||
|
src={fileUri}
|
||||||
|
onPlay={onSartPlaying}
|
||||||
|
onPause={onStopPlaying}
|
||||||
|
onAbort={onVideoAbort}
|
||||||
|
onDurationChange={onDurationChange}
|
||||||
|
onTimeUpdate={onTimeUpdate}
|
||||||
|
onError={onVideoError}
|
||||||
|
onClick={onVideoClick}
|
||||||
|
onDoubleClick={toggleFullscreenVideo}
|
||||||
|
onFocusCapture={onVideoFocus}
|
||||||
|
>
|
||||||
|
{renderSubtitles()}
|
||||||
|
</video>
|
||||||
|
|
||||||
|
{filePath != null && compatPlayerEnabled && <MediaSourcePlayer rotate={effectiveRotation} filePath={filePath} videoStream={activeVideoStream} audioStream={activeAudioStream} playerTime={playerTime ?? 0} commandedTime={commandedTime} playing={playing} eventId={compatPlayerEventId} masterVideoRef={videoRef} mediaSourceQuality={mediaSourceQuality} playbackVolume={playbackVolume} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{bigWaveformEnabled && <BigWaveform waveforms={waveforms} relevantTime={relevantTime} playing={playing} durationSafe={durationSafe} zoom={zoomUnrounded} seekRel={seekRel} />}
|
||||||
|
|
||||||
|
{compatPlayerEnabled && (
|
||||||
|
<div style={{ position: 'absolute', top: 0, right: 0, left: 0, marginTop: '1em', marginLeft: '1em', color: 'white', opacity: 0.7, display: 'flex', alignItems: 'center', pointerEvents: 'none' }}>
|
||||||
|
{isRotationSet ? (
|
||||||
|
<>
|
||||||
|
<MdRotate90DegreesCcw size={26} style={{ marginRight: 5 }} />
|
||||||
|
{t('Rotation preview')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{t('FFmpeg-assisted playback')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!compatPlayerRequired && <FaWindowClose role="button" style={{ cursor: 'pointer', pointerEvents: 'initial', verticalAlign: 'middle', padding: 10 }} onClick={() => setHideMediaSourcePlayer(true)} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isFileOpened && (
|
||||||
|
<div className="no-user-select" style={{ position: 'absolute', right: 0, bottom: 0, marginBottom: 10, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<VolumeControl playbackVolume={playbackVolume} setPlaybackVolume={setPlaybackVolume} />
|
||||||
|
|
||||||
|
{shouldShowPlaybackStreamSelector && <PlaybackStreamSelector subtitleStreams={subtitleStreams} videoStreams={videoStreams} audioStreams={audioStreams} activeSubtitleStreamIndex={activeSubtitleStreamIndex} activeVideoStreamIndex={activeVideoStreamIndex} activeAudioStreamIndex={activeAudioStreamIndex} onActiveSubtitleChange={onActiveSubtitleChange} onActiveVideoStreamChange={onActiveVideoStreamChange} onActiveAudioStreamChange={onActiveAudioStreamChange} />}
|
||||||
|
|
||||||
|
{compatPlayerEnabled && <div style={{ color: 'white', opacity: 0.7, padding: '.5em' }} role="button" onClick={() => incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}</div>}
|
||||||
|
|
||||||
|
{!showRightBar && (
|
||||||
|
<FaAngleLeft
|
||||||
|
title={t('Show sidebar')}
|
||||||
|
size={30}
|
||||||
|
role="button"
|
||||||
|
style={{ marginRight: 10, color: 'var(--gray12)', opacity: 0.7 }}
|
||||||
|
onClick={toggleSegmentsList}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{working && <Working text={working.text} cutProgress={cutProgress} onAbortClick={handleAbortWorkingClick} />}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible(undefined)} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{showRightBar && isFileOpened && filePath != null && (
|
||||||
|
<SegmentList
|
||||||
|
width={rightBarWidth}
|
||||||
|
currentSegIndex={currentSegIndexSafe}
|
||||||
|
apparentCutSegments={apparentCutSegments}
|
||||||
|
inverseCutSegments={inverseCutSegments}
|
||||||
|
getFrameCount={getFrameCount}
|
||||||
|
formatTimecode={formatTimecode}
|
||||||
|
onSegClick={setCurrentSegIndex}
|
||||||
|
updateSegOrder={updateSegOrder}
|
||||||
|
updateSegOrders={updateSegOrders}
|
||||||
|
onLabelSegment={onLabelSegment}
|
||||||
|
currentCutSeg={currentCutSeg}
|
||||||
|
segmentAtCursor={segmentAtCursor}
|
||||||
|
addSegment={addSegment}
|
||||||
|
onDuplicateSegmentClick={duplicateSegment}
|
||||||
|
removeCutSegment={removeCutSegment}
|
||||||
|
onRemoveSelected={removeSelectedSegments}
|
||||||
|
toggleSegmentsList={toggleSegmentsList}
|
||||||
|
splitCurrentSegment={splitCurrentSegment}
|
||||||
|
isSegmentSelected={isSegmentSelected}
|
||||||
|
selectedSegments={selectedSegmentsOrInverse}
|
||||||
|
onSelectSingleSegment={selectOnlySegment}
|
||||||
|
onToggleSegmentSelected={toggleSegmentSelected}
|
||||||
|
onDeselectAllSegments={deselectAllSegments}
|
||||||
|
onSelectAllSegments={selectAllSegments}
|
||||||
|
onInvertSelectedSegments={invertSelectedSegments}
|
||||||
|
onExtractSegmentFramesAsImages={extractSegmentFramesAsImages}
|
||||||
|
jumpSegStart={jumpSegStart}
|
||||||
|
jumpSegEnd={jumpSegEnd}
|
||||||
|
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
||||||
|
onSelectSegmentsByExpr={onSelectSegmentsByExpr}
|
||||||
|
onLabelSelectedSegments={onLabelSelectedSegments}
|
||||||
|
updateSegAtIndex={updateSegAtIndex}
|
||||||
|
editingSegmentTags={editingSegmentTags}
|
||||||
|
editingSegmentTagsSegmentIndex={editingSegmentTagsSegmentIndex}
|
||||||
|
setEditingSegmentTags={setEditingSegmentTags}
|
||||||
|
setEditingSegmentTagsSegmentIndex={setEditingSegmentTagsSegmentIndex}
|
||||||
|
onEditSegmentTags={onEditSegmentTags}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnimatePresence>
|
<div className="no-user-select" style={bottomStyle}>
|
||||||
{showRightBar && isFileOpened && filePath != null && (
|
<Timeline
|
||||||
<SegmentList
|
shouldShowKeyframes={shouldShowKeyframes}
|
||||||
width={rightBarWidth}
|
waveforms={waveforms}
|
||||||
currentSegIndex={currentSegIndexSafe}
|
shouldShowWaveform={shouldShowWaveform}
|
||||||
apparentCutSegments={apparentCutSegments}
|
waveformEnabled={waveformEnabled}
|
||||||
inverseCutSegments={inverseCutSegments}
|
showThumbnails={showThumbnails}
|
||||||
getFrameCount={getFrameCount}
|
neighbouringKeyFrames={neighbouringKeyFrames}
|
||||||
|
thumbnails={thumbnailsSorted}
|
||||||
|
playerTime={playerTime}
|
||||||
|
commandedTime={commandedTime}
|
||||||
|
relevantTime={relevantTime}
|
||||||
|
commandedTimeRef={commandedTimeRef}
|
||||||
|
startTimeOffset={startTimeOffset}
|
||||||
|
zoom={zoom}
|
||||||
|
seekAbs={userSeekAbs}
|
||||||
|
durationSafe={durationSafe}
|
||||||
|
apparentCutSegments={apparentCutSegments}
|
||||||
|
setCurrentSegIndex={setCurrentSegIndex}
|
||||||
|
currentSegIndexSafe={currentSegIndexSafe}
|
||||||
|
inverseCutSegments={inverseCutSegments}
|
||||||
|
formatTimecode={formatTimecode}
|
||||||
|
formatTimeAndFrames={formatTimeAndFrames}
|
||||||
|
onZoomWindowStartTimeChange={setZoomWindowStartTime}
|
||||||
|
playing={playing}
|
||||||
|
isFileOpened={isFileOpened}
|
||||||
|
onWheel={onTimelineWheel}
|
||||||
|
goToTimecode={goToTimecode}
|
||||||
|
isSegmentSelected={isSegmentSelected}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BottomBar
|
||||||
|
zoom={zoom}
|
||||||
|
setZoom={setZoom}
|
||||||
|
timelineToggleComfortZoom={timelineToggleComfortZoom}
|
||||||
|
hasVideo={hasVideo}
|
||||||
|
isRotationSet={isRotationSet}
|
||||||
|
rotation={rotation}
|
||||||
|
areWeCutting={areWeCutting}
|
||||||
|
increaseRotation={increaseRotation}
|
||||||
|
cleanupFilesDialog={cleanupFilesDialog}
|
||||||
|
captureSnapshot={captureSnapshot}
|
||||||
|
onExportPress={onExportPress}
|
||||||
|
segmentsToExport={segmentsToExport}
|
||||||
|
seekAbs={userSeekAbs}
|
||||||
|
currentSegIndexSafe={currentSegIndexSafe}
|
||||||
|
cutSegments={cutSegments}
|
||||||
|
currentCutSeg={currentCutSeg}
|
||||||
|
selectedSegments={selectedSegments}
|
||||||
|
setCutStart={setCutStart}
|
||||||
|
setCutEnd={setCutEnd}
|
||||||
|
setCurrentSegIndex={setCurrentSegIndex}
|
||||||
|
jumpCutEnd={jumpCutEnd}
|
||||||
|
jumpCutStart={jumpCutStart}
|
||||||
|
jumpTimelineStart={jumpTimelineStart}
|
||||||
|
jumpTimelineEnd={jumpTimelineEnd}
|
||||||
|
startTimeOffset={startTimeOffset}
|
||||||
|
setCutTime={setCutTime}
|
||||||
|
currentApparentCutSeg={currentApparentCutSeg}
|
||||||
|
playing={playing}
|
||||||
|
shortStep={shortStep}
|
||||||
|
seekClosestKeyframe={seekClosestKeyframe}
|
||||||
|
togglePlay={togglePlay}
|
||||||
|
showThumbnails={showThumbnails}
|
||||||
|
toggleShowThumbnails={toggleShowThumbnails}
|
||||||
|
toggleWaveformMode={toggleWaveformMode}
|
||||||
|
waveformMode={waveformMode}
|
||||||
|
hasAudio={hasAudio}
|
||||||
|
keyframesEnabled={keyframesEnabled}
|
||||||
|
toggleShowKeyframes={toggleShowKeyframes}
|
||||||
|
detectedFps={detectedFps}
|
||||||
|
toggleLoopSelectedSegments={toggleLoopSelectedSegments}
|
||||||
|
isFileOpened={isFileOpened}
|
||||||
|
darkMode={darkMode}
|
||||||
|
setDarkMode={setDarkMode}
|
||||||
|
outputPlaybackRate={outputPlaybackRate}
|
||||||
|
setOutputPlaybackRate={setOutputPlaybackRate}
|
||||||
|
formatTimecode={formatTimecode}
|
||||||
|
parseTimecode={parseTimecode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ExportConfirm 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} smartCutBitrate={smartCutBitrate} setSmartCutBitrate={setSmartCutBitrate} />
|
||||||
|
|
||||||
|
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} maxWidth={1000}>
|
||||||
|
{mainStreams && filePath != null && (
|
||||||
|
<StreamsSelector
|
||||||
|
mainFilePath={filePath}
|
||||||
|
mainFileFormatData={mainFileFormatData}
|
||||||
|
mainFileChapters={mainFileChapters}
|
||||||
|
allFilesMeta={allFilesMeta}
|
||||||
|
externalFilesMeta={externalFilesMeta}
|
||||||
|
setExternalFilesMeta={setExternalFilesMeta}
|
||||||
|
showAddStreamSourceDialog={showIncludeExternalStreamsDialog}
|
||||||
|
mainFileStreams={mainStreams}
|
||||||
|
isCopyingStreamId={isCopyingStreamId}
|
||||||
|
toggleCopyStreamId={toggleCopyStreamId}
|
||||||
|
setCopyStreamIdsForPath={setCopyStreamIdsForPath}
|
||||||
|
onExtractAllStreamsPress={extractAllStreams}
|
||||||
|
onExtractStreamPress={extractSingleStream}
|
||||||
|
shortestFlag={shortestFlag}
|
||||||
|
setShortestFlag={setShortestFlag}
|
||||||
|
nonCopiedExtraStreams={nonCopiedExtraStreams}
|
||||||
|
customTagsByFile={customTagsByFile}
|
||||||
|
setCustomTagsByFile={setCustomTagsByFile}
|
||||||
|
paramsByStreamId={paramsByStreamId}
|
||||||
|
updateStreamParams={updateStreamParams}
|
||||||
formatTimecode={formatTimecode}
|
formatTimecode={formatTimecode}
|
||||||
onSegClick={setCurrentSegIndex}
|
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
|
||||||
updateSegOrder={updateSegOrder}
|
|
||||||
updateSegOrders={updateSegOrders}
|
|
||||||
onLabelSegment={onLabelSegment}
|
|
||||||
currentCutSeg={currentCutSeg}
|
|
||||||
segmentAtCursor={segmentAtCursor}
|
|
||||||
addSegment={addSegment}
|
|
||||||
onDuplicateSegmentClick={duplicateSegment}
|
|
||||||
removeCutSegment={removeCutSegment}
|
|
||||||
onRemoveSelected={removeSelectedSegments}
|
|
||||||
toggleSegmentsList={toggleSegmentsList}
|
|
||||||
splitCurrentSegment={splitCurrentSegment}
|
|
||||||
isSegmentSelected={isSegmentSelected}
|
|
||||||
selectedSegments={selectedSegmentsOrInverse}
|
|
||||||
onSelectSingleSegment={selectOnlySegment}
|
|
||||||
onToggleSegmentSelected={toggleSegmentSelected}
|
|
||||||
onDeselectAllSegments={deselectAllSegments}
|
|
||||||
onSelectAllSegments={selectAllSegments}
|
|
||||||
onInvertSelectedSegments={invertSelectedSegments}
|
|
||||||
onExtractSegmentFramesAsImages={extractSegmentFramesAsImages}
|
|
||||||
jumpSegStart={jumpSegStart}
|
|
||||||
jumpSegEnd={jumpSegEnd}
|
|
||||||
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
|
||||||
onSelectSegmentsByExpr={onSelectSegmentsByExpr}
|
|
||||||
onLabelSelectedSegments={onLabelSelectedSegments}
|
|
||||||
updateSegAtIndex={updateSegAtIndex}
|
|
||||||
editingSegmentTags={editingSegmentTags}
|
|
||||||
editingSegmentTagsSegmentIndex={editingSegmentTagsSegmentIndex}
|
|
||||||
setEditingSegmentTags={setEditingSegmentTags}
|
|
||||||
setEditingSegmentTagsSegmentIndex={setEditingSegmentTagsSegmentIndex}
|
|
||||||
onEditSegmentTags={onEditSegmentTags}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</Sheet>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="no-user-select" style={bottomStyle}>
|
<LastCommandsSheet
|
||||||
<Timeline
|
visible={lastCommandsVisible}
|
||||||
shouldShowKeyframes={shouldShowKeyframes}
|
onTogglePress={toggleLastCommands}
|
||||||
waveforms={waveforms}
|
ffmpegCommandLog={ffmpegCommandLog}
|
||||||
shouldShowWaveform={shouldShowWaveform}
|
|
||||||
waveformEnabled={waveformEnabled}
|
|
||||||
showThumbnails={showThumbnails}
|
|
||||||
neighbouringKeyFrames={neighbouringKeyFrames}
|
|
||||||
thumbnails={thumbnailsSorted}
|
|
||||||
playerTime={playerTime}
|
|
||||||
commandedTime={commandedTime}
|
|
||||||
relevantTime={relevantTime}
|
|
||||||
commandedTimeRef={commandedTimeRef}
|
|
||||||
startTimeOffset={startTimeOffset}
|
|
||||||
zoom={zoom}
|
|
||||||
seekAbs={userSeekAbs}
|
|
||||||
durationSafe={durationSafe}
|
|
||||||
apparentCutSegments={apparentCutSegments}
|
|
||||||
setCurrentSegIndex={setCurrentSegIndex}
|
|
||||||
currentSegIndexSafe={currentSegIndexSafe}
|
|
||||||
inverseCutSegments={inverseCutSegments}
|
|
||||||
formatTimecode={formatTimecode}
|
|
||||||
formatTimeAndFrames={formatTimeAndFrames}
|
|
||||||
onZoomWindowStartTimeChange={setZoomWindowStartTime}
|
|
||||||
playing={playing}
|
|
||||||
isFileOpened={isFileOpened}
|
|
||||||
onWheel={onTimelineWheel}
|
|
||||||
goToTimecode={goToTimecode}
|
|
||||||
isSegmentSelected={isSegmentSelected}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BottomBar
|
<Sheet visible={settingsVisible} onClosePress={toggleSettings}>
|
||||||
zoom={zoom}
|
<Settings
|
||||||
setZoom={setZoom}
|
onTunerRequested={onTunerRequested}
|
||||||
timelineToggleComfortZoom={timelineToggleComfortZoom}
|
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
|
||||||
hasVideo={hasVideo}
|
askForCleanupChoices={askForCleanupChoices}
|
||||||
isRotationSet={isRotationSet}
|
toggleStoreProjectInWorkingDir={toggleStoreProjectInWorkingDir}
|
||||||
rotation={rotation}
|
simpleMode={simpleMode}
|
||||||
areWeCutting={areWeCutting}
|
clearOutDir={clearOutDir}
|
||||||
increaseRotation={increaseRotation}
|
|
||||||
cleanupFilesDialog={cleanupFilesDialog}
|
|
||||||
captureSnapshot={captureSnapshot}
|
|
||||||
onExportPress={onExportPress}
|
|
||||||
segmentsToExport={segmentsToExport}
|
|
||||||
seekAbs={userSeekAbs}
|
|
||||||
currentSegIndexSafe={currentSegIndexSafe}
|
|
||||||
cutSegments={cutSegments}
|
|
||||||
currentCutSeg={currentCutSeg}
|
|
||||||
selectedSegments={selectedSegments}
|
|
||||||
setCutStart={setCutStart}
|
|
||||||
setCutEnd={setCutEnd}
|
|
||||||
setCurrentSegIndex={setCurrentSegIndex}
|
|
||||||
jumpCutEnd={jumpCutEnd}
|
|
||||||
jumpCutStart={jumpCutStart}
|
|
||||||
jumpTimelineStart={jumpTimelineStart}
|
|
||||||
jumpTimelineEnd={jumpTimelineEnd}
|
|
||||||
startTimeOffset={startTimeOffset}
|
|
||||||
setCutTime={setCutTime}
|
|
||||||
currentApparentCutSeg={currentApparentCutSeg}
|
|
||||||
playing={playing}
|
|
||||||
shortStep={shortStep}
|
|
||||||
seekClosestKeyframe={seekClosestKeyframe}
|
|
||||||
togglePlay={togglePlay}
|
|
||||||
showThumbnails={showThumbnails}
|
|
||||||
toggleShowThumbnails={toggleShowThumbnails}
|
|
||||||
toggleWaveformMode={toggleWaveformMode}
|
|
||||||
waveformMode={waveformMode}
|
|
||||||
hasAudio={hasAudio}
|
|
||||||
keyframesEnabled={keyframesEnabled}
|
|
||||||
toggleShowKeyframes={toggleShowKeyframes}
|
|
||||||
detectedFps={detectedFps}
|
|
||||||
toggleLoopSelectedSegments={toggleLoopSelectedSegments}
|
|
||||||
isFileOpened={isFileOpened}
|
|
||||||
darkMode={darkMode}
|
|
||||||
setDarkMode={setDarkMode}
|
|
||||||
outputPlaybackRate={outputPlaybackRate}
|
|
||||||
setOutputPlaybackRate={setOutputPlaybackRate}
|
|
||||||
formatTimecode={formatTimecode}
|
|
||||||
parseTimecode={parseTimecode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ExportConfirm 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} smartCutBitrate={smartCutBitrate} setSmartCutBitrate={setSmartCutBitrate} />
|
|
||||||
|
|
||||||
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} maxWidth={1000}>
|
|
||||||
{mainStreams && filePath != null && (
|
|
||||||
<StreamsSelector
|
|
||||||
mainFilePath={filePath}
|
|
||||||
mainFileFormatData={mainFileFormatData}
|
|
||||||
mainFileChapters={mainFileChapters}
|
|
||||||
allFilesMeta={allFilesMeta}
|
|
||||||
externalFilesMeta={externalFilesMeta}
|
|
||||||
setExternalFilesMeta={setExternalFilesMeta}
|
|
||||||
showAddStreamSourceDialog={showIncludeExternalStreamsDialog}
|
|
||||||
mainFileStreams={mainStreams}
|
|
||||||
isCopyingStreamId={isCopyingStreamId}
|
|
||||||
toggleCopyStreamId={toggleCopyStreamId}
|
|
||||||
setCopyStreamIdsForPath={setCopyStreamIdsForPath}
|
|
||||||
onExtractAllStreamsPress={extractAllStreams}
|
|
||||||
onExtractStreamPress={extractSingleStream}
|
|
||||||
shortestFlag={shortestFlag}
|
|
||||||
setShortestFlag={setShortestFlag}
|
|
||||||
nonCopiedExtraStreams={nonCopiedExtraStreams}
|
|
||||||
customTagsByFile={customTagsByFile}
|
|
||||||
setCustomTagsByFile={setCustomTagsByFile}
|
|
||||||
paramsByStreamId={paramsByStreamId}
|
|
||||||
updateStreamParams={updateStreamParams}
|
|
||||||
formatTimecode={formatTimecode}
|
|
||||||
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</Sheet>
|
||||||
</Sheet>
|
|
||||||
|
|
||||||
<LastCommandsSheet
|
<ConcatDialog isShown={batchFiles.length > 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} />
|
||||||
visible={lastCommandsVisible}
|
|
||||||
onTogglePress={toggleLastCommands}
|
|
||||||
ffmpegCommandLog={ffmpegCommandLog}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Sheet visible={settingsVisible} onClosePress={toggleSettings}>
|
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
||||||
<Settings
|
</div>
|
||||||
onTunerRequested={onTunerRequested}
|
</ThemeProvider>
|
||||||
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
|
</UserSettingsContext.Provider>
|
||||||
askForCleanupChoices={askForCleanupChoices}
|
</SegColorsContext.Provider>
|
||||||
toggleStoreProjectInWorkingDir={toggleStoreProjectInWorkingDir}
|
|
||||||
simpleMode={simpleMode}
|
|
||||||
clearOutDir={clearOutDir}
|
|
||||||
/>
|
|
||||||
</Sheet>
|
|
||||||
|
|
||||||
<ConcatDialog isShown={batchFiles.length > 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} />
|
<div id="swal2-container-wrapper" className={darkMode ? 'dark-theme' : undefined} style={{ color: 'var(--gray12)', background: 'var(--gray1)' }} />
|
||||||
|
</>
|
||||||
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
|
||||||
</div>
|
|
||||||
</ThemeProvider>
|
|
||||||
</UserSettingsContext.Provider>
|
|
||||||
</SegColorsContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import { getActiveDisposition, attachedPicDisposition } from './util/streams';
|
|||||||
import TagEditor from './components/TagEditor';
|
import TagEditor from './components/TagEditor';
|
||||||
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe';
|
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe';
|
||||||
import { CustomTagsByFile, FilesMeta, FormatTimecode, ParamsByStreamId, StreamParams } from './types';
|
import { CustomTagsByFile, FilesMeta, FormatTimecode, ParamsByStreamId, StreamParams } from './types';
|
||||||
|
import useUserSettings from './hooks/useUserSettings';
|
||||||
|
|
||||||
|
|
||||||
const dispositionOptions = ['default', 'dub', 'original', 'comment', 'lyrics', 'karaoke', 'forced', 'hearing_impaired', 'visual_impaired', 'clean_effects', 'attached_pic', 'captions', 'descriptions', 'dependent', 'metadata'];
|
const dispositionOptions = ['default', 'dub', 'original', 'comment', 'lyrics', 'karaoke', 'forced', 'hearing_impaired', 'visual_impaired', 'clean_effects', 'attached_pic', 'captions', 'descriptions', 'dependent', 'metadata'];
|
||||||
@ -147,13 +148,9 @@ const EditStreamDialog = memo(({ editingStream: { streamId: editingStreamId, pat
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function onInfoClick(json: unknown, title: string) {
|
|
||||||
showJson5Dialog({ title, json });
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copyStream, fileDuration, setEditingStream, onExtractStreamPress, paramsByStreamId, updateStreamParams, formatTimecode, loadSubtitleTrackToSegments }: {
|
const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copyStream, fileDuration, setEditingStream, onExtractStreamPress, paramsByStreamId, updateStreamParams, formatTimecode, loadSubtitleTrackToSegments, onInfoClick }: {
|
||||||
filePath: string, stream: FFprobeStream, onToggle: (a: number) => void, batchSetCopyStreamIds: (filter: (a: FFprobeStream) => boolean, enabled: boolean) => void, copyStream: boolean, fileDuration: number | undefined, setEditingStream: (a: EditingStream) => void, onExtractStreamPress?: () => void, paramsByStreamId: ParamsByStreamId, updateStreamParams: UpdateStreamParams, formatTimecode: FormatTimecode, loadSubtitleTrackToSegments?: (index: number) => void,
|
filePath: string, stream: FFprobeStream, onToggle: (a: number) => void, batchSetCopyStreamIds: (filter: (a: FFprobeStream) => boolean, enabled: boolean) => void, copyStream: boolean, fileDuration: number | undefined, setEditingStream: (a: EditingStream) => void, onExtractStreamPress?: () => void, paramsByStreamId: ParamsByStreamId, updateStreamParams: UpdateStreamParams, formatTimecode: FormatTimecode, loadSubtitleTrackToSegments?: (index: number) => void, onInfoClick: (json: unknown, title: string) => void,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -283,8 +280,8 @@ const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copySt
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function FileHeading({ path, formatData, chapters, onTrashClick, onEditClick, setCopyAllStreams, onExtractAllStreamsPress }: {
|
function FileHeading({ path, formatData, chapters, onTrashClick, onEditClick, setCopyAllStreams, onExtractAllStreamsPress, onInfoClick }: {
|
||||||
path: string, formatData: FFprobeFormat | undefined, chapters?: FFprobeChapter[] | undefined, onTrashClick?: (() => void) | undefined, onEditClick?: (() => void) | undefined, setCopyAllStreams: (a: boolean) => void, onExtractAllStreamsPress?: () => Promise<void>,
|
path: string, formatData: FFprobeFormat | undefined, chapters?: FFprobeChapter[] | undefined, onTrashClick?: (() => void) | undefined, onEditClick?: (() => void) | undefined, setCopyAllStreams: (a: boolean) => void, onExtractAllStreamsPress?: () => Promise<void>, onInfoClick: (json: unknown, title: string) => void,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -360,6 +357,7 @@ function StreamsSelector({
|
|||||||
const [editingStream, setEditingStream] = useState<EditingStream>();
|
const [editingStream, setEditingStream] = useState<EditingStream>();
|
||||||
const [editingTag, setEditingTag] = useState<string>();
|
const [editingTag, setEditingTag] = useState<string>();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { darkMode } = useUserSettings();
|
||||||
|
|
||||||
function getFormatDuration(formatData: FFprobeFormat | undefined) {
|
function getFormatDuration(formatData: FFprobeFormat | undefined) {
|
||||||
if (!formatData || !formatData.duration) return undefined;
|
if (!formatData || !formatData.duration) return undefined;
|
||||||
@ -394,13 +392,17 @@ function StreamsSelector({
|
|||||||
|
|
||||||
const externalFilesEntries = Object.entries(externalFilesMeta);
|
const externalFilesEntries = Object.entries(externalFilesMeta);
|
||||||
|
|
||||||
|
const onInfoClick = useCallback((json: unknown, title: string) => {
|
||||||
|
showJson5Dialog({ title, json, darkMode });
|
||||||
|
}, [darkMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p style={{ margin: '.5em 2em .5em 1em' }}>{t('Click to select which tracks to keep when exporting:')}</p>
|
<p style={{ margin: '.5em 2em .5em 1em' }}>{t('Click to select which tracks to keep when exporting:')}</p>
|
||||||
|
|
||||||
<div style={fileStyle}>
|
<div style={fileStyle}>
|
||||||
{/* We only support editing main file metadata for now */}
|
{/* We only support editing main file metadata for now */}
|
||||||
<FileHeading path={mainFilePath} formatData={mainFileFormatData} chapters={mainFileChapters} onEditClick={() => setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} />
|
<FileHeading onInfoClick={onInfoClick} path={mainFilePath} formatData={mainFileFormatData} chapters={mainFileChapters} onEditClick={() => setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} />
|
||||||
<table style={tableStyle}>
|
<table style={tableStyle}>
|
||||||
<Thead />
|
<Thead />
|
||||||
|
|
||||||
@ -420,6 +422,7 @@ function StreamsSelector({
|
|||||||
updateStreamParams={updateStreamParams}
|
updateStreamParams={updateStreamParams}
|
||||||
formatTimecode={formatTimecode}
|
formatTimecode={formatTimecode}
|
||||||
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
|
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
|
||||||
|
onInfoClick={onInfoClick}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -428,7 +431,7 @@ function StreamsSelector({
|
|||||||
|
|
||||||
{externalFilesEntries.map(([path, { streams: externalFileStreams, formatData }]) => (
|
{externalFilesEntries.map(([path, { streams: externalFileStreams, formatData }]) => (
|
||||||
<div key={path} style={fileStyle}>
|
<div key={path} style={fileStyle}>
|
||||||
<FileHeading path={path} formatData={formatData} onTrashClick={() => removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} />
|
<FileHeading path={path} formatData={formatData} onTrashClick={() => removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} onInfoClick={onInfoClick} />
|
||||||
|
|
||||||
<table style={tableStyle}>
|
<table style={tableStyle}>
|
||||||
<Thead />
|
<Thead />
|
||||||
@ -446,6 +449,7 @@ function StreamsSelector({
|
|||||||
paramsByStreamId={paramsByStreamId}
|
paramsByStreamId={paramsByStreamId}
|
||||||
updateStreamParams={updateStreamParams}
|
updateStreamParams={updateStreamParams}
|
||||||
formatTimecode={formatTimecode}
|
formatTimecode={formatTimecode}
|
||||||
|
onInfoClick={onInfoClick}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -9,4 +9,5 @@
|
|||||||
padding: 0 .5em 0 .3em;
|
padding: 0 .5em 0 .3em;
|
||||||
outline: .05em solid var(--gray8);
|
outline: .05em solid var(--gray8);
|
||||||
border: .05em solid var(--gray7);
|
border: .05em solid var(--gray7);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
33
src/renderer/src/components/Checkbox.module.css
Normal file
33
src/renderer/src/components/Checkbox.module.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.CheckboxRoot {
|
||||||
|
all: unset
|
||||||
|
}
|
||||||
|
|
||||||
|
.CheckboxRoot {
|
||||||
|
background-color: var(--gray8);
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: .2em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 10px var(--gray1);
|
||||||
|
}
|
||||||
|
.CheckboxRoot:hover {
|
||||||
|
background-color: var(--gray9);
|
||||||
|
}
|
||||||
|
.CheckboxRoot:focus {
|
||||||
|
box-shadow: 0 0 0 2px var(--gray1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CheckboxIndicator {
|
||||||
|
color: var(--gray12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CheckboxRoot[data-disabled]{
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Label {
|
||||||
|
padding-left: .5em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
25
src/renderer/src/components/Checkbox.tsx
Normal file
25
src/renderer/src/components/Checkbox.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useId } from 'react';
|
||||||
|
import { Root, Indicator, CheckboxProps } from '@radix-ui/react-checkbox';
|
||||||
|
import { FaCheck } from 'react-icons/fa';
|
||||||
|
|
||||||
|
import classes from './Checkbox.module.css';
|
||||||
|
|
||||||
|
|
||||||
|
export default function Checkbox({ label, disabled, style, ...props }: CheckboxProps & { label?: string | undefined }) {
|
||||||
|
const id = useId();
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', ...style }}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
|
<Root className={classes['CheckboxRoot']} disabled={disabled} {...props} id={id}>
|
||||||
|
<Indicator className={classes['CheckboxIndicator']}>
|
||||||
|
<FaCheck style={{ fontSize: '.7em' }} />
|
||||||
|
</Indicator>
|
||||||
|
</Root>
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label className={classes['Label']} htmlFor={id} style={{ opacity: disabled ? 0.5 : undefined }}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import { memo, useState, useCallback, useEffect, useMemo, CSSProperties } from 'react';
|
import { memo, useState, useCallback, useEffect, useMemo, CSSProperties } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TextInput, IconButton, Alert, Checkbox, Dialog, Button, Paragraph, CogIcon } from 'evergreen-ui';
|
import { IconButton, Checkbox as EvergreenCheckbox, Dialog, Paragraph } from 'evergreen-ui';
|
||||||
import { AiOutlineMergeCells } from 'react-icons/ai';
|
import { AiOutlineMergeCells } from 'react-icons/ai';
|
||||||
import { FaQuestionCircle, FaExclamationTriangle } from 'react-icons/fa';
|
import { FaQuestionCircle, FaExclamationTriangle, FaCog } from 'react-icons/fa';
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
|
||||||
import invariant from 'tiny-invariant';
|
import invariant from 'tiny-invariant';
|
||||||
|
import Checkbox from './Checkbox';
|
||||||
|
|
||||||
import Swal from '../swal';
|
import { ReactSwal } from '../swal';
|
||||||
import { readFileMeta, getSmarterOutFormat } from '../ffmpeg';
|
import { readFileMeta, getSmarterOutFormat } from '../ffmpeg';
|
||||||
import useFileFormatState from '../hooks/useFileFormatState';
|
import useFileFormatState from '../hooks/useFileFormatState';
|
||||||
import OutputFormatSelect from './OutputFormatSelect';
|
import OutputFormatSelect from './OutputFormatSelect';
|
||||||
@ -15,17 +15,23 @@ import useUserSettings from '../hooks/useUserSettings';
|
|||||||
import { isMov } from '../util/streams';
|
import { isMov } from '../util/streams';
|
||||||
import { getOutFileExtension, getSuffixedFileName } from '../util';
|
import { getOutFileExtension, getSuffixedFileName } from '../util';
|
||||||
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../../ffprobe';
|
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../../ffprobe';
|
||||||
|
import Sheet from './Sheet';
|
||||||
|
import TextInput from './TextInput';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
const { basename } = window.require('path');
|
const { basename } = window.require('path');
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
const containerStyle: CSSProperties = { color: 'black' };
|
|
||||||
|
|
||||||
const rowStyle: CSSProperties = {
|
const rowStyle: CSSProperties = {
|
||||||
color: 'black', fontSize: 14, margin: '4px 0px', overflowY: 'auto', whiteSpace: 'nowrap',
|
fontSize: '1em', margin: '4px 0px', overflowY: 'auto', whiteSpace: 'nowrap',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function Alert({ text }: { text: string }) {
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: '1em' }}><FaExclamationTriangle style={{ color: 'var(--orange8)', fontSize: '1.3em', verticalAlign: 'middle', marginRight: '.2em' }} /> {text}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles }: {
|
function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles }: {
|
||||||
isShown: boolean, onHide: () => void, paths: string[], onConcat: (a: { paths: string[], includeAllStreams: boolean, streams: FFprobeStream[], outFileName: string, fileFormat: string, clearBatchFilesAfterConcat: boolean }) => Promise<void>, alwaysConcatMultipleFiles: boolean, setAlwaysConcatMultipleFiles: (a: boolean) => void,
|
isShown: boolean, onHide: () => void, paths: string[], onConcat: (a: { paths: string[], includeAllStreams: boolean, streams: FFprobeStream[], outFileName: string, fileFormat: string, clearBatchFilesAfterConcat: boolean }) => Promise<void>, alwaysConcatMultipleFiles: boolean, setAlwaysConcatMultipleFiles: (a: boolean) => void,
|
||||||
}) {
|
}) {
|
||||||
@ -167,72 +173,65 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog
|
<Sheet visible={isShown} onClosePress={onHide} maxWidth="100%" style={{ padding: '0 2em' }}>
|
||||||
title={t('Merge/concatenate files')}
|
<h2>{t('Merge/concatenate files')}</h2>
|
||||||
shouldCloseOnOverlayClick={false}
|
|
||||||
isShown={isShown}
|
<div style={{ marginBottom: '1em' }}>
|
||||||
onCloseComplete={onHide}
|
<div style={{ whiteSpace: 'pre-wrap', fontSize: '.9em', marginBottom: '1em' }}>
|
||||||
topOffset="3vh"
|
|
||||||
width="90vw"
|
|
||||||
footer={(
|
|
||||||
<>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
|
||||||
<Checkbox checked={enableReadFileMeta} onChange={(e) => setEnableReadFileMeta(e.target.checked)} label={t('Check compatibility')} marginLeft={10} marginRight={10} />
|
|
||||||
<Button iconBefore={CogIcon} onClick={() => setSettingsVisible(true)}>{t('Options')}</Button>
|
|
||||||
{fileFormat && detectedFileFormat ? (
|
|
||||||
<OutputFormatSelect style={{ height: 30, maxWidth: 180 }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />
|
|
||||||
) : (
|
|
||||||
<Button disabled isLoading>{t('Loading')}</Button>
|
|
||||||
)}
|
|
||||||
<Button iconBefore={<AiOutlineMergeCells />} isLoading={detectedFileFormat == null} disabled={!isOutFileNameValid} appearance="primary" onClick={onConcatClick}>{t('Merge!')}</Button>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
|
||||||
<Paragraph marginRight=".5em">{t('Output file name')}:</Paragraph>
|
|
||||||
<TextInput value={outFileName || ''} onChange={(e) => setOutFileName(e.target.value)} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div style={containerStyle}>
|
|
||||||
<div style={{ whiteSpace: 'pre-wrap', fontSize: 14, marginBottom: 10 }}>
|
|
||||||
{t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).')}
|
{t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div style={{ backgroundColor: 'var(--gray1)', borderRadius: '.1em' }}>
|
||||||
{paths.map((path, index) => (
|
{paths.map((path, index) => (
|
||||||
<div key={path} style={rowStyle} title={path}>
|
<div key={path} style={rowStyle} title={path}>
|
||||||
<div>
|
<div>
|
||||||
{index + 1}
|
{index + 1}
|
||||||
{'. '}
|
{'. '}
|
||||||
<span style={{ color: 'rgba(0,0,0,0.7)' }}>{basename(path)}</span>
|
<span>{basename(path)}</span>
|
||||||
{!allFilesMetaCache[path] && <FaQuestionCircle color="#996A13" style={{ marginLeft: 10 }} />}
|
{!allFilesMetaCache[path] && <FaQuestionCircle style={{ color: 'var(--orange8)', verticalAlign: 'middle', marginLeft: '1em' }} />}
|
||||||
{problemsByFile[path] && <IconButton appearance="minimal" icon={FaExclamationTriangle} onClick={() => onProblemsByFileClick(path)} title={i18n.t('Mismatches detected')} color="#996A13" style={{ marginLeft: 10 }} />}
|
{problemsByFile[path] && <IconButton appearance="minimal" icon={FaExclamationTriangle} onClick={() => onProblemsByFileClick(path)} title={i18n.t('Mismatches detected')} style={{ color: 'var(--orange8)', marginLeft: '1em' }} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', marginBottom: '.5em', gap: '.5em' }}>
|
||||||
|
<Checkbox checked={enableReadFileMeta} onCheckedChange={(checked) => setEnableReadFileMeta(!!checked)} label={t('Check compatibility')} />
|
||||||
|
|
||||||
|
<Button onClick={() => setSettingsVisible(true)} style={{ height: '1.7em' }}><FaCog style={{ fontSize: '1em', verticalAlign: 'middle' }} /> {t('Options')}</Button>
|
||||||
|
|
||||||
|
{fileFormat && detectedFileFormat && (
|
||||||
|
<OutputFormatSelect style={{ height: '1.7em', maxWidth: '20em' }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end', marginBottom: '1em' }}>
|
||||||
|
<div style={{ marginRight: '.5em' }}>{t('Output file name')}:</div>
|
||||||
|
<TextInput value={outFileName || ''} onChange={(e) => setOutFileName(e.target.value)} />
|
||||||
|
<Button disabled={detectedFileFormat == null || !isOutFileNameValid} onClick={onConcatClick} style={{ fontSize: '1.3em', padding: '0 .3em', marginLeft: '1em' }}><AiOutlineMergeCells style={{ fontSize: '1.4em', verticalAlign: 'middle' }} /> {t('Merge!')}</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{enableReadFileMeta && (!allFilesMeta || Object.values(problemsByFile).length > 0) && (
|
{enableReadFileMeta && (!allFilesMeta || Object.values(problemsByFile).length > 0) && (
|
||||||
<Alert intent="warning">{t('A mismatch was detected in at least one file. You may proceed, but the resulting file might not be playable.')}</Alert>
|
<Alert text={t('A mismatch was detected in at least one file. You may proceed, but the resulting file might not be playable.')} />
|
||||||
)}
|
)}
|
||||||
{!enableReadFileMeta && (
|
{!enableReadFileMeta && (
|
||||||
<Alert intent="warning">{t('File compatibility check is not enabled, so the merge operation might not produce a valid output. Enable "Check compatibility" below to check file compatibility before merging.')}</Alert>
|
<Alert text={t('File compatibility check is not enabled, so the merge operation might not produce a valid output. Enable "Check compatibility" below to check file compatibility before merging.')} />
|
||||||
)}
|
)}
|
||||||
</Dialog>
|
</Sheet>
|
||||||
|
|
||||||
<Dialog isShown={settingsVisible} onCloseComplete={() => setSettingsVisible(false)} title={t('Merge options')} hasCancel={false} confirmLabel={t('Close')}>
|
<Dialog isShown={settingsVisible} onCloseComplete={() => setSettingsVisible(false)} title={t('Merge options')} hasCancel={false} confirmLabel={t('Close')}>
|
||||||
<Checkbox checked={includeAllStreams} onChange={(e) => setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} />
|
<EvergreenCheckbox checked={includeAllStreams} onChange={(e) => setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} />
|
||||||
|
|
||||||
<Checkbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
|
<EvergreenCheckbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
|
||||||
|
|
||||||
{fileFormat != null && isMov(fileFormat) && <Checkbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />}
|
{fileFormat != null && isMov(fileFormat) && <EvergreenCheckbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />}
|
||||||
|
|
||||||
<Checkbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
|
<EvergreenCheckbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
|
||||||
|
|
||||||
<Checkbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
|
<EvergreenCheckbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
|
||||||
|
|
||||||
<Checkbox checked={clearBatchFilesAfterConcat} onChange={(e) => setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} />
|
<EvergreenCheckbox checked={clearBatchFilesAfterConcat} onChange={(e) => setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} />
|
||||||
|
|
||||||
<Paragraph>{t('Note that also other settings from the normal export dialog apply to this merge function. For more information about all options, see the export dialog.')}</Paragraph>
|
<Paragraph>{t('Note that also other settings from the normal export dialog apply to this merge function. For more information about all options, see the export dialog.')}</Paragraph>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -5,7 +5,7 @@ import { FaRegCheckCircle } from 'react-icons/fa';
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { useTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { IoIosHelpCircle } from 'react-icons/io';
|
import { IoIosHelpCircle } from 'react-icons/io';
|
||||||
import { SweetAlertIcon } from 'sweetalert2';
|
import type { SweetAlertIcon } from 'sweetalert2';
|
||||||
|
|
||||||
import ExportButton from './ExportButton';
|
import ExportButton from './ExportButton';
|
||||||
import ExportModeButton from './ExportModeButton';
|
import ExportModeButton from './ExportModeButton';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { memo, Fragment, useEffect, useMemo, useCallback, useState, ReactNode, SetStateAction, Dispatch, useRef } from 'react';
|
import { memo, Fragment, useEffect, useMemo, useCallback, useState, ReactNode, SetStateAction, Dispatch, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SearchInput, PlusIcon, InlineAlert, UndoIcon, Paragraph, TakeActionIcon, IconButton, Button, DeleteIcon, AddIcon, Heading, Text, Dialog } from 'evergreen-ui';
|
import { SearchInput, PlusIcon, InlineAlert, UndoIcon, Paragraph, TakeActionIcon, IconButton, Button, DeleteIcon, AddIcon, Dialog } from 'evergreen-ui';
|
||||||
import { FaMouse, FaPlus, FaStepForward, FaStepBackward } from 'react-icons/fa';
|
import { FaMouse, FaPlus, FaStepForward, FaStepBackward } from 'react-icons/fa';
|
||||||
import Mousetrap from 'mousetrap';
|
import Mousetrap from 'mousetrap';
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
@ -14,6 +14,7 @@ import SegmentCutpointButton from './SegmentCutpointButton';
|
|||||||
import { getModifier } from '../hooks/useTimelineScroll';
|
import { getModifier } from '../hooks/useTimelineScroll';
|
||||||
import { KeyBinding, KeyboardAction } from '../../../../types';
|
import { KeyBinding, KeyboardAction } from '../../../../types';
|
||||||
import { StateSegment } from '../types';
|
import { StateSegment } from '../types';
|
||||||
|
import Sheet from './Sheet';
|
||||||
|
|
||||||
|
|
||||||
type Category = string;
|
type Category = string;
|
||||||
@ -22,7 +23,7 @@ type ActionsMap = Record<KeyboardAction, { name: string, category?: Category, be
|
|||||||
|
|
||||||
const renderKeys = (keys: string[]) => keys.map((key, i) => (
|
const renderKeys = (keys: string[]) => keys.map((key, i) => (
|
||||||
<Fragment key={key}>
|
<Fragment key={key}>
|
||||||
{i > 0 && <FaPlus size={8} style={{ marginLeft: 4, marginRight: 4, color: 'rgba(0,0,0,0.5)' }} />}
|
{i > 0 && <FaPlus style={{ fontSize: '.4em', opacity: 0.8, marginLeft: '.4em', marginRight: '.4em' }} />}
|
||||||
<kbd>{key.toUpperCase()}</kbd>
|
<kbd>{key.toUpperCase()}</kbd>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
));
|
));
|
||||||
@ -116,7 +117,7 @@ const CreateBinding = memo(({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const rowStyle = { display: 'flex', alignItems: 'center', margin: '.2em 0', borderBottom: '1px solid rgba(0,0,0,0.1)', paddingBottom: '.5em' };
|
const rowStyle = { display: 'flex', alignItems: 'center', borderBottom: '1px solid rgba(0,0,0,0.1)', paddingBottom: '.2em' };
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const KeyboardShortcuts = memo(({
|
const KeyboardShortcuts = memo(({
|
||||||
@ -626,18 +627,18 @@ const KeyboardShortcuts = memo(({
|
|||||||
const extraLinesPerCategory: Record<Category, ReactNode> = {
|
const extraLinesPerCategory: Record<Category, ReactNode> = {
|
||||||
[zoomOperationsCategory]: [
|
[zoomOperationsCategory]: [
|
||||||
<div key="1" style={{ ...rowStyle, alignItems: 'center' }}>
|
<div key="1" style={{ ...rowStyle, alignItems: 'center' }}>
|
||||||
<Text>{t('Zoom in/out timeline')}</Text>
|
<span>{t('Zoom in/out timeline')}</span>
|
||||||
<div style={{ flexGrow: 1 }} />
|
<div style={{ flexGrow: 1 }} />
|
||||||
<FaMouse style={{ marginRight: 3 }} />
|
<FaMouse style={{ marginRight: '.3em' }} />
|
||||||
<Text>{t('Mouse scroll/wheel up/down')}</Text>
|
<span>{t('Mouse scroll/wheel up/down')}</span>
|
||||||
</div>,
|
</div>,
|
||||||
|
|
||||||
<div key="2" style={{ ...rowStyle, alignItems: 'center' }}>
|
<div key="2" style={{ ...rowStyle, alignItems: 'center' }}>
|
||||||
<Text>{t('Pan timeline')}</Text>
|
<span>{t('Pan timeline')}</span>
|
||||||
<div style={{ flexGrow: 1 }} />
|
<div style={{ flexGrow: 1 }} />
|
||||||
{getModifier(mouseWheelZoomModifierKey).map((v) => <kbd key={v} style={{ marginRight: '.7em' }}>{v}</kbd>)}
|
{getModifier(mouseWheelZoomModifierKey).map((v) => <kbd key={v} style={{ marginRight: '.7em' }}>{v}</kbd>)}
|
||||||
<FaMouse style={{ marginRight: 3 }} />
|
<FaMouse style={{ marginRight: '.3em' }} />
|
||||||
<Text>{t('Mouse scroll/wheel up/down')}</Text>
|
<span>{t('Mouse scroll/wheel up/down')}</span>
|
||||||
</div>,
|
</div>,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -723,14 +724,14 @@ const KeyboardShortcuts = memo(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ color: 'black', marginBottom: '1em' }}>
|
<div style={{ marginBottom: '1em' }}>
|
||||||
<div>
|
<div style={{ marginBottom: '1em' }}>
|
||||||
<SearchInput ref={searchInputRef} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search" width="100%" />
|
<SearchInput ref={searchInputRef} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search" width="100%" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{categoriesWithActions.map(([category, actionsInCategory]) => (
|
{categoriesWithActions.map(([category, actionsInCategory]) => (
|
||||||
<div key={category}>
|
<div key={category}>
|
||||||
{category !== 'undefined' && <Heading marginTop={30} marginBottom={14}>{category}</Heading>}
|
{category !== 'undefined' && <div style={{ marginTop: '2em', marginBottom: '.7em', fontSize: '1.4em' }}>{category}</div>}
|
||||||
|
|
||||||
{actionsInCategory.map(([action, actionObj]) => {
|
{actionsInCategory.map(([action, actionObj]) => {
|
||||||
const actionName = (actionObj && actionObj.name) || action;
|
const actionName = (actionObj && actionObj.name) || action;
|
||||||
@ -742,8 +743,8 @@ const KeyboardShortcuts = memo(({
|
|||||||
<div key={action} style={rowStyle}>
|
<div key={action} style={rowStyle}>
|
||||||
<div>
|
<div>
|
||||||
{beforeContent}
|
{beforeContent}
|
||||||
<Text title={action} marginRight={10}>{actionName}</Text>
|
<span title={action} style={{ marginRight: '.5em', opacity: 0.9 }}>{actionName}</span>
|
||||||
<div style={{ fontSize: '.8em', opacity: 0.4 }} title={t('API action name: {{action}}', { action })}>{action}</div>
|
<div style={{ fontSize: '.8em', opacity: 0.3 }} title={t('API action name: {{action}}', { action })}>{action}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ flexGrow: 1 }} />
|
<div style={{ flexGrow: 1 }} />
|
||||||
@ -757,7 +758,7 @@ const KeyboardShortcuts = memo(({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{bindingsForThisAction.length === 0 && <Text color="muted">{t('No binding')}</Text>}
|
{bindingsForThisAction.length === 0 && <span style={{ opacity: 0.8, fontSize: '.8em' }}>{t('No binding')}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<IconButton title={t('Bind new key to action')} appearance="minimal" intent="success" icon={AddIcon} onClick={() => onAddBindingClick(action)} />
|
<IconButton title={t('Bind new key to action')} appearance="minimal" intent="success" icon={AddIcon} onClick={() => onAddBindingClick(action)} />
|
||||||
@ -785,17 +786,13 @@ function KeyboardShortcutsDialog({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Sheet visible={isShown} onClosePress={onHide} maxWidth="40em" style={{ padding: '0 2em' }}>
|
||||||
title={t('Keyboard & mouse shortcuts')}
|
<h2>{t('Keyboard & mouse shortcuts')}</h2>
|
||||||
isShown={isShown}
|
|
||||||
confirmLabel={t('Done')}
|
<KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
||||||
hasCancel={false}
|
<Button onClick={onHide}>{t('Done')}</Button>
|
||||||
onCloseComplete={onHide}
|
|
||||||
onConfirm={onHide}
|
</Sheet>
|
||||||
topOffset="3vh"
|
|
||||||
>
|
|
||||||
{isShown ? <KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> : <div />}
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,10 @@ import { useDebounce } from 'use-debounce';
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { WarningSignIcon, ErrorIcon, Button, IconButton, TickIcon, ResetIcon } from 'evergreen-ui';
|
import { WarningSignIcon, ErrorIcon, Button, IconButton, TickIcon, ResetIcon } from 'evergreen-ui';
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
|
||||||
import { IoIosHelpCircle } from 'react-icons/io';
|
import { IoIosHelpCircle } from 'react-icons/io';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
import Swal from '../swal';
|
import { ReactSwal } from '../swal';
|
||||||
import HighlightedText from './HighlightedText';
|
import HighlightedText from './HighlightedText';
|
||||||
import { defaultOutSegTemplate, segNumVariable, segSuffixVariable, GenerateOutSegFileNames } from '../util/outputNameTemplate';
|
import { defaultOutSegTemplate, segNumVariable, segSuffixVariable, GenerateOutSegFileNames } from '../util/outputNameTemplate';
|
||||||
import useUserSettings from '../hooks/useUserSettings';
|
import useUserSettings from '../hooks/useUserSettings';
|
||||||
@ -15,8 +14,6 @@ import Switch from './Switch';
|
|||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import TextInput from './TextInput';
|
import TextInput from './TextInput';
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
const electron = window.require('electron');
|
const electron = window.require('electron');
|
||||||
|
|
||||||
const formatVariable = (variable) => `\${${variable}}`;
|
const formatVariable = (variable) => `\${${variable}}`;
|
||||||
|
@ -7,7 +7,7 @@ import styles from './Sheet.module.css';
|
|||||||
|
|
||||||
|
|
||||||
function Sheet({ visible, onClosePress, children, maxWidth = 800, style }: {
|
function Sheet({ visible, onClosePress, children, maxWidth = 800, style }: {
|
||||||
visible: boolean, onClosePress: () => void, children: ReactNode, maxWidth?: number, style?: CSSProperties
|
visible: boolean, onClosePress: () => void, children: ReactNode, maxWidth?: number | string, style?: CSSProperties
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { Checkbox, RadioGroup, Paragraph } from 'evergreen-ui';
|
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
|
||||||
|
|
||||||
import Swal from '../swal';
|
import { ReactSwal } from '../swal';
|
||||||
import { Html5ifyMode } from '../../../../types';
|
import { Html5ifyMode } from '../../../../types';
|
||||||
|
import Checkbox from '../components/Checkbox';
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
@ -31,33 +27,50 @@ export async function askForHtml5ifySpeed({ allowedOptions, showRemember, initia
|
|||||||
let selectedOption: Html5ifyMode = initialOption != null && inputOptions[initialOption] ? initialOption : Object.keys(inputOptions)[0]! as Html5ifyMode;
|
let selectedOption: Html5ifyMode = initialOption != null && inputOptions[initialOption] ? initialOption : Object.keys(inputOptions)[0]! as Html5ifyMode;
|
||||||
let rememberChoice = !!initialOption;
|
let rememberChoice = !!initialOption;
|
||||||
|
|
||||||
const Html = () => {
|
function AskForHtml5ifySpeed() {
|
||||||
const [option, setOption] = useState(selectedOption);
|
const [option, setOption] = useState(selectedOption);
|
||||||
const [remember, setRemember] = useState(rememberChoice);
|
const [remember, setRemember] = useState(rememberChoice);
|
||||||
|
|
||||||
const onOptionChange = useCallback((e) => {
|
const onOptionChange = useCallback((e) => {
|
||||||
selectedOption = e.target.value;
|
selectedOption = e.currentTarget.value;
|
||||||
setOption(selectedOption);
|
setOption(selectedOption);
|
||||||
}, []);
|
}, []);
|
||||||
const onRememberChange = useCallback((e) => {
|
|
||||||
rememberChoice = e.target.checked;
|
const onRememberChange = useCallback((checked) => {
|
||||||
|
rememberChoice = checked;
|
||||||
setRemember(rememberChoice);
|
setRemember(rememberChoice);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<Paragraph>{i18n.t('These options will let you convert files to a format that is supported by the player. You can try different options and see which works with your file. Note that the conversion is for preview only. When you run an export, the output will still be lossless with full quality')}</Paragraph>
|
<p>{i18n.t('These options will let you convert files to a format that is supported by the player. You can try different options and see which works with your file. Note that the conversion is for preview only. When you run an export, the output will still be lossless with full quality')}</p>
|
||||||
<RadioGroup
|
|
||||||
options={Object.entries(inputOptions).map(([value, label]) => ({ label, value }))}
|
{Object.entries(inputOptions).map(([value, label]) => {
|
||||||
value={option}
|
const id = `html5ify-${value}`;
|
||||||
onChange={onOptionChange}
|
return (
|
||||||
/>
|
<div key={value}>
|
||||||
{showRemember && <Checkbox checked={remember} onChange={onRememberChange} label={i18n.t('Use this for all files until LosslessCut is restarted?')} />}
|
<input
|
||||||
|
id={id}
|
||||||
|
type="radio"
|
||||||
|
name="html5ify-speed"
|
||||||
|
value={value}
|
||||||
|
checked={option === value}
|
||||||
|
onChange={onOptionChange}
|
||||||
|
/>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label htmlFor={id} style={{ marginLeft: '.5em' }}>{label}</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{showRemember && <Checkbox checked={remember} onCheckedChange={onRememberChange} label={i18n.t('Use this for all files until LosslessCut is restarted?')} style={{ marginTop: '.5em' }} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const { value: response } = await ReactSwal.fire({
|
const { value: response } = await ReactSwal.fire({
|
||||||
title: i18n.t('Convert to supported format'),
|
title: i18n.t('Convert to supported format'),
|
||||||
html: <Html />,
|
html: <AskForHtml5ifySpeed />,
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
import { CSSProperties, ReactNode, useState } from 'react';
|
import { CSSProperties, ReactNode, useState } from 'react';
|
||||||
import { ArrowRightIcon, HelpIcon, TickCircleIcon, WarningSignIcon, InfoSignIcon, Checkbox, IconComponent } from 'evergreen-ui';
|
import { ArrowRightIcon, HelpIcon, TickCircleIcon, WarningSignIcon, InfoSignIcon, IconComponent } from 'evergreen-ui';
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
|
||||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||||
import { tomorrow as syntaxStyle } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
import { tomorrow as lightSyntaxStyle, tomorrowNight as darkSyntaxStyle } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
import { SweetAlertOptions } from 'sweetalert2';
|
import type { SweetAlertOptions } from 'sweetalert2';
|
||||||
|
|
||||||
import { formatDuration } from '../util/duration';
|
import { formatDuration } from '../util/duration';
|
||||||
import Swal, { swalToastOptions, toast } from '../swal';
|
import Swal, { ReactSwal, swalToastOptions, toast } from '../swal';
|
||||||
import { parseYouTube } from '../edlFormats';
|
import { parseYouTube } from '../edlFormats';
|
||||||
import CopyClipboardButton from '../components/CopyClipboardButton';
|
import CopyClipboardButton from '../components/CopyClipboardButton';
|
||||||
|
import Checkbox from '../components/Checkbox';
|
||||||
import { isWindows, showItemInFolder } from '../util';
|
import { isWindows, showItemInFolder } from '../util';
|
||||||
import { ParseTimecode, SegmentBase } from '../types';
|
import { ParseTimecode, SegmentBase } from '../types';
|
||||||
|
|
||||||
const { dialog, shell } = window.require('@electron/remote');
|
const { dialog, shell } = window.require('@electron/remote');
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
export async function promptTimeOffset({ initialValue, title, text, inputPlaceholder, parseTimecode }: { initialValue?: string | undefined, title: string, text?: string | undefined, inputPlaceholder: string, parseTimecode: ParseTimecode }) {
|
export async function promptTimeOffset({ initialValue, title, text, inputPlaceholder, parseTimecode }: { initialValue?: string | undefined, title: string, text?: string | undefined, inputPlaceholder: string, parseTimecode: ParseTimecode }) {
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
@ -119,12 +118,12 @@ export async function askForFileOpenAction(inputOptions: Record<string, string>)
|
|||||||
|
|
||||||
{Object.entries(inputOptions).map(([key, text]) => (
|
{Object.entries(inputOptions).map(([key, text]) => (
|
||||||
<button type="button" key={key} onClick={() => onClick(key)} className="button-unstyled" style={{ display: 'block', marginBottom: '.5em' }}>
|
<button type="button" key={key} onClick={() => onClick(key)} className="button-unstyled" style={{ display: 'block', marginBottom: '.5em' }}>
|
||||||
<ArrowRightIcon color="rgba(0,0,0,0.5)" verticalAlign="middle" /> {text}
|
<ArrowRightIcon style={{ color: 'var(--gray10)' }} verticalAlign="middle" /> {text}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<button type="button" onClick={() => onClick()} className="button-unstyled" style={{ display: 'block', marginTop: '.5em' }}>
|
<button type="button" onClick={() => onClick()} className="button-unstyled" style={{ display: 'block', marginTop: '.5em' }}>
|
||||||
<ArrowRightIcon color="rgba(150,0,0,1)" /> {i18n.t('Cancel')}
|
<ArrowRightIcon style={{ color: 'var(--red11)' }} /> {i18n.t('Cancel')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -390,18 +389,18 @@ const CleanupChoices = ({ cleanupChoicesInitial, onChange: onChangeProp }) => {
|
|||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<p>{i18n.t('What do you want to do after exporting a file or when pressing the "delete source file" button?')}</p>
|
<p>{i18n.t('What do you want to do after exporting a file or when pressing the "delete source file" button?')}</p>
|
||||||
|
|
||||||
<Checkbox label={i18n.t('Close currently opened file')} checked={closeFile} disabled={trashSourceFile || trashTmpFiles} onChange={(e) => onChange('closeFile', e.target.checked)} />
|
<Checkbox label={i18n.t('Close currently opened file')} checked={closeFile} disabled={trashSourceFile || trashTmpFiles} onCheckedChange={(checked) => onChange('closeFile', checked)} />
|
||||||
|
|
||||||
<div style={{ marginTop: 25 }}>
|
<div style={{ marginTop: 25 }}>
|
||||||
<Checkbox label={i18n.t('Trash auto-generated files')} checked={trashTmpFiles} onChange={(e) => onChange('trashTmpFiles', e.target.checked)} />
|
<Checkbox label={i18n.t('Trash auto-generated files')} checked={trashTmpFiles} onCheckedChange={(checked) => onChange('trashTmpFiles', checked)} />
|
||||||
<Checkbox label={i18n.t('Trash original source file')} checked={trashSourceFile} onChange={(e) => onChange('trashSourceFile', e.target.checked)} />
|
<Checkbox label={i18n.t('Trash original source file')} checked={trashSourceFile} onCheckedChange={(checked) => onChange('trashSourceFile', checked)} />
|
||||||
<Checkbox label={i18n.t('Trash project LLC file')} checked={trashProjectFile} onChange={(e) => onChange('trashProjectFile', e.target.checked)} />
|
<Checkbox label={i18n.t('Trash project LLC file')} checked={trashProjectFile} onCheckedChange={(checked) => onChange('trashProjectFile', checked)} />
|
||||||
<Checkbox label={i18n.t('Permanently delete the files if trash fails?')} disabled={!(trashTmpFiles || trashProjectFile || trashSourceFile)} checked={deleteIfTrashFails} onChange={(e) => onChange('deleteIfTrashFails', e.target.checked)} />
|
<Checkbox label={i18n.t('Permanently delete the files if trash fails?')} disabled={!(trashTmpFiles || trashProjectFile || trashSourceFile)} checked={deleteIfTrashFails} onCheckedChange={(checked) => onChange('deleteIfTrashFails', checked)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: 25 }}>
|
<div style={{ marginTop: 25 }}>
|
||||||
<Checkbox label={i18n.t('Show this dialog every time?')} checked={askForCleanup} onChange={(e) => onChange('askForCleanup', e.target.checked)} />
|
<Checkbox label={i18n.t('Show this dialog every time?')} checked={askForCleanup} onCheckedChange={(checked) => onChange('askForCleanup', checked)} />
|
||||||
<Checkbox label={i18n.t('Do all of this automatically after exporting a file?')} checked={cleanupAfterExport} onChange={(e) => onChange('cleanupAfterExport', e.target.checked)} />
|
<Checkbox label={i18n.t('Do all of this automatically after exporting a file?')} checked={cleanupAfterExport} onCheckedChange={(checked) => onChange('cleanupAfterExport', checked)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -442,7 +441,7 @@ export async function createRandomSegments(fileDuration: number) {
|
|||||||
|
|
||||||
const { durationMin, durationMax, gapMin, gapMax } = response;
|
const { durationMin, durationMax, gapMin, gapMax } = response;
|
||||||
|
|
||||||
const randomInRange = (min, max) => min + Math.random() * (max - min);
|
const randomInRange = (min: number, max: number) => min + Math.random() * (max - min);
|
||||||
|
|
||||||
const edl: SegmentBase[] = [];
|
const edl: SegmentBase[] = [];
|
||||||
for (let start = randomInRange(gapMin, gapMax); start < fileDuration && edl.length < maxSegments; start += randomInRange(gapMin, gapMax)) {
|
for (let start = randomInRange(gapMin, gapMax); start < fileDuration && edl.length < maxSegments; start += randomInRange(gapMin, gapMax)) {
|
||||||
@ -560,7 +559,7 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) =>
|
|||||||
html: (
|
html: (
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<div style={{ marginBottom: '1em' }}>
|
<div style={{ marginBottom: '1em' }}>
|
||||||
<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. <button type="button" className="button-unstyled" style={{ fontWeight: 'bold' }} onClick={() => shell.openExternal('https://github.com/mifi/lossless-cut/blob/master/expressions.md')}>View available syntax.</button></Trans>
|
<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. <button type="button" className="link-button" onClick={() => shell.openExternal('https://github.com/mifi/lossless-cut/blob/master/expressions.md')}>View available syntax.</button></Trans>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '1em' }}><b>{i18n.t('Variables')}:</b> segment.label, segment.start, segment.end, segment.duration, segment.tags.*</div>
|
<div style={{ marginBottom: '1em' }}><b>{i18n.t('Variables')}:</b> segment.label, segment.start, segment.end, segment.duration, segment.tags.*</div>
|
||||||
@ -568,7 +567,7 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) =>
|
|||||||
<div><b>{i18n.t('Examples')}:</b></div>
|
<div><b>{i18n.t('Examples')}:</b></div>
|
||||||
|
|
||||||
{Object.entries(examples).map(([key, { name }]) => (
|
{Object.entries(examples).map(([key, { name }]) => (
|
||||||
<button key={key} type="button" onClick={() => addExample(key)} className="button-unstyled" style={{ display: 'block', marginBottom: '.1em' }}>
|
<button key={key} type="button" onClick={() => addExample(key)} className="link-button" style={{ display: 'block', marginBottom: '.1em' }}>
|
||||||
{name}
|
{name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@ -580,9 +579,9 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) =>
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showJson5Dialog({ title, json }: { title: string, json: unknown }) {
|
export function showJson5Dialog({ title, json, darkMode }: { title: string, json: unknown, darkMode: boolean }) {
|
||||||
const html = (
|
const html = (
|
||||||
<SyntaxHighlighter language="javascript" style={syntaxStyle} customStyle={{ textAlign: 'left', maxHeight: 300, overflowY: 'auto', fontSize: 14 }}>
|
<SyntaxHighlighter language="javascript" style={darkMode ? darkSyntaxStyle : lightSyntaxStyle} customStyle={{ textAlign: 'left', maxHeight: 300, overflowY: 'auto', fontSize: 14 }}>
|
||||||
{JSON5.stringify(json, null, 2)}
|
{JSON5.stringify(json, null, 2)}
|
||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
);
|
);
|
||||||
@ -598,7 +597,7 @@ export async function openDirToast({ filePath, text, html, ...props }: SweetAler
|
|||||||
const swal = text ? toast : ReactSwal;
|
const swal = text ? toast : ReactSwal;
|
||||||
|
|
||||||
// @ts-expect-error todo
|
// @ts-expect-error todo
|
||||||
const { value } = await swal.fire({
|
const { value } = await swal.fire<string>({
|
||||||
...swalToastOptions,
|
...swalToastOptions,
|
||||||
showConfirmButton: true,
|
showConfirmButton: true,
|
||||||
confirmButtonText: i18n.t('Show'),
|
confirmButtonText: i18n.t('Show'),
|
||||||
@ -611,19 +610,31 @@ export async function openDirToast({ filePath, text, html, ...props }: SweetAler
|
|||||||
if (value) showItemInFolder(filePath);
|
if (value) showItemInFolder(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const UnorderedList = ({ children }) => <ul style={{ paddingLeft: '1em' }}>{children}</ul>;
|
const UnorderedList = ({ children }) => (
|
||||||
// @ts-expect-error todo
|
<ul style={{ paddingLeft: '1em' }}>{children}</ul>
|
||||||
const ListItem = ({ icon: Icon, iconColor, children, style }: { icon: IconComponent, iconColor?: string, children: ReactNode, style?: CSSProperties }) => <li style={{ listStyle: 'none', ...style }}>{Icon && <Icon color={iconColor} size={14} marginRight=".3em" />} {children}</li>;
|
);
|
||||||
|
const ListItem = ({ icon: Icon, iconColor, children, style }: { icon: IconComponent, iconColor?: string, children: ReactNode, style?: CSSProperties }) => (
|
||||||
|
<li style={{ listStyle: 'none', ...style }}>
|
||||||
|
{Icon && <Icon style={{ color: iconColor }} size={14} marginRight=".4em" />}
|
||||||
|
{children}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
const Notices = ({ notices }) => notices.map((msg) => <ListItem key={msg} icon={InfoSignIcon} iconColor="info">{msg}</ListItem>);
|
const Notices = ({ notices }: { notices: string[] }) => notices.map((msg) => (
|
||||||
const Warnings = ({ warnings }) => warnings.map((msg) => <ListItem key={msg} icon={WarningSignIcon} iconColor="warning">{msg}</ListItem>);
|
<ListItem key={msg} icon={InfoSignIcon} iconColor="var(--blue9)">{msg}</ListItem>
|
||||||
const OutputIncorrectSeeHelpMenu = () => <ListItem icon={HelpIcon}>{i18n.t('If output does not look right, see the Help menu.')}</ListItem>;
|
));
|
||||||
|
const Warnings = ({ warnings }: { warnings: string[] }) => warnings.map((msg) => (
|
||||||
|
<ListItem key={msg} icon={WarningSignIcon} iconColor="var(--orange8)">{msg}</ListItem>
|
||||||
|
));
|
||||||
|
const OutputIncorrectSeeHelpMenu = () => (
|
||||||
|
<ListItem icon={HelpIcon}>{i18n.t('If output does not look right, see the Help menu.')}</ListItem>
|
||||||
|
);
|
||||||
|
|
||||||
export async function openExportFinishedToast({ filePath, warnings, notices }) {
|
export async function openExportFinishedToast({ filePath, warnings, notices }: { filePath: string, warnings: string[], notices: string[] }) {
|
||||||
const hasWarnings = warnings.length > 0;
|
const hasWarnings = warnings.length > 0;
|
||||||
const html = (
|
const html = (
|
||||||
<UnorderedList>
|
<UnorderedList>
|
||||||
<ListItem icon={TickCircleIcon} iconColor={hasWarnings ? 'warning' : 'success'} style={{ fontWeight: 'bold' }}>{hasWarnings ? i18n.t('Export finished with warning(s)', { count: warnings.length }) : i18n.t('Export is done!')}</ListItem>
|
<ListItem icon={TickCircleIcon} iconColor={hasWarnings ? 'var(--orange8)' : 'var(--green11)'} style={{ fontWeight: 'bold' }}>{hasWarnings ? i18n.t('Export finished with warning(s)', { count: warnings.length }) : i18n.t('Export is done!')}</ListItem>
|
||||||
<ListItem icon={InfoSignIcon}>{i18n.t('Please test the output file in your desired player/editor before you delete the source file.')}</ListItem>
|
<ListItem icon={InfoSignIcon}>{i18n.t('Please test the output file in your desired player/editor before you delete the source file.')}</ListItem>
|
||||||
<OutputIncorrectSeeHelpMenu />
|
<OutputIncorrectSeeHelpMenu />
|
||||||
<Notices notices={notices} />
|
<Notices notices={notices} />
|
||||||
@ -634,7 +645,7 @@ export async function openExportFinishedToast({ filePath, warnings, notices }) {
|
|||||||
await openDirToast({ filePath, html, width: 800, position: 'center', timer: hasWarnings ? undefined : 30000 });
|
await openDirToast({ filePath, html, width: 800, position: 'center', timer: hasWarnings ? undefined : 30000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openConcatFinishedToast({ filePath, warnings, notices }) {
|
export async function openConcatFinishedToast({ filePath, warnings, notices }: { filePath: string, warnings: string[], notices: string[] }) {
|
||||||
const hasWarnings = warnings.length > 0;
|
const hasWarnings = warnings.length > 0;
|
||||||
const html = (
|
const html = (
|
||||||
<UnorderedList>
|
<UnorderedList>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import { Button, TextInputField, LinkIcon } from 'evergreen-ui';
|
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
import { FaLink } from 'react-icons/fa';
|
||||||
|
|
||||||
import Swal from '../swal';
|
import Swal, { ReactSwal } from '../swal';
|
||||||
|
import Button from '../components/Button';
|
||||||
|
import TextInput from '../components/TextInput';
|
||||||
|
|
||||||
|
|
||||||
const { shell } = window.require('electron');
|
const { shell } = window.require('electron');
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
export interface ParameterDialogParameter { value: string, label?: string, hint?: string }
|
export interface ParameterDialogParameter { value: string, label?: string, hint?: string }
|
||||||
export type ParameterDialogParameters = Record<string, ParameterDialogParameter>;
|
export type ParameterDialogParameters = Record<string, ParameterDialogParameter>;
|
||||||
@ -37,15 +37,28 @@ const ParametersInput = ({ description, parameters: parametersIn, onChange, onSu
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left', padding: '.5em', borderRadius: '.3em' }}>
|
||||||
{description && <p>{description}</p>}
|
{description && <p>{description}</p>}
|
||||||
|
|
||||||
{docUrl && <p><Button iconBefore={LinkIcon} onClick={() => shell.openExternal(docUrl)}>Read more</Button></p>}
|
{docUrl && <p><Button onClick={() => shell.openExternal(docUrl)}><FaLink style={{ fontSize: '.8em' }} /> Read more</Button></p>}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{Object.entries(parametersIn).map(([key, parameter], i) => (
|
{Object.entries(parametersIn).map(([key, parameter], i) => {
|
||||||
<TextInputField ref={i === 0 ? firstInputRef : undefined} key={key} label={parameter.label || key} value={getParameter(key)} onChange={(e) => handleChange(key, e.target.value)} hint={parameter.hint} />
|
const id = `parameter-${key}`;
|
||||||
))}
|
return (
|
||||||
|
<div key={key} style={{ marginBottom: '.5em' }}>
|
||||||
|
<label htmlFor={id} style={{ display: 'block', fontFamily: 'monospace', marginBottom: '.3em' }}>{parameter.label || key}</label>
|
||||||
|
<TextInput
|
||||||
|
id={id}
|
||||||
|
ref={i === 0 ? firstInputRef : undefined}
|
||||||
|
value={getParameter(key)}
|
||||||
|
onChange={(e) => handleChange(key, e.target.value)}
|
||||||
|
style={{ marginBottom: '.2em' }}
|
||||||
|
/>
|
||||||
|
{parameter.hint && <div style={{ opacity: 0.6, fontSize: '0.8em' }}>{parameter.hint}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
<input type="submit" value="submit" style={{ display: 'none' }} />
|
<input type="submit" value="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
|
@ -6,8 +6,6 @@ import * as Electron from 'electron';
|
|||||||
import Remote from '@electron/remote';
|
import Remote from '@electron/remote';
|
||||||
import type path from 'node:path';
|
import type path from 'node:path';
|
||||||
|
|
||||||
import 'sweetalert2/dist/sweetalert2.css';
|
|
||||||
|
|
||||||
import '@fontsource/open-sans/300.css';
|
import '@fontsource/open-sans/300.css';
|
||||||
import '@fontsource/open-sans/300-italic.css';
|
import '@fontsource/open-sans/300-italic.css';
|
||||||
import '@fontsource/open-sans/400.css';
|
import '@fontsource/open-sans/400.css';
|
||||||
@ -28,6 +26,7 @@ import ErrorBoundary from './ErrorBoundary';
|
|||||||
import './i18n';
|
import './i18n';
|
||||||
|
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
import './swal2.scss';
|
||||||
|
|
||||||
|
|
||||||
type TypedRemote = Omit<typeof Remote, 'require'> & {
|
type TypedRemote = Omit<typeof Remote, 'require'> & {
|
||||||
|
@ -11,6 +11,8 @@ https://www.radix-ui.com/docs/colors/palette-composition/understanding-the-scale
|
|||||||
@import '@radix-ui/colors/greenDark.css';
|
@import '@radix-ui/colors/greenDark.css';
|
||||||
@import '@radix-ui/colors/cyan.css';
|
@import '@radix-ui/colors/cyan.css';
|
||||||
@import '@radix-ui/colors/cyanDark.css';
|
@import '@radix-ui/colors/cyanDark.css';
|
||||||
|
@import '@radix-ui/colors/blue.css';
|
||||||
|
@import '@radix-ui/colors/blueDark.css';
|
||||||
@import '@radix-ui/colors/gray.css';
|
@import '@radix-ui/colors/gray.css';
|
||||||
@import '@radix-ui/colors/grayDark.css';
|
@import '@radix-ui/colors/grayDark.css';
|
||||||
@import '@radix-ui/colors/blackA.css';
|
@import '@radix-ui/colors/blackA.css';
|
||||||
@ -85,6 +87,17 @@ code.highlighted {
|
|||||||
outline: revert;
|
outline: revert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
all: unset;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: .15em;
|
||||||
|
text-decoration-thickness: .05em;
|
||||||
|
}
|
||||||
|
.link-button:focus {
|
||||||
|
outline: revert;
|
||||||
|
}
|
||||||
|
|
||||||
/* https://stackoverflow.com/questions/18270894/html5-video-does-not-hide-controls-in-fullscreen-mode-in-chrome */
|
/* https://stackoverflow.com/questions/18270894/html5-video-does-not-hide-controls-in-fullscreen-mode-in-chrome */
|
||||||
video.main-player::-webkit-media-controls {
|
video.main-player::-webkit-media-controls {
|
||||||
display:none !important;
|
display:none !important;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import withReactContent from 'sweetalert2-react-content';
|
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import { CSSProperties } from 'react';
|
|
||||||
|
|
||||||
import CopyClipboardButton from './components/CopyClipboardButton';
|
import CopyClipboardButton from './components/CopyClipboardButton';
|
||||||
import { isStoreBuild, isMasBuild, isWindowsStoreBuild } from './util';
|
import { isStoreBuild, isMasBuild, isWindowsStoreBuild } from './util';
|
||||||
import Swal from './swal';
|
import { ReactSwal } from './swal';
|
||||||
|
|
||||||
const electron = window.require('electron');
|
const electron = window.require('electron');
|
||||||
|
|
||||||
@ -16,23 +14,18 @@ const { app } = remote;
|
|||||||
const { platform } = remote.require('./index.js');
|
const { platform } = remote.require('./index.js');
|
||||||
|
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
const linkStyle: CSSProperties = { fontWeight: 'bold', cursor: 'pointer' };
|
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export function openSendReportDialog(err: unknown | undefined, state?: unknown) {
|
export function openSendReportDialog(err: unknown | undefined, state?: unknown) {
|
||||||
const reportInstructions = isStoreBuild
|
const reportInstructions = isStoreBuild
|
||||||
? (
|
? (
|
||||||
<p><Trans>Please send an email to <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('mailto:losslesscut@mifi.no')}>losslesscut@mifi.no</span> where you describe what you were doing.</Trans></p>
|
<p><Trans>Please send an email to <span className="link-button" role="button" onClick={() => electron.shell.openExternal('mailto:losslesscut@mifi.no')}>losslesscut@mifi.no</span> where you describe what you were doing.</Trans></p>
|
||||||
) : (
|
) : (
|
||||||
<Trans>
|
<Trans>
|
||||||
<p>
|
<p>
|
||||||
If you're having a problem or question about LosslessCut, please first check the links in the <b>Help</b> menu. If you cannot find any resolution, you may ask a question in <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/discussions')}>GitHub discussions</span> or on <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut')}>Discord.</span>
|
If you're having a problem or question about LosslessCut, please first check the links in the <b>Help</b> menu. If you cannot find any resolution, you may ask a question in <span className="link-button" role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/discussions')}>GitHub discussions</span> or on <span className="link-button" role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut')}>Discord.</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you believe that you found a bug in LosslessCut, you may <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>report a bug</span>.
|
If you believe that you found a bug in LosslessCut, you may <span className="link-button" role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>report a bug</span>.
|
||||||
</p>
|
</p>
|
||||||
</Trans>
|
</Trans>
|
||||||
);
|
);
|
||||||
@ -67,11 +60,11 @@ export function openSendReportDialog(err: unknown | undefined, state?: unknown)
|
|||||||
<div style={{ textAlign: 'left', overflow: 'auto', maxHeight: 300, overflowY: 'auto' }}>
|
<div style={{ textAlign: 'left', overflow: 'auto', maxHeight: 300, overflowY: 'auto' }}>
|
||||||
{reportInstructions}
|
{reportInstructions}
|
||||||
|
|
||||||
<p><Trans>Include the following text:</Trans> <CopyClipboardButton text={text} /></p>
|
<p style={{ marginBottom: 0 }}><Trans>Include the following text:</Trans> <CopyClipboardButton text={text} /></p>
|
||||||
|
|
||||||
{!isStoreBuild && <p style={{ fontSize: '.8em', color: 'rgba(0,0,0,0.5)' }}><Trans>You might want to redact any sensitive information like paths.</Trans></p>}
|
{!isStoreBuild && <p style={{ marginTop: '.2em', fontSize: '.8em', opacity: 0.7 }}><Trans>You might want to redact any sensitive information like paths.</Trans></p>}
|
||||||
|
|
||||||
<div style={{ fontWeight: 600, fontSize: 12, whiteSpace: 'pre-wrap', color: '#900' }} contentEditable suppressContentEditableWarning>
|
<div style={{ fontWeight: 600, fontSize: '.75em', fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: 'var(--gray11)', backgroundColor: 'var(--gray3)', padding: '.3em' }} contentEditable suppressContentEditableWarning>
|
||||||
{text}
|
{text}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import SwalRaw, { SweetAlertOptions } from 'sweetalert2';
|
import SwalRaw from 'sweetalert2/dist/sweetalert2.js';
|
||||||
|
import type { SweetAlertOptions } from 'sweetalert2';
|
||||||
import { primaryColor } from './colors';
|
import withReactContent from 'sweetalert2-react-content';
|
||||||
|
|
||||||
|
|
||||||
const { systemPreferences } = window.require('@electron/remote');
|
const { systemPreferences } = window.require('@electron/remote');
|
||||||
@ -8,7 +8,7 @@ const { systemPreferences } = window.require('@electron/remote');
|
|||||||
const animationSettings = systemPreferences.getAnimationSettings();
|
const animationSettings = systemPreferences.getAnimationSettings();
|
||||||
|
|
||||||
let commonSwalOptions: SweetAlertOptions = {
|
let commonSwalOptions: SweetAlertOptions = {
|
||||||
confirmButtonColor: primaryColor,
|
target: '#swal2-container-wrapper',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (animationSettings.prefersReducedMotion) {
|
if (animationSettings.prefersReducedMotion) {
|
||||||
@ -53,3 +53,5 @@ export const errorToast = (text: string) => toast.fire({
|
|||||||
icon: 'error',
|
icon: 'error',
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ReactSwal = withReactContent(Swal);
|
||||||
|
46
src/renderer/src/swal2.scss
Normal file
46
src/renderer/src/swal2.scss
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
@import 'sweetalert2/src/variables';
|
||||||
|
|
||||||
|
// see colors.ts primaryColor
|
||||||
|
$swal2-confirm-button-background-color: var(--cyan9);
|
||||||
|
|
||||||
|
$myswal-background: var(--gray1);
|
||||||
|
$myswal-foreground: var(--gray12);
|
||||||
|
$swal2-outline-color: lighten($swal2-outline-color, 10%);
|
||||||
|
|
||||||
|
$swal2-background: $myswal-background;
|
||||||
|
$swal2-html-container-color: $myswal-foreground;
|
||||||
|
$swal2-title-color: $myswal-foreground;
|
||||||
|
$swal2-backdrop: rgba(0, 0, 0, .75);
|
||||||
|
|
||||||
|
$swal2-close-button-color: var(--gray11);
|
||||||
|
|
||||||
|
// FOOTER
|
||||||
|
$swal2-footer-border-color: var(--gray2);
|
||||||
|
$swal2-footer-color: $myswal-background;
|
||||||
|
|
||||||
|
// TIMER POGRESS BAR
|
||||||
|
$swal2-timer-progress-bar-background: var(--gray8);
|
||||||
|
|
||||||
|
// INPUT
|
||||||
|
$swal2-input-color: $myswal-foreground;
|
||||||
|
$swal2-input-background: var(--gray3);
|
||||||
|
|
||||||
|
// VALIDATION MESSAGE
|
||||||
|
$swal2-validation-message-background: var(--gray3);
|
||||||
|
$swal2-validation-message-color: $myswal-foreground;
|
||||||
|
|
||||||
|
// QUEUE
|
||||||
|
$swal2-progress-step-background: var(--gray5);
|
||||||
|
|
||||||
|
// COMMON VARIABLES FOR CONFIRM AND CANCEL BUTTONS
|
||||||
|
$swal2-button-focus-box-shadow: 0 0 0 1px $swal2-background, 0 0 0 3px $swal2-outline-color;
|
||||||
|
|
||||||
|
// TOAST
|
||||||
|
$swal2-toast-background: $myswal-background;
|
||||||
|
$swal2-toast-button-focus-box-shadow: 0 0 0 1px $swal2-background, 0 0 0 3px $swal2-outline-color;
|
||||||
|
|
||||||
|
.swal2-textarea::placeholder {
|
||||||
|
color: var(--gray8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@import 'sweetalert2/src/sweetalert2.scss';
|
314
yarn.lock
314
yarn.lock
@ -1709,6 +1709,42 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/primitive@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/primitive@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
checksum: 2b93e161d3fdabe9a64919def7fa3ceaecf2848341e9211520c401181c9eaebb8451c630b066fad2256e5c639c95edc41de0ba59c40eff37e799918d019822d1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-checkbox@npm:^1.0.4":
|
||||||
|
version: 1.0.4
|
||||||
|
resolution: "@radix-ui/react-checkbox@npm:1.0.4"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
"@radix-ui/primitive": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-context": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-presence": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-use-previous": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-use-size": "npm:1.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: e3f2f169c017349e3e7844911f116641e44a50d9cc3ba9e270a6bc9d2118641ac515c67fe2a611dad98eefb29ae1e2e6a47a81abd44570faaabe7056ec3f02b1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs@npm:1.0.0":
|
"@radix-ui/react-compose-refs@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-compose-refs@npm:1.0.0"
|
resolution: "@radix-ui/react-compose-refs@npm:1.0.0"
|
||||||
@ -1720,6 +1756,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-compose-refs@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-compose-refs@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 2b9a613b6db5bff8865588b6bf4065f73021b3d16c0a90b2d4c23deceeb63612f1f15de188227ebdc5f88222cab031be617a9dd025874c0487b303be3e5cc2a8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-context@npm:1.0.0":
|
"@radix-ui/react-context@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-context@npm:1.0.0"
|
resolution: "@radix-ui/react-context@npm:1.0.0"
|
||||||
@ -1731,6 +1782,42 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-context@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-context@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: a02187a3bae3a0f1be5fab5ad19c1ef06ceff1028d957e4d9994f0186f594a9c3d93ee34bacb86d1fa8eb274493362944398e1c17054d12cb3b75384f9ae564b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-presence@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-presence@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 406f0b5a54ea4e7881e15bddc3863234bb14bf3abd4a6e56ea57c6df6f9265a9ad5cfa158e3a98614f0dcbbb7c5f537e1f7158346e57cc3f29b522d62cf28823
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-primitive@npm:1.0.1":
|
"@radix-ui/react-primitive@npm:1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "@radix-ui/react-primitive@npm:1.0.1"
|
resolution: "@radix-ui/react-primitive@npm:1.0.1"
|
||||||
@ -1744,6 +1831,26 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-primitive@npm:1.0.3":
|
||||||
|
version: 1.0.3
|
||||||
|
resolution: "@radix-ui/react-primitive@npm:1.0.3"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
"@radix-ui/react-slot": "npm:1.0.2"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: bedb934ac07c710dc5550a7bfc7065d47e099d958cde1d37e4b1947ae5451f1b7e6f8ff5965e242578bf2c619065e6038c3a3aa779e5eafa7da3e3dbc685799f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-slot@npm:1.0.1":
|
"@radix-ui/react-slot@npm:1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "@radix-ui/react-slot@npm:1.0.1"
|
resolution: "@radix-ui/react-slot@npm:1.0.1"
|
||||||
@ -1756,6 +1863,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-slot@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@radix-ui/react-slot@npm:1.0.2"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 734866561e991438fbcf22af06e56b272ed6ee8f7b536489ee3bf2f736f8b53bf6bc14ebde94834aa0aceda854d018a0ce20bb171defffbaed1f566006cbb887
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-switch@npm:^1.0.1":
|
"@radix-ui/react-switch@npm:^1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "@radix-ui/react-switch@npm:1.0.1"
|
resolution: "@radix-ui/react-switch@npm:1.0.1"
|
||||||
@ -1786,6 +1909,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-callback-ref@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: b9fd39911c3644bbda14a84e4fca080682bef84212b8d8931fcaa2d2814465de242c4cfd8d7afb3020646bead9c5e539d478cea0a7031bee8a8a3bb164f3bc4c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-controllable-state@npm:1.0.0":
|
"@radix-ui/react-use-controllable-state@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-use-controllable-state@npm:1.0.0"
|
resolution: "@radix-ui/react-use-controllable-state@npm:1.0.0"
|
||||||
@ -1798,6 +1936,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-controllable-state@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-use-controllable-state@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
"@radix-ui/react-use-callback-ref": "npm:1.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: dee2be1937d293c3a492cb6d279fc11495a8f19dc595cdbfe24b434e917302f9ac91db24e8cc5af9a065f3f209c3423115b5442e65a5be9fd1e9091338972be9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-layout-effect@npm:1.0.0":
|
"@radix-ui/react-use-layout-effect@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-use-layout-effect@npm:1.0.0"
|
resolution: "@radix-ui/react-use-layout-effect@npm:1.0.0"
|
||||||
@ -1809,6 +1963,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-layout-effect@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-use-layout-effect@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: bed9c7e8de243a5ec3b93bb6a5860950b0dba359b6680c84d57c7a655e123dec9b5891c5dfe81ab970652e7779fe2ad102a23177c7896dde95f7340817d47ae5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-previous@npm:1.0.0":
|
"@radix-ui/react-use-previous@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-use-previous@npm:1.0.0"
|
resolution: "@radix-ui/react-use-previous@npm:1.0.0"
|
||||||
@ -1820,6 +1989,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-previous@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-use-previous@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 66b4312e857c58b75f3bf62a2048ef090b79a159e9da06c19a468c93e62336969c33dbef60ff16969f00b20386cc25d138f6a353f1658b35baac0a6eff4761b9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-size@npm:1.0.0":
|
"@radix-ui/react-use-size@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-use-size@npm:1.0.0"
|
resolution: "@radix-ui/react-use-size@npm:1.0.0"
|
||||||
@ -1832,6 +2016,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-size@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/react-use-size@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.13.10"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 6cc150ad1e9fa85019c225c5a5d50a0af6cdc4653dad0c21b4b40cd2121f36ee076db326c43e6bc91a69766ccff5a84e917d27970176b592577deea3c85a3e26
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@npm:4.10.0":
|
"@rollup/rollup-android-arm-eabi@npm:4.10.0":
|
||||||
version: 4.10.0
|
version: 4.10.0
|
||||||
resolution: "@rollup/rollup-android-arm-eabi@npm:4.10.0"
|
resolution: "@rollup/rollup-android-arm-eabi@npm:4.10.0"
|
||||||
@ -2797,6 +2997,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"anymatch@npm:~3.1.2":
|
||||||
|
version: 3.1.3
|
||||||
|
resolution: "anymatch@npm:3.1.3"
|
||||||
|
dependencies:
|
||||||
|
normalize-path: "npm:^3.0.0"
|
||||||
|
picomatch: "npm:^2.0.4"
|
||||||
|
checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"app-builder-bin@npm:4.0.0":
|
"app-builder-bin@npm:4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "app-builder-bin@npm:4.0.0"
|
resolution: "app-builder-bin@npm:4.0.0"
|
||||||
@ -3175,6 +3385,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"binary-extensions@npm:^2.0.0":
|
||||||
|
version: 2.3.0
|
||||||
|
resolution: "binary-extensions@npm:2.3.0"
|
||||||
|
checksum: bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"bl@npm:^4.0.3":
|
"bl@npm:^4.0.3":
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
resolution: "bl@npm:4.1.0"
|
resolution: "bl@npm:4.1.0"
|
||||||
@ -3271,6 +3488,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"braces@npm:~3.0.2":
|
||||||
|
version: 3.0.3
|
||||||
|
resolution: "braces@npm:3.0.3"
|
||||||
|
dependencies:
|
||||||
|
fill-range: "npm:^7.1.1"
|
||||||
|
checksum: fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"broccoli-node-api@npm:^1.7.0":
|
"broccoli-node-api@npm:^1.7.0":
|
||||||
version: 1.7.0
|
version: 1.7.0
|
||||||
resolution: "broccoli-node-api@npm:1.7.0"
|
resolution: "broccoli-node-api@npm:1.7.0"
|
||||||
@ -3619,6 +3845,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"chokidar@npm:>=3.0.0 <4.0.0":
|
||||||
|
version: 3.6.0
|
||||||
|
resolution: "chokidar@npm:3.6.0"
|
||||||
|
dependencies:
|
||||||
|
anymatch: "npm:~3.1.2"
|
||||||
|
braces: "npm:~3.0.2"
|
||||||
|
fsevents: "npm:~2.3.2"
|
||||||
|
glob-parent: "npm:~5.1.2"
|
||||||
|
is-binary-path: "npm:~2.1.0"
|
||||||
|
is-glob: "npm:~4.0.1"
|
||||||
|
normalize-path: "npm:~3.0.0"
|
||||||
|
readdirp: "npm:~3.6.0"
|
||||||
|
dependenciesMeta:
|
||||||
|
fsevents:
|
||||||
|
optional: true
|
||||||
|
checksum: c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"chownr@npm:^1.1.1":
|
"chownr@npm:^1.1.1":
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
resolution: "chownr@npm:1.1.4"
|
resolution: "chownr@npm:1.1.4"
|
||||||
@ -5822,6 +6067,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fill-range@npm:^7.1.1":
|
||||||
|
version: 7.1.1
|
||||||
|
resolution: "fill-range@npm:7.1.1"
|
||||||
|
dependencies:
|
||||||
|
to-regex-range: "npm:^5.0.1"
|
||||||
|
checksum: a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"finalhandler@npm:1.2.0":
|
"finalhandler@npm:1.2.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "finalhandler@npm:1.2.0"
|
resolution: "finalhandler@npm:1.2.0"
|
||||||
@ -6290,7 +6544,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"glob-parent@npm:^5.1.2":
|
"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
|
||||||
version: 5.1.2
|
version: 5.1.2
|
||||||
resolution: "glob-parent@npm:5.1.2"
|
resolution: "glob-parent@npm:5.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6878,6 +7132,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"immutable@npm:^4.0.0":
|
||||||
|
version: 4.3.6
|
||||||
|
resolution: "immutable@npm:4.3.6"
|
||||||
|
checksum: 59fedb67f26e265035616b27e33ef90b53b434cf76fb09212ec2d6ae32ee8d2fe2641e6dc32dbc78498c521fbf5f72c6740d39affba63a0a36a3884272371857
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"import-fresh@npm:^3.2.1":
|
"import-fresh@npm:^3.2.1":
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
resolution: "import-fresh@npm:3.3.0"
|
resolution: "import-fresh@npm:3.3.0"
|
||||||
@ -7062,6 +7323,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"is-binary-path@npm:~2.1.0":
|
||||||
|
version: 2.1.0
|
||||||
|
resolution: "is-binary-path@npm:2.1.0"
|
||||||
|
dependencies:
|
||||||
|
binary-extensions: "npm:^2.0.0"
|
||||||
|
checksum: 078e51b4f956c2c5fd2b26bb2672c3ccf7e1faff38e0ebdba45612265f4e3d9fc3127a1fa8370bbf09eab61339203c3d3b7af5662cbf8be4030f8fac37745b0e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"is-boolean-object@npm:^1.1.0":
|
"is-boolean-object@npm:^1.1.0":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "is-boolean-object@npm:1.1.2"
|
resolution: "is-boolean-object@npm:1.1.2"
|
||||||
@ -7172,7 +7442,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3":
|
"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1":
|
||||||
version: 4.0.3
|
version: 4.0.3
|
||||||
resolution: "is-glob@npm:4.0.3"
|
resolution: "is-glob@npm:4.0.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7893,6 +8163,7 @@ __metadata:
|
|||||||
"@fontsource/open-sans": "npm:^4.5.14"
|
"@fontsource/open-sans": "npm:^4.5.14"
|
||||||
"@octokit/core": "npm:5"
|
"@octokit/core": "npm:5"
|
||||||
"@radix-ui/colors": "npm:^0.1.8"
|
"@radix-ui/colors": "npm:^0.1.8"
|
||||||
|
"@radix-ui/react-checkbox": "npm:^1.0.4"
|
||||||
"@radix-ui/react-switch": "npm:^1.0.1"
|
"@radix-ui/react-switch": "npm:^1.0.1"
|
||||||
"@tsconfig/node18": "npm:^18.2.2"
|
"@tsconfig/node18": "npm:^18.2.2"
|
||||||
"@tsconfig/strictest": "npm:^2.0.2"
|
"@tsconfig/strictest": "npm:^2.0.2"
|
||||||
@ -7967,6 +8238,7 @@ __metadata:
|
|||||||
react-syntax-highlighter: "npm:^15.4.3"
|
react-syntax-highlighter: "npm:^15.4.3"
|
||||||
react-use: "npm:^17.4.0"
|
react-use: "npm:^17.4.0"
|
||||||
rimraf: "npm:^5.0.5"
|
rimraf: "npm:^5.0.5"
|
||||||
|
sass: "npm:^1.77.2"
|
||||||
screenfull: "npm:^6.0.2"
|
screenfull: "npm:^6.0.2"
|
||||||
scroll-into-view-if-needed: "npm:^2.2.28"
|
scroll-into-view-if-needed: "npm:^2.2.28"
|
||||||
semver: "npm:^7.6.0"
|
semver: "npm:^7.6.0"
|
||||||
@ -8675,6 +8947,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0":
|
||||||
|
version: 3.0.0
|
||||||
|
resolution: "normalize-path@npm:3.0.0"
|
||||||
|
checksum: 88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"normalize-url@npm:^6.0.1":
|
"normalize-url@npm:^6.0.1":
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
resolution: "normalize-url@npm:6.1.0"
|
resolution: "normalize-url@npm:6.1.0"
|
||||||
@ -9200,7 +9479,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"picomatch@npm:^2.2.3":
|
"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3":
|
||||||
version: 2.3.1
|
version: 2.3.1
|
||||||
resolution: "picomatch@npm:2.3.1"
|
resolution: "picomatch@npm:2.3.1"
|
||||||
checksum: 60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc
|
checksum: 60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc
|
||||||
@ -9789,6 +10068,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"readdirp@npm:~3.6.0":
|
||||||
|
version: 3.6.0
|
||||||
|
resolution: "readdirp@npm:3.6.0"
|
||||||
|
dependencies:
|
||||||
|
picomatch: "npm:^2.2.1"
|
||||||
|
checksum: 196b30ef6ccf9b6e18c4e1724b7334f72a093d011a99f3b5920470f0b3406a51770867b3e1ae9711f227ef7a7065982f6ee2ce316746b2cb42c88efe44297fe7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"reflect.getprototypeof@npm:^1.0.4":
|
"reflect.getprototypeof@npm:^1.0.4":
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
resolution: "reflect.getprototypeof@npm:1.0.5"
|
resolution: "reflect.getprototypeof@npm:1.0.5"
|
||||||
@ -10259,6 +10547,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"sass@npm:^1.77.2":
|
||||||
|
version: 1.77.2
|
||||||
|
resolution: "sass@npm:1.77.2"
|
||||||
|
dependencies:
|
||||||
|
chokidar: "npm:>=3.0.0 <4.0.0"
|
||||||
|
immutable: "npm:^4.0.0"
|
||||||
|
source-map-js: "npm:>=0.6.2 <2.0.0"
|
||||||
|
bin:
|
||||||
|
sass: sass.js
|
||||||
|
checksum: 4df71f1a01cd59613e7a25bfcec96ddf06e3546c238ba3238b96c6ac0dcf34b9ce238b4de7b39656f6cb0a5e7acccde19f53b521ae4abcdcbe600e0de9c97644
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"sax@npm:^1.2.4":
|
"sax@npm:^1.2.4":
|
||||||
version: 1.2.4
|
version: 1.2.4
|
||||||
resolution: "sax@npm:1.2.4"
|
resolution: "sax@npm:1.2.4"
|
||||||
@ -10616,6 +10917,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"source-map-js@npm:>=0.6.2 <2.0.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "source-map-js@npm:1.2.0"
|
||||||
|
checksum: 74f331cfd2d121c50790c8dd6d3c9de6be21926de80583b23b37029b0f37aefc3e019fa91f9a10a5e120c08135297e1ecf312d561459c45908cb1e0e365f49e5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"source-map-js@npm:^1.0.2":
|
"source-map-js@npm:^1.0.2":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "source-map-js@npm:1.0.2"
|
resolution: "source-map-js@npm:1.0.2"
|
||||||
|
Loading…
Reference in New Issue
Block a user