diff --git a/src/App.jsx b/src/App.jsx index 922c53d..75065c0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -48,8 +48,9 @@ import { save as edlStoreSave, load as edlStoreLoad } from './edlStore'; import { getOutPath, formatDuration, toast, errorToast, showFfmpegFail, setFileNameTitle, promptTimeOffset, generateColor, getOutDir, withBlur, checkDirWriteAccess, dirExists, - openDirToast, askForHtml5ifySpeed, + openDirToast, askForHtml5ifySpeed, isMasBuild, isStoreBuild, } from './util'; +import { openSendReportDialog } from './reporting'; import loadingLottie from './7077-magic-flow.json'; @@ -61,7 +62,6 @@ const trash = window.require('trash'); const { unlink, exists } = window.require('fs-extra'); const { extname } = window.require('path'); - const { dialog, app } = electron.remote; const ReactSwal = withReactContent(Swal); @@ -109,9 +109,6 @@ const calcShouldShowKeyframes = (zoomedDuration) => (zoomedDuration != null && z const commonFormats = ['mov', 'mp4', 'matroska', 'mp3', 'ipod']; -const isMasBuild = window.process.mas; -const isStoreBuild = isMasBuild || window.process.windowsStore; - // TODO flex const topBarHeight = 32; const timelineHeight = 36; @@ -892,49 +889,20 @@ const App = memo(() => { const outSegments = useMemo(() => (invertCutSegments ? inverseCutSegments : apparentCutSegments), [invertCutSegments, inverseCutSegments, apparentCutSegments]); - const openSendReportDialog = useCallback(async (err) => { - const reportInstructions = isStoreBuild - ?

Please send an email to electron.shell.openExternal('mailto:losslesscut@yankee.no')}>losslesscut@yankee.no where you describe what you were doing.

- :

Please create an issue at electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>https://github.com/mifi/lossless-cut/issues where you describe what you were doing.

