import { CheckIcon } from '@radix-ui/react-icons'; import { GetBackups, GetLastLogs, SendCrashReport, } from '@wailsapp/go/main/App'; import type { main } from '@wailsapp/go/models'; import { EventsOff, EventsOn } from '@wailsapp/runtime'; import { Fragment, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { languages } from '~/locale/languages'; import { ProcessedLogEntry, processEntry } from '~/store/logging/reducer'; import DialogContent from '~/ui/components/DialogContent'; import { LogItem } from '~/ui/components/LogViewer'; import Scrollbar from '~/ui/components/utils/Scrollbar'; import { Button, Checkbox, CheckboxIndicator, Dialog, DialogActions, Field, FlexRow, InputBox, Label, MultiToggle, MultiToggleItem, PageContainer, PageHeader, SectionHeader, styled, Textarea, TextBlock, } from '~/ui/theme'; import AlertContent from './components/AlertContent'; import { Alert, AlertDescription } from './theme/alert'; const Container = styled('div', { position: 'relative', display: 'flex', flexDirection: 'row', overflow: 'hidden', height: '100vh', border: '2px solid $red10', }); const ErrorHeader = styled('h1', { color: '$red10', textTransform: 'capitalize', }); const ErrorDetails = styled('dl', { display: 'grid', gridTemplateColumns: '100px 1fr', margin: '0', }); const ErrorDetailKey = styled('dt', { fontWeight: 'bold', textTransform: 'capitalize', gridColumn: '1', }); const ErrorDetailValue = styled('dd', { padding: '0', margin: '0', marginBottom: '0.5rem', gridColumn: '2', }); const LogContainer = styled('div', { display: 'flex', flexDirection: 'column', gap: '3px', }); const Mono = styled('code', { background: '$gray5', padding: '3px 5px', borderRadius: '3px', whiteSpace: 'nowrap', }); const MiniHeader = styled(SectionHeader, { fontSize: '14pt', }); const LanguageSelector = styled('div', { top: '10px', right: '10px', display: 'flex', gap: '1rem', position: 'absolute', zIndex: '1', }); const LanguageItem = styled(MultiToggleItem, { fontSize: '8pt', padding: '5px 6px 4px', textTransform: 'uppercase', }); const BackupItem = styled('article', { marginBottom: '0.4rem', backgroundColor: '$gray2', margin: '0.5rem 0', padding: '0.3rem 0.5rem', borderLeft: '5px solid $teal8', borderRadius: '0.25rem', borderBottom: '1px solid $gray4', transition: 'all 50ms', display: 'flex', }); const BackupDate = styled('div', { flex: '1', display: 'flex', gap: '0.5rem', alignItems: 'baseline', }); const BackupActions = styled('div', { display: 'flex', alignItems: 'center', gap: '0.25rem', }); interface RecoveryDialogProps { open: boolean; onOpenChange: (state: boolean) => void; } function RecoveryDialog({ open, onOpenChange }: RecoveryDialogProps) { const { t } = useTranslation(); const [backups, setBackups] = useState([]); useEffect(() => { GetBackups().then((backupList) => { setBackups(backupList); console.log(backupList); }); }, []); return ( { if (onOpenChange) onOpenChange(state); }} > These action will irreversibly modify your database, please make sure your database is corrupted in the first place before proceeding. Restore from backup Restore a previously backed up database. This will overwrite your current database with the saved copy. Check below for the list of saved copies. {backups .sort((a, b) => b.date - a.date) .map((backup) => ( {new Date(backup.date).toLocaleString()} ))} ); } interface ReportDialogProps { open: boolean; onOpenChange: (state: boolean) => void; errorData?: ProcessedLogEntry; } function ReportDialog({ open, onOpenChange, errorData }: ReportDialogProps) { const { t } = useTranslation(); const [errorDesc, setErrorDesc] = useState(''); const [contactEnabled, setContactEnabled] = useState(false); const [contactInfo, setContactInfo] = useState(''); const [submitted, setSubmitted] = useState(false); const [code, setCode] = useState(''); const [submissionError, setSubmissionError] = useState(null); const waiting = submitted && code.length < 1; if (code) { return ( { if (onOpenChange) onOpenChange(state); }} > { setSubmissionError(null); }} > ), }} /> ); } return ( <> { if (!val) setSubmissionError(null); }} > { setSubmissionError(null); }} /> { if (onOpenChange) onOpenChange(state); }} >
{ e.preventDefault(); console.log('test'); let desc = errorDesc; if (contactEnabled && contactInfo) { desc += `\n\nEmail contact: ${contactInfo}`; } SendCrashReport(JSON.stringify(errorData), desc) .then((submissionCode) => { setCode(submissionCode); }) .catch((err) => { setSubmissionError(err as Error); }); setSubmitted(true); }} > {t('pages.crash.report.thanks-line')} {t('pages.crash.report.transparency-line')}
  • , }} />
  • {t('pages.crash.report.transparency-info')}
  • {t('pages.crash.report.transparency-user')}
setContactInfo(e.target.value)} />
); } export default function ErrorWindow(): JSX.Element { const [t, i18n] = useTranslation(); const [logs, setLogs] = useState([]); const [reportDialogOpen, setReportDialogOpen] = useState(false); const [recoveryDialogOpen, setRecoveryDialogOpen] = useState(false); useEffect(() => { void GetLastLogs().then((appLogs) => { setLogs(appLogs.map(processEntry).reverse()); }); EventsOn('log-event', (event: main.LogEntry) => { setLogs([processEntry(event), ...logs]); }); return () => { EventsOff('log-event'); }; }, []); const fatal = logs.find((log) => log.level === 'error'); return ( { void i18n.changeLanguage(newLang); }} > {languages.map((lang) => ( {lang.code} ))} {t('pages.crash.fatal-message')} {fatal ? ( <> {fatal.message} {Object.keys(fatal.data) .filter((key) => key.length > 1) .map((key) => ( {key} {fatal.data[key]} ))} ) : null} {t('pages.crash.action-header')} {t('pages.crash.action-submit-line')} {t('pages.crash.action-recover-line')} , }} /> {t('pages.crash.app-log-header')} {logs.map((log) => ( ))} ); }