fix reduce motion

not working for swal
pull/2599/head
Mikael Finstad 9 months ago
parent 2511022d5c
commit 251102831a
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -104,6 +104,7 @@
"leaflet": "^1.9.4",
"lodash": "^4.17.19",
"luxon": "^3.5.0",
"mitt": "^3.0.1",
"mkdirp": "^1.0.3",
"mousetrap": "^1.6.5",
"nanoid": "^5.0.9",

@ -76,7 +76,7 @@ import {
isMasBuild,
toastError,
} from './util';
import { toast, errorToast, showPlaybackFailedMessage } from './swal';
import getSwal, { errorToast, showPlaybackFailedMessage } from './swal';
import { adjustRate } from './util/rate-calculator';
import { askExtractFramesAsImages } from './dialogs/extractFrames';
import { askForOutDir, askForImportChapters, askForFileOpenAction, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, showConcatFailedDialog, openYouTubeChaptersDialog, showRefuseToOverwrite, showOpenDialog, showMuxNotSupported, promptDownloadMediaUrl, CleanupChoicesType, showOutputNotWritable } from './dialogs';
@ -237,7 +237,7 @@ function App() {
const showNotification = useCallback((opts: SweetAlertOptions) => {
if (!hideAllNotifications) {
toast.fire(opts);
getSwal().toast.fire(opts);
}
}, [hideAllNotifications]);
@ -321,7 +321,7 @@ function App() {
const checkFileOpened = useCallback(() => {
if (isFileOpened) return true;
toast.fire({ icon: 'info', title: i18n.t('You need to open a media file first') });
getSwal().toast.fire({ icon: 'info', title: i18n.t('You need to open a media file first') });
return false;
}, [isFileOpened]);
@ -1429,7 +1429,7 @@ function App() {
} else if (isAudioDefinitelyNotSupported(fileMeta.streams)) {
showNotification({ icon: 'info', text: i18n.t('The audio track is not supported while previewing. You can convert to a supported format from the menu') });
} else if (!validDuration) {
toast.fire({ icon: 'warning', timer: 10000, text: i18n.t('This file does not have a valid duration. This may cause issues. You can try to fix the file\'s duration from the File menu') });
getSwal().toast.fire({ icon: 'warning', timer: 10000, text: i18n.t('This file does not have a valid duration. This may cause issues. You can try to fix the file\'s duration from the File menu') });
}
// This needs to be last, because it triggers <video> to load the video
@ -2190,7 +2190,7 @@ function App() {
setWorking(undefined);
}
} else if (error.code === PIPELINE_ERROR_READ) { // file is not readable or was removed
toast.fire({ icon: 'error', timer: 10000, text: i18n.t('Failed to read file. Perhaps it has been moved?') });
getSwal().toast.fire({ icon: 'error', timer: 10000, text: i18n.t('Failed to read file. Perhaps it has been moved?') });
}
} catch (err) {
toastError(err);

@ -18,7 +18,7 @@ import Select from './components/Select';
import SimpleModeButton from './components/SimpleModeButton';
import { withBlur, mirrorTransform, checkAppPath } from './util';
import { toast } from './swal';
import getSwal from './swal';
import { getSegColor as getSegColorRaw } from './util/colors';
import { useSegColors } from './contexts';
import { isExactDurationMatch } from './util/duration';
@ -42,8 +42,8 @@ const InvertCutModeButton = memo(({ invertCutSegments, setInvertCutSegments }: {
const onYinYangClick = useCallback(() => {
setInvertCutSegments((v) => {
const newVal = !v;
if (newVal) toast.fire({ title: t('When you export, selected segments on the timeline will be REMOVED - the surrounding areas will be KEPT') });
else toast.fire({ title: t('When you export, selected segments on the timeline will be KEPT - the surrounding areas will be REMOVED.') });
if (newVal) getSwal().toast.fire({ title: t('When you export, selected segments on the timeline will be REMOVED - the surrounding areas will be KEPT') });
else getSwal().toast.fire({ title: t('When you export, selected segments on the timeline will be KEPT - the surrounding areas will be REMOVED.') });
return newVal;
});
}, [setInvertCutSegments, t]);

@ -9,7 +9,6 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { useVirtualizer } from '@tanstack/react-virtual';
import { CSS } from '@dnd-kit/utilities';
import Swal from './swal';
import useContextMenu from './hooks/useContextMenu';
import useUserSettings from './hooks/useUserSettings';
import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } from './colors';
@ -20,6 +19,7 @@ import { ContextMenuTemplate, DefiniteSegmentBase, FormatTimecode, GetFrameCount
import { UseSegments } from './hooks/useSegments';
import * as Dialog from './components/Dialog';
import { DialogButton } from './components/Button';
import getSwal from './swal';
const buttonBaseStyle = {
@ -377,7 +377,7 @@ function SegmentList({
const onReorderSegs = useCallback(async (index: number) => {
if (cutSegments.length < 2) return;
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
title: `${t('Change order of segment')} ${index + 1}`,
text: t('Please enter a number from 1 to {{n}} to be the new order for the current segment', { n: cutSegments.length }),
input: 'text',

@ -1,10 +1,9 @@
// eslint-disable-next-line import/prefer-default-export
export const mySpring = { type: 'spring', damping: 50, stiffness: 700 };
import mitt from 'mitt';
let prefersReducedMotionValue = false;
export function setPrefersReducedMotion(v: boolean) {
prefersReducedMotionValue = v;
}
// eslint-disable-next-line import/prefer-default-export
export const mySpring = { type: 'spring', damping: 50, stiffness: 700 };
export const prefersReducedMotion = () => prefersReducedMotionValue;
export const emitter = mitt<{
reducedMotion: boolean
}>();

@ -14,7 +14,7 @@ import Switch from './Switch';
import { primaryTextColor } from '../colors';
import { withBlur } from '../util';
import { toast } from '../swal';
import getSwal from '../swal';
import { isMov as ffmpegIsMov } from '../util/streams';
import useUserSettings from '../hooks/useUserSettings';
import styles from './ExportConfirm.module.css';
@ -197,59 +197,59 @@ function ExportConfirm({
separate: t('Export each segment to a separate file'),
})[effectiveExportMode], [effectiveExportMode, t]);
const showHelpText = useCallback(({ icon = 'info', timer = 10000, text }: { icon?: SweetAlertIcon, timer?: number, text: string }) => toast.fire({ icon, timer, text }), []);
const showHelpText = useCallback(({ icon = 'info', timer = 10000, text }: { icon?: SweetAlertIcon, timer?: number, text: string }) => getSwal().toast.fire({ icon, timer, text }), []);
const onPreserveChaptersPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Whether to preserve chapters from source file.') });
}, []);
showHelpText({ text: i18n.t('Whether to preserve chapters from source file.') });
}, [showHelpText]);
const onPreserveMovDataHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Preserve all MOV/MP4 metadata tags (e.g. EXIF, GPS position etc.) from source file? Note that some players have trouble playing back files where all metadata is preserved, like iTunes and other Apple software') });
}, []);
showHelpText({ text: i18n.t('Preserve all MOV/MP4 metadata tags (e.g. EXIF, GPS position etc.) from source file? Note that some players have trouble playing back files where all metadata is preserved, like iTunes and other Apple software') });
}, [showHelpText]);
const onPreserveMetadataHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Whether to preserve metadata from source file. Default: Global (file metadata), per-track and per-chapter metadata will be copied. Non-global: Only per-track and per-chapter metadata will be copied. None: No metadata will be copied') });
}, []);
showHelpText({ text: i18n.t('Whether to preserve metadata from source file. Default: Global (file metadata), per-track and per-chapter metadata will be copied. Non-global: Only per-track and per-chapter metadata will be copied. None: No metadata will be copied') });
}, [showHelpText]);
const onMovFastStartHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Enabling this will allow faster playback of the exported file. This makes processing use 3 times as much export I/O, which is negligible for small files but might slow down exporting of large files.') });
}, []);
showHelpText({ text: i18n.t('Enabling this will allow faster playback of the exported file. This makes processing use 3 times as much export I/O, which is negligible for small files but might slow down exporting of large files.') });
}, [showHelpText]);
const onOutFmtHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Defaults to same format as input file. You can losslessly change the file format (container) of the file with this option. Not all formats support all codecs. Matroska/MP4/MOV support the most common codecs. Sometimes it\'s even impossible to export to the same output format as input.') });
}, []);
showHelpText({ text: i18n.t('Defaults to same format as input file. You can losslessly change the file format (container) of the file with this option. Not all formats support all codecs. Matroska/MP4/MOV support the most common codecs. Sometimes it\'s even impossible to export to the same output format as input.') });
}, [showHelpText]);
const onKeyframeCutHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('With "keyframe cut", we will cut at the nearest keyframe before the desired start cutpoint. This is recommended for most files. With "Normal cut" you may have to manually set the cutpoint a few frames before the next keyframe to achieve a precise cut') });
}, []);
showHelpText({ text: i18n.t('With "keyframe cut", we will cut at the nearest keyframe before the desired start cutpoint. This is recommended for most files. With "Normal cut" you may have to manually set the cutpoint a few frames before the next keyframe to achieve a precise cut') });
}, [showHelpText]);
const onSmartCutHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('This experimental feature will re-encode the part of the video from the cutpoint until the next keyframe in order to attempt to make a 100% accurate cut. Only works on some files. I\'ve had success with some h264 files, and only a few h265 files. See more here: {{url}}', { url: 'https://github.com/mifi/lossless-cut/issues/126' }) });
}, []);
showHelpText({ text: i18n.t('This experimental feature will re-encode the part of the video from the cutpoint until the next keyframe in order to attempt to make a 100% accurate cut. Only works on some files. I\'ve had success with some h264 files, and only a few h265 files. See more here: {{url}}', { url: 'https://github.com/mifi/lossless-cut/issues/126' }) });
}, [showHelpText]);
const onTracksHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('Not all formats support all track types, and LosslessCut is unable to properly cut some track types, so you may have to sacrifice some tracks by disabling them in order to get correct result.') });
}, []);
showHelpText({ text: i18n.t('Not all formats support all track types, and LosslessCut is unable to properly cut some track types, so you may have to sacrifice some tracks by disabling them in order to get correct result.') });
}, [showHelpText]);
const onSegmentsToChaptersHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('When merging, do you want to create chapters in the merged file, according to the cut segments? NOTE: This may dramatically increase processing time') });
}, []);
showHelpText({ text: i18n.t('When merging, do you want to create chapters in the merged file, according to the cut segments? NOTE: This may dramatically increase processing time') });
}, [showHelpText]);
const onPreserveMetadataOnMergeHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('When merging, do you want to preserve metadata from your original file? NOTE: This may dramatically increase processing time') });
}, []);
showHelpText({ text: i18n.t('When merging, do you want to preserve metadata from your original file? NOTE: This may dramatically increase processing time') });
}, [showHelpText]);
const onOutSegTemplateHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('You can customize the file name of the output segment(s) using special variables.', { count: segmentsToExport.length }) });
}, [segmentsToExport.length]);
showHelpText({ text: i18n.t('You can customize the file name of the output segment(s) using special variables.', { count: segmentsToExport.length }) });
}, [segmentsToExport.length, showHelpText]);
const onMergedFileTemplateHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('You can customize the file name of the merged file using special variables.') });
}, []);
showHelpText({ text: i18n.t('You can customize the file name of the merged file using special variables.') });
}, [showHelpText]);
const onExportModeHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: exportModeDescription });
}, [exportModeDescription]);
showHelpText({ text: exportModeDescription });
}, [exportModeDescription, showHelpText]);
const onAvoidNegativeTsHelpPress = useCallback(() => {
// https://ffmpeg.org/ffmpeg-all.html#Format-Options
@ -260,16 +260,16 @@ function ExportConfirm({
auto: i18n.t('Enables shifting when required by the target format.'),
disabled: i18n.t('Disables shifting of timestamp.'),
};
toast.fire({ icon: 'info', timer: 10000, text: `${avoidNegativeTs}: ${texts[avoidNegativeTs]}` });
}, [avoidNegativeTs]);
showHelpText({ text: `${avoidNegativeTs}: ${texts[avoidNegativeTs]}` });
}, [avoidNegativeTs, showHelpText]);
const onCutFromAdjustmentFramesHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: i18n.t('This option allows you to shift all segment start times forward by one or more frames before cutting. This can be useful if the output video starts from the wrong (preceding) keyframe.') });
}, []);
showHelpText({ text: i18n.t('This option allows you to shift all segment start times forward by one or more frames before cutting. This can be useful if the output video starts from the wrong (preceding) keyframe.') });
}, [showHelpText]);
const onFfmpegExperimentalHelpPress = useCallback(() => {
toast.fire({ icon: 'info', timer: 10000, text: t('Enable experimental ffmpeg features flag?') });
}, [t]);
showHelpText({ text: t('Enable experimental ffmpeg features flag?') });
}, [showHelpText, t]);
const canEditSegTemplate = !willMerge || !autoDeleteMergedSegments;