; + const openSendReportDialogWithState = useCallback(async (err) => { + const state = { + filePath, + fileFormat, + externalStreamFiles, + mainStreams, + copyStreamIdsByFile, + cutSegments, + fileFormatData, + rotation, + shortestFlag, + }; - ReactSwal.fire({ - showCloseButton: true, - title: i18n.t('Send problem report'), - html: ( -
- {reportInstructions} - -

Include the following text:

- -
- {`${err ? err.message : ''}\n\n${JSON.stringify({ - err: err && { - code: err.code, - killed: err.killed, - failed: err.failed, - timedOut: err.timedOut, - isCanceled: err.isCanceled, - exitCode: err.exitCode, - signal: err.signal, - signalDescription: err.signalDescription, - }, - - state: { - filePath, - fileFormat, - externalStreamFiles, - mainStreams, - copyStreamIdsByFile, - cutSegments, - fileFormatData, - rotation, - shortestFlag, - }, - }, null, 2)}`} -
-
- ), - }); + openSendReportDialog(err, state); }, [copyStreamIdsByFile, cutSegments, externalStreamFiles, fileFormat, fileFormatData, filePath, mainStreams, rotation, shortestFlag]); const handleCutFailed = useCallback(async (err) => { @@ -955,9 +923,9 @@ const App = memo(() => { const { value } = await ReactSwal.fire({ title: i18n.t('Unable to export this file'), html, timer: null, showConfirmButton: true, showCancelButton: true, confirmButtonText: i18n.t('OK'), cancelButtonText: i18n.t('Report') }); if (!value) { - openSendReportDialog(err); + openSendReportDialogWithState(err); } - }, [openSendReportDialog]); + }, [openSendReportDialogWithState]); const cutClick = useCallback(async () => { if (working) return; @@ -1519,7 +1487,7 @@ const App = memo(() => { electron.ipcRenderer.on('openSettings', openSettings); electron.ipcRenderer.on('openAbout', openAbout); electron.ipcRenderer.on('batchConvertFriendlyFormat', batchConvertFriendlyFormat); - electron.ipcRenderer.on('openSendReportDialog', openSendReportDialog); + electron.ipcRenderer.on('openSendReportDialog', openSendReportDialogWithState); return () => { electron.ipcRenderer.removeListener('file-opened', fileOpened); @@ -1536,11 +1504,11 @@ const App = memo(() => { electron.ipcRenderer.removeListener('openSettings', openSettings); electron.ipcRenderer.removeListener('openAbout', openAbout); electron.ipcRenderer.removeListener('batchConvertFriendlyFormat', batchConvertFriendlyFormat); - electron.ipcRenderer.removeListener('openSendReportDialog', openSendReportDialog); + electron.ipcRenderer.removeListener('openSendReportDialog', openSendReportDialogWithState); }; }, [ mergeFiles, outputDir, filePath, isFileOpened, customOutDir, startTimeOffset, html5ifyCurrentFile, - createDummyVideo, resetState, extractAllStreams, userOpenFiles, cutSegmentsHistory, openSendReportDialog, + createDummyVideo, resetState, extractAllStreams, userOpenFiles, cutSegmentsHistory, openSendReportDialogWithState, loadEdlFile, cutSegments, edlFilePath, askBeforeClose, toggleHelp, toggleSettings, assureOutDirAccess, html5ifyAndLoad, html5ifyInternal, ]); @@ -1673,6 +1641,8 @@ const App = memo(() => { const { t } = useTranslation(); + // throw new Error('Test'); + return (
diff --git a/src/ErrorBoundary.jsx b/src/ErrorBoundary.jsx new file mode 100644 index 0000000..d547129 --- /dev/null +++ b/src/ErrorBoundary.jsx @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import { openSendReportDialog } from './reporting'; + +class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { error: undefined }; + } + + static getDerivedStateFromError(error) { + return { error }; + } + + componentDidCatch(error, errorInfo) { + console.error('componentDidCatch', error, errorInfo); + } + + render() { + const { error } = this.state; + if (error) { + return ( +
+

Something went wrong

+
{error.message}
+

+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/index.jsx b/src/index.jsx index 36ad228..40f5bde 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,6 +1,7 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; import App from './App'; +import ErrorBoundary from './ErrorBoundary'; import './i18n'; import './fonts.css'; @@ -11,4 +12,4 @@ const electron = window.require('electron'); console.log('Version', electron.remote.app.getVersion()); -ReactDOM.render(}>, document.getElementById('root')); +ReactDOM.render(}>, document.getElementById('root')); diff --git a/src/reporting.jsx b/src/reporting.jsx new file mode 100644 index 0000000..288388b --- /dev/null +++ b/src/reporting.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import Swal from 'sweetalert2'; +import withReactContent from 'sweetalert2-react-content'; +import i18n from 'i18next'; + +import { isStoreBuild } from './util'; + +const electron = window.require('electron'); // eslint-disable-line + + +const ReactSwal = withReactContent(Swal); + +// eslint-disable-next-line import/prefer-default-export +export function openSendReportDialog(err, state) { + const reportInstructions = isStoreBuild + ?

Please send an email to electron.shell.openExternal('mailto:losslesscut@yankee.no')}>losslesscut@yankee.no where you describe what you were doing.

+ :

Please create an issue at electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>https://github.com/mifi/lossless-cut/issues where you describe what you were doing.

; + + ReactSwal.fire({ + showCloseButton: true, + title: i18n.t('Send problem report'), + html: ( +
+ {reportInstructions} + +

Include the following text:

+ +
+ {`${err ? err.stack : ''}\n\n${JSON.stringify({ + err: err && { + code: err.code, + killed: err.killed, + failed: err.failed, + timedOut: err.timedOut, + isCanceled: err.isCanceled, + exitCode: err.exitCode, + signal: err.signal, + signalDescription: err.signalDescription, + }, + + state, + }, null, 2)}`} +
+
+ ), + }); +} diff --git a/src/util.js b/src/util.js index f7aec82..2a92116 100644 --- a/src/util.js +++ b/src/util.js @@ -187,3 +187,6 @@ export async function askForHtml5ifySpeed(allowedOptions) { return value; } + +export const isMasBuild = window.process.mas; +export const isStoreBuild = isMasBuild || window.process.windowsStore;