signal-desktop/ts/components/PreferencesInternal.tsx
2025-05-19 16:32:06 -07:00

166 lines
4.8 KiB
TypeScript

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState, useCallback } from 'react';
import type { LocalizerType } from '../types/I18N';
import { toLogFormat } from '../types/errors';
import { formatFileSize } from '../util/formatFileSize';
import { SECOND } from '../util/durations';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
import { SettingsRow, SettingsControl } from './PreferencesUtil';
import { Button, ButtonVariant } from './Button';
import { Spinner } from './Spinner';
export function PreferencesInternal({
i18n,
exportLocalBackup: doExportLocalBackup,
validateBackup: doValidateBackup,
}: {
i18n: LocalizerType;
exportLocalBackup: () => Promise<BackupValidationResultType>;
validateBackup: () => Promise<BackupValidationResultType>;
}): JSX.Element {
const [isExportPending, setIsExportPending] = useState(false);
const [exportResult, setExportResult] = useState<
BackupValidationResultType | undefined
>();
const [isValidationPending, setIsValidationPending] = useState(false);
const [validationResult, setValidationResult] = useState<
BackupValidationResultType | undefined
>();
const validateBackup = useCallback(async () => {
setIsValidationPending(true);
setValidationResult(undefined);
try {
setValidationResult(await doValidateBackup());
} catch (error) {
setValidationResult({ error: toLogFormat(error) });
} finally {
setIsValidationPending(false);
}
}, [doValidateBackup]);
const renderValidationResult = useCallback(
(
backupResult: BackupValidationResultType | undefined
): JSX.Element | undefined => {
if (backupResult == null) {
return;
}
if ('result' in backupResult) {
const {
result: { totalBytes, stats, duration },
} = backupResult;
let snapshotDirEl: JSX.Element | undefined;
if ('snapshotDir' in backupResult.result) {
snapshotDirEl = (
<p>
Backup path:
<pre>
<code>{backupResult.result.snapshotDir}</code>
</pre>
</p>
);
}
return (
<div className="Preferences--internal--validate-backup--result">
{snapshotDirEl}
<p>Main file size: {formatFileSize(totalBytes)}</p>
<p>Duration: {Math.round(duration / SECOND)}s</p>
<pre>
<code>{JSON.stringify(stats, null, 2)}</code>
</pre>
</div>
);
}
const { error } = backupResult;
return (
<div className="Preferences--internal--validate-backup--error">
<pre>
<code>{error}</code>
</pre>
</div>
);
},
[]
);
const exportLocalBackup = useCallback(async () => {
setIsExportPending(true);
setExportResult(undefined);
try {
setExportResult(await doExportLocalBackup());
} catch (error) {
setExportResult({ error: toLogFormat(error) });
} finally {
setIsExportPending(false);
}
}, [doExportLocalBackup]);
return (
<>
<div className="Preferences__title Preferences__title--internal">
<div className="Preferences__title--header">
{i18n('icu:Preferences__button--internal')}
</div>
</div>
<SettingsRow
className="Preferences--internal--backups"
title={i18n('icu:Preferences__button--backups')}
>
<SettingsControl
left={i18n('icu:Preferences__internal__validate-backup--description')}
right={
<Button
variant={ButtonVariant.Secondary}
onClick={validateBackup}
disabled={isValidationPending}
>
{isValidationPending ? (
<Spinner size="22px" svgSize="small" />
) : (
i18n('icu:Preferences__internal__validate-backup')
)}
</Button>
}
/>
{renderValidationResult(validationResult)}
</SettingsRow>
<SettingsRow
className="Preferences--internal--backups"
title={i18n('icu:Preferences__internal__local-backups')}
>
<SettingsControl
left={i18n(
'icu:Preferences__internal__export-local-backup--description'
)}
right={
<Button
variant={ButtonVariant.Secondary}
onClick={exportLocalBackup}
disabled={isExportPending}
>
{isExportPending ? (
<Spinner size="22px" svgSize="small" />
) : (
i18n('icu:Preferences__internal__export-local-backup')
)}
</Button>
}
/>
{renderValidationResult(exportResult)}
</SettingsRow>
</>
);
}