@ -1,11 +1,11 @@
import i18n from 'i18next';
import Swal from '../swal';
import getSwal from '../swal';
// eslint-disable-next-line import/prefer-default-export
export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps }: { segmentsNumFrames: number, plural: boolean, fps: number }) {
const { value: captureChoice } = await Swal.fire<string>({
const { value: captureChoice } = await getSwal().Swal.fire<string>({
text: i18n.t(plural ? 'Extract frames of the selected segments as images' : 'Extract frames of the current segment as images'),
icon: 'question',
input: 'radio',
@ -27,7 +27,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
let estimatedMaxNumFiles = segmentsNumFrames;
if (captureChoice === 'thumbnailFilter') {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
text: i18n.t('Capture the best image every nth second'),
icon: 'question',
input: 'text',
@ -46,7 +46,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
if (captureChoice === 'selectNthSec' || captureChoice === 'selectNthFrame') {
let nthFrame: number;
if (captureChoice === 'selectNthFrame') {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
text: i18n.t('Capture exactly one image every nth frame'),
icon: 'question',
input: 'number',
@ -59,7 +59,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
if (Number.isNaN(intervalFrames) || intervalFrames < 1) return undefined;
nthFrame = intervalFrames;
} else {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
text: i18n.t('Capture exactly one image every nth second'),
icon: 'question',
input: 'text',
@ -77,7 +77,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
estimatedMaxNumFiles = Math.round(segmentsNumFrames / nthFrame);
}
if (captureChoice === 'selectScene') {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
text: i18n.t('Capture frames that differ the most from the previous frame'),
icon: 'question',
input: 'text',
@ -97,7 +97,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
estimatedMaxNumFiles += 1; // just to be sure
if (estimatedMaxNumFiles > 1000) {
const { isConfirmed } = await Swal.fire({
const { isConfirmed } = await getSwal().Swal.fire({
icon: 'warning',
text: i18n.t('Note that depending on input parameters, up to {{estimatedMaxNumFiles}} files may be produced!', { estimatedMaxNumFiles }),
showCancelButton: true,

@ -5,7 +5,6 @@ import invariant from 'tiny-invariant';
import { FaArrowRight, FaExclamationTriangle, FaInfoCircle, FaQuestionCircle } from 'react-icons/fa';
import { formatDuration } from '../util/duration';
import Swal, { ReactSwal } from '../swal';
import { parseYouTube } from '../edlFormats';
import CopyClipboardButton from '../components/CopyClipboardButton';
import Checkbox from '../components/Checkbox';
@ -13,6 +12,7 @@ import { isWindows } from '../util';
import { ParseTimecode } from '../types';
import { FindKeyframeMode } from '../ffmpeg';
import { dangerColor } from '../colors';
import getSwal from '../swal';
const remote = window.require('@electron/remote');
const { dialog } = remote;
@ -30,7 +30,7 @@ export const showOpenDialog = async ({
export async function askForYouTubeInput() {
const example = i18n.t('YouTube video description\n00:00 Intro\n00:01 Chapter 2\n00:00:02.123 Chapter 3');
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
title: i18n.t('Import text chapters / YouTube'),
input: 'textarea',
inputPlaceholder: example,
@ -85,10 +85,10 @@ export async function askForFileOpenAction(inputOptions: Record<string, string>)
let value;
function onClick(key?: string) {
value = key;
Swal.close();
getSwal().Swal.close();
}
const swal = ReactSwal.fire({
const swal = getSwal().Swal.fire({
html: (
<div style={{ textAlign: 'left' }}>
<div style={{ marginBottom: '1em' }}>{i18n.t('You opened a new file. What do you want to do?')}</div>
@ -116,35 +116,35 @@ export async function askForFileOpenAction(inputOptions: Record<string, string>)
}
export async function showDiskFull() {
await Swal.fire({
await getSwal().Swal.fire({
icon: 'error',
text: i18n.t('You ran out of space'),
});
}
export async function showMuxNotSupported() {
await Swal.fire({
await getSwal().Swal.fire({
icon: 'error',
text: i18n.t('At least one codec is not supported by the selected output file format. Try another output format or try to disable one or more tracks.'),
});
}
export async function showOutputNotWritable() {
await Swal.fire({
await getSwal().Swal.fire({
icon: 'error',
text: i18n.t('You are not allowed to write the output file. This probably means that the file already exists with the wrong permissions, or you don\'t have write permissions to the output folder.'),
});
}
export async function showRefuseToOverwrite() {
await Swal.fire({
await getSwal().Swal.fire({
icon: 'warning',
text: i18n.t('Output file already exists, refusing to overwrite. You can turn on overwriting in settings.'),
});
}
export async function askForImportChapters() {
const { isConfirmed } = await Swal.fire({
const { isConfirmed } = await getSwal().Swal.fire({
icon: 'question',
text: i18n.t('This file has embedded chapters. Do you want to import the chapters as cut-segments?'),
showCancelButton: true,
@ -158,7 +158,7 @@ export async function askForImportChapters() {
const maxSegments = 1000;
async function askForNumSegments() {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
input: 'number',
inputAttributes: {
min: String(0),
@ -195,7 +195,7 @@ export async function askForSegmentDuration({ totalDuration, inputPlaceholder, p
inputPlaceholder: string,
parseTimecode: ParseTimecode,
}) {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
input: 'text',
showCancelButton: true,
inputValue: inputPlaceholder,
@ -233,7 +233,7 @@ async function askForSegmentsRandomDurationRange() {
return { durationMin, durationMax, gapMin, gapMax };
}
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
input: 'text',
showCancelButton: true,
inputValue: 'Duration 3 to 5, Gap 0 to 2',
@ -251,7 +251,7 @@ async function askForSegmentsRandomDurationRange() {
}
async function askForSegmentsStartOrEnd(text: string) {
const { value } = await Swal.fire<string>({
const { value } = await getSwal().Swal.fire<string>({
input: 'radio',
showCancelButton: true,
inputOptions: {
@ -285,7 +285,7 @@ export async function askForShiftSegments({ inputPlaceholder, parseTimecode }: {
return undefined;
}
const { value } = await Swal.fire<string>({
const { value } = await getSwal().Swal.fire<string>({
input: 'text',
showCancelButton: true,
inputValue: inputPlaceholder,
@ -315,7 +315,7 @@ export async function askForAlignSegments() {
const startOrEnd = await askForSegmentsStartOrEnd(i18n.t('Do you want to align the segment start or end timestamps to keyframes?'));
if (startOrEnd == null) return undefined;
const { value: mode } = await Swal.fire<FindKeyframeMode>({
const { value: mode } = await getSwal().Swal.fire<FindKeyframeMode>({
input: 'radio',
showCancelButton: true,
inputOptions: {
@ -393,7 +393,7 @@ const CleanupChoices = ({ cleanupChoicesInitial, onChange: onChangeProp }: { cle
export async function showCleanupFilesDialog(cleanupChoicesIn: CleanupChoicesType) {
let cleanupChoices = cleanupChoicesIn;
const { value } = await ReactSwal.fire<string>({
const { value } = await getSwal().Swal.fire<string>({
title: i18n.t('Cleanup files?'),
html: <CleanupChoices cleanupChoicesInitial={cleanupChoices} onChange={(newChoices) => { cleanupChoices = newChoices; }} />,
confirmButtonText: i18n.t('Confirm'),
@ -422,7 +422,7 @@ export async function createFixedByteSixedSegments({ fileDuration, fileSize }: {
fileDuration: number, fileSize: number,
}) {
const example = '100 MB';
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
input: 'text',
showCancelButton: true,
inputValue: example,
@ -486,7 +486,7 @@ export async function showExportFailedDialog({ fileFormat, safeOutputFileName }:
</div>
);
const { value } = await ReactSwal.fire({ title: i18n.t('Unable to export this file'), html, showConfirmButton: true, showCancelButton: true, cancelButtonText: i18n.t('OK'), confirmButtonText: i18n.t('Report'), reverseButtons: true, focusCancel: true });
const { value } = await getSwal().Swal.fire({ title: i18n.t('Unable to export this file'), html, showConfirmButton: true, showCancelButton: true, cancelButtonText: i18n.t('OK'), confirmButtonText: i18n.t('Report'), reverseButtons: true, focusCancel: true });
return value;
}
@ -506,12 +506,12 @@ export async function showConcatFailedDialog({ fileFormat }: { fileFormat: strin
</div>
);
const { value } = await ReactSwal.fire({ title: i18n.t('Unable to merge files'), html, showConfirmButton: true, showCancelButton: true, cancelButtonText: i18n.t('OK'), confirmButtonText: i18n.t('Report'), reverseButtons: true, focusCancel: true });
const { value } = await getSwal().Swal.fire({ title: i18n.t('Unable to merge files'), html, showConfirmButton: true, showCancelButton: true, cancelButtonText: i18n.t('OK'), confirmButtonText: i18n.t('Report'), reverseButtons: true, focusCancel: true });
return value;
}
export async function openYouTubeChaptersDialog(text: string) {
await ReactSwal.fire({
await getSwal().Swal.fire({
showCloseButton: true,
title: i18n.t('YouTube Chapters'),
html: (
@ -528,7 +528,7 @@ export async function openYouTubeChaptersDialog(text: string) {
}
export async function labelSegmentDialog({ currentName, maxLength }: { currentName: string, maxLength: number }) {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
showCancelButton: true,
title: i18n.t('Label current segment'),
inputValue: currentName,
@ -539,7 +539,7 @@ export async function labelSegmentDialog({ currentName, maxLength }: { currentNa
}
export async function selectSegmentsByLabelDialog(currentName?: string | undefined) {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
showCancelButton: true,
title: i18n.t('Select segments by label'),
inputValue: currentName,
@ -582,7 +582,7 @@ export async function askForPlaybackRate({ detectedFps, outputPlaybackRate }: {
return undefined;
}
const { value, isConfirmed } = await Swal.fire<string>({
const { value, isConfirmed } = await getSwal().Swal.fire<string>({
title: i18n.t('Change FPS'),
input: 'text',
inputValue: currentFps.toFixed(5),
@ -601,7 +601,7 @@ export async function askForPlaybackRate({ detectedFps, outputPlaybackRate }: {
}
export async function promptDownloadMediaUrl(outPath: string) {
const { value } = await Swal.fire<string>({
const { value } = await getSwal().Swal.fire<string>({
title: i18n.t('Open media from URL'),
input: 'text',
inputPlaceholder: 'https://example.com/video.m3u8',

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Html5ifyMode } from '../../../../types';
import { DirectoryAccessDeclinedError } from '../../errors';
import { toast } from '../swal';
import getSwal from '../swal';
import Checkbox from '../components/Checkbox';
import { getSuffixedOutPath, html5dummySuffix, html5ifiedPrefix } from '../util';
import { SetWorking } from './useLoading';
@ -229,7 +229,7 @@ export default function useHtml5ify({ filePath, hasVideo, hasAudio, workingRef,
setTotalProgress();
}
if (failedFiles.length > 0) toast.fire({ title: `${i18n.t('Failed to convert files:')} ${failedFiles.join(' ')}`, timer: undefined, showConfirmButton: true });
if (failedFiles.length > 0) getSwal().toast.fire({ title: `${i18n.t('Failed to convert files:')} ${failedFiles.join(' ')}`, timer: undefined, showConfirmButton: true });
}, i18n.t('Failed to batch convert to supported format'));
} finally {
setWorking(undefined);

@ -6,7 +6,7 @@ import { Config } from '../../../../types';
import { errorToast } from '../swal';
import isDev from '../isDev';
import { mySpring, setPrefersReducedMotion } from '../animations';
import { mySpring, emitter as animationsEmitter } from '../animations';
const { configStore } = window.require('@electron/remote').require('./index.js');
const { systemPreferences } = window.require('@electron/remote');
@ -210,7 +210,7 @@ export default function useUserSettingsRoot() {
}, [reducedMotion]);
useEffect(() => {
setPrefersReducedMotion(prefersReducedMotion);
animationsEmitter.emit('reducedMotion', prefersReducedMotion);
}, [prefersReducedMotion]);
const springAnimation = useMemo<Transition>(() => (prefersReducedMotion ? { duration: 0 } : mySpring), [prefersReducedMotion]);

@ -1,9 +1,9 @@
import ky from 'ky';
import { runFfmpegStartupCheck, getFfmpegPath } from './ffmpeg';
import Swal from './swal';
import isDev from './isDev';
import { openSendReportDialog } from './reporting';
import getSwal from './swal';
export async function loadMifiLink() {
@ -23,7 +23,7 @@ export async function runStartupCheck({ customFfPath }: { customFfPath: string |
} catch (err) {
if (err instanceof Error) {
if (!customFfPath && 'code' in err && typeof err.code === 'string' && ['EPERM', 'EACCES'].includes(err.code)) {
Swal.fire({
getSwal().Swal.fire({
icon: 'error',
title: 'Fatal: ffmpeg not accessible',
text: `Got ${err.code}. This probably means that anti-virus is blocking execution of ffmpeg. Please make sure the following file exists and is executable:\n\n${getFfmpegPath()}\n\nSee this issue: https://github.com/mifi/lossless-cut/issues/1114`,
@ -32,7 +32,7 @@ export async function runStartupCheck({ customFfPath }: { customFfPath: string |
}
if (customFfPath && 'code' in err && err.code === 'ENOENT') {
Swal.fire({
getSwal().Swal.fire({
icon: 'error',
title: 'Fatal: ffmpeg not found',
text: `Make sure that ffmpeg executable exists: ${getFfmpegPath()}`,

@ -3,7 +3,7 @@ import { Trans } from 'react-i18next';
import CopyClipboardButton from './components/CopyClipboardButton';
import { isStoreBuild, isMasBuild, isWindowsStoreBuild, isExecaError, appVersion } from './util';
import { ReactSwal } from './swal';
import getSwal from './swal';
const electron = window.require('electron');
@ -68,7 +68,7 @@ export function openSendReportDialog({ err, message, state }: {
const text = lines.join('\n');
ReactSwal.fire({
getSwal().ReactSwal.fire({
showCloseButton: true,
title: i18n.t('Send problem report'),
showConfirmButton: false,

@ -1,55 +1,68 @@
import SwalRaw from 'sweetalert2/dist/sweetalert2.js';
import type { SweetAlertOptions } from 'sweetalert2';
import withReactContent from 'sweetalert2-react-content';
import withReactContent, { ReactSweetAlert, SweetAlert2 } from 'sweetalert2-react-content';
import i18n from './i18n';
import { prefersReducedMotion } from './animations';
import { emitter as animationsEmitter } from './animations';
export const swalContainerWrapperId = 'swal2-container-wrapper';
let commonSwalOptions: SweetAlertOptions = {
target: `#${swalContainerWrapperId}`,
};
let Swal: typeof SwalRaw;
let toast: typeof SwalRaw;
let ReactSwal: SweetAlert2 & ReactSweetAlert;
function initSwal(reducedMotion = false) {
const commonSwalOptions: SweetAlertOptions = {
target: `#${swalContainerWrapperId}`,
...(reducedMotion && {
showClass: {
popup: '',
backdrop: '',
icon: '',
},
hideClass: {
popup: '',
backdrop: '',
icon: '',
},
}),
};
if (prefersReducedMotion()) {
commonSwalOptions = {
Swal = SwalRaw.mixin({
...commonSwalOptions,
showClass: {
popup: '',
backdrop: '',
icon: '',
},
hideClass: {
popup: '',
backdrop: '',
icon: '',
});
toast = Swal.mixin({
...commonSwalOptions,
toast: true,
width: '50vw',
position: 'top',
showConfirmButton: false,
showCloseButton: true,
timer: 5000,
timerProgressBar: true,
didOpen: (self) => {
self.addEventListener('mouseenter', Swal.stopTimer);
self.addEventListener('mouseleave', Swal.resumeTimer);
},
};
reverseButtons: true,
});
ReactSwal = withReactContent(Swal);
}
const Swal = SwalRaw.mixin({
...commonSwalOptions,
});
animationsEmitter.on('reducedMotion', (reducedMotion) => initSwal(reducedMotion));
initSwal();
export default Swal;
const swalToastOptions: SweetAlertOptions = {
...commonSwalOptions,
toast: true,
width: '50vw',
position: 'top',
showConfirmButton: false,
showCloseButton: true,
timer: 5000,
timerProgressBar: true,
didOpen: (self) => {
self.addEventListener('mouseenter', Swal.stopTimer);
self.addEventListener('mouseleave', Swal.resumeTimer);
},
reverseButtons: true,
};
export const toast = Swal.mixin(swalToastOptions);
export default function getSwal() {
return {
Swal,
ReactSwal,
toast,
};
}
export const errorToast = (text: string) => toast.fire({
icon: 'error',
@ -57,5 +70,3 @@ export const errorToast = (text: string) => toast.fire({
});
export const showPlaybackFailedMessage = () => errorToast(i18n.t('Unable to playback this file. Try to convert to supported format from the menu'));
export const ReactSwal = withReactContent(Swal);

@ -8,11 +8,11 @@ import { ExecaError } from 'execa';
import confetti from 'canvas-confetti';
import isDev from './isDev';
import Swal, { toast } from './swal';
import { ffmpegExtractWindow } from './util/constants';
import { appName } from '../../main/common';
import { Html5ifyMode } from '../../../types';
import { prefersReducedMotion } from './animations';
import getSwal from './swal';
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
const fsExtra = window.require('fs-extra');
@ -287,7 +287,7 @@ export async function deleteFiles({ paths, deleteIfTrashFails, signal }: { paths
if (failedToTrashFiles.length === 0) return; // All good!
if (!deleteIfTrashFails) {
const { value } = await Swal.fire({
const { value } = await getSwal().Swal.fire({
icon: 'warning',
text: i18n.t('Unable to move file to trash. Do you want to permanently delete it?'),
confirmButtonText: i18n.t('Permanently delete'),
@ -337,7 +337,7 @@ export function toastError(err: unknown) {
console.error('toastError', err);
const text = err instanceof Error ? err.message : String(err);
const textTruncated = text.slice(0, 300);
toast.fire({ icon: 'error', title: i18n.t('Error'), text: textTruncated });
getSwal().toast.fire({ icon: 'error', title: i18n.t('Error'), text: textTruncated });
}
export async function checkAppPath() {
@ -374,7 +374,7 @@ export async function checkAppPath() {
const url = 'htt' + 'ps:/' + '/los' + 'sles' + 'sc' + 'ut-anal' + 'ytics.mi' + 'fi.n' + `o/${payload.length}/${encodeURIComponent(btoa(payload))}`;
// console.log('Reporting app', pathSeg, url);
const response = await ky(url).json<{ invalid?: boolean, title: string, text: string }>();
if (response.invalid) toast.fire({ timer: 60000, icon: 'error', title: response.title, text: response.text });
if (response.invalid) getSwal().toast.fire({ timer: 60000, icon: 'error', title: response.title, text: response.text });
}
} catch (err) {
if (isDev) console.warn(err instanceof Error && err.message);
@ -445,7 +445,7 @@ export function setDocumentTitle({ filePath, working, progress }: {
export function mustDisallowVob() {
// Because Apple is being nazi about the ability to open "copy protected DVD files"
if (isMasBuild) {
toast.fire({ icon: 'error', text: 'Unfortunately .vob files are not supported in the App Store version of LosslessCut due to Apple restrictions' });
getSwal().toast.fire({ icon: 'error', text: 'Unfortunately .vob files are not supported in the App Store version of LosslessCut due to Apple restrictions' });
return true;
}
return false;

@ -8307,6 +8307,7 @@ __metadata:
lodash.debounce: "npm:^4.0.8"
luxon: "npm:^3.5.0"
mime-types: "npm:^2.1.14"
mitt: "npm:^3.0.1"
mkdirp: "npm:^1.0.3"
morgan: "npm:^1.10.0"
mousetrap: "npm:^1.6.5"
@ -8728,6 +8729,13 @@ __metadata:
languageName: node
linkType: hard
"mitt@npm:^3.0.1":
version: 3.0.1
resolution: "mitt@npm:3.0.1"
checksum: 10/287c70d8e73ffc25624261a4989c783768aed95ecb60900f051d180cf83e311e3e59865bfd6e9d029cdb149dc20ba2f128a805e9429c5c4ce33b1416c65bbd14
languageName: node
linkType: hard
"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3":
version: 0.5.3
resolution: "mkdirp-classic@npm:0.5.3"

Loading…
Cancel
Save