feat: Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. (#2322)

This commit is contained in:
Mo
2023-05-02 11:05:10 -05:00
committed by GitHub
parent 3df23cdb5c
commit 7e3db49322
76 changed files with 1526 additions and 1013 deletions

View File

@@ -58,7 +58,7 @@ const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) =
}
const processEmailChange = useCallback(async () => {
await application.downloadBackup()
await application.performDesktopTextBackup()
setLockContinue(true)

View File

@@ -6,6 +6,8 @@ import DataBackups from './DataBackups'
import EmailBackups from './EmailBackups'
import FileBackupsCrossPlatform from './Files/FileBackupsCrossPlatform'
import { observer } from 'mobx-react-lite'
import TextBackupsCrossPlatform from './TextBackups/TextBackupsCrossPlatform'
import PlaintextBackupsCrossPlatform from './PlaintextBackups/PlaintextBackupsCrossPlatform'
type Props = {
viewControllerManager: ViewControllerManager
@@ -16,6 +18,8 @@ const Backups: FunctionComponent<Props> = ({ application, viewControllerManager
return (
<PreferencesPane>
<DataBackups application={application} viewControllerManager={viewControllerManager} />
<TextBackupsCrossPlatform application={application} />
<PlaintextBackupsCrossPlatform />
<FileBackupsCrossPlatform application={application} />
<EmailBackups application={application} />
</PreferencesPane>

View File

@@ -1,4 +1,3 @@
import { isDesktopApplication } from '@/Utils'
import { alertDialog, sanitizeFileName } from '@standardnotes/ui-services'
import {
STRING_IMPORT_SUCCESS,
@@ -15,7 +14,7 @@ import { ChangeEventHandler, MouseEventHandler, useCallback, useEffect, useRef,
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { Title, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
@@ -177,14 +176,7 @@ const DataBackups = ({ application, viewControllerManager }: Props) => {
<PreferencesGroup>
<PreferencesSegment>
<Title>Data Backups</Title>
{isDesktopApplication() && (
<Text className="mb-3">
Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
</Text>
)}
<Subtitle>Download a backup of all your data</Subtitle>
<Subtitle>Download a backup of all your text-based data</Subtitle>
{isEncryptionEnabled && (
<form className="sk-panel-form sk-panel-row">

View File

@@ -118,7 +118,7 @@ const EmailBackups = ({ application }: Props) => {
)}
<div className={`${!hasAccount ? 'pointer-events-none cursor-default opacity-50' : ''}`}>
<Subtitle>Email frequency</Subtitle>
<Subtitle>Frequency</Subtitle>
<Text>How often to receive backups.</Text>
<div className="mt-2">
{isLoading ? (

View File

@@ -15,13 +15,13 @@ const FileBackupsCrossPlatform = ({ application }: Props) => {
const fileBackupsService = useMemo(() => application.fileBackups, [application])
return fileBackupsService ? (
<FileBackupsDesktop application={application} backupsService={fileBackupsService} />
<FileBackupsDesktop backupsService={fileBackupsService} />
) : (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>File Backups</Title>
<Subtitle>Automatically save encrypted backups of files uploaded on any device to this computer.</Subtitle>
<Title>Automatic File Backups</Title>
<Subtitle>Automatically save encrypted backups of your files.</Subtitle>
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />

View File

@@ -1,7 +1,6 @@
import { WebApplication } from '@/Application/Application'
import { observer } from 'mobx-react-lite'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useState } from 'react'
import Button from '@/Components/Button/Button'
import Switch from '@/Components/Switch/Switch'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
@@ -10,30 +9,21 @@ import BackupsDropZone from './BackupsDropZone'
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { BackupServiceInterface } from '@standardnotes/snjs'
import { useApplication } from '@/Components/ApplicationProvider'
type Props = {
application: WebApplication
backupsService: NonNullable<WebApplication['fileBackups']>
backupsService: BackupServiceInterface
}
const FileBackupsDesktop = ({ application, backupsService }: Props) => {
const [backupsEnabled, setBackupsEnabled] = useState(false)
const [backupsLocation, setBackupsLocation] = useState('')
useEffect(() => {
void backupsService.isFilesBackupsEnabled().then(setBackupsEnabled)
}, [backupsService])
useEffect(() => {
if (backupsEnabled) {
void backupsService.getFilesBackupsLocation().then(setBackupsLocation)
}
}, [backupsService, backupsEnabled])
const FileBackupsDesktop = ({ backupsService }: Props) => {
const application = useApplication()
const [backupsEnabled, setBackupsEnabled] = useState(backupsService.isFilesBackupsEnabled())
const [backupsLocation, setBackupsLocation] = useState(backupsService.getFilesBackupsLocation())
const changeBackupsLocation = useCallback(async () => {
await backupsService.changeFilesBackupsLocation()
setBackupsLocation(await backupsService.getFilesBackupsLocation())
const newLocation = await backupsService.changeFilesBackupsLocation()
setBackupsLocation(newLocation)
}, [backupsService])
const openBackupsLocation = useCallback(async () => {
@@ -42,25 +32,24 @@ const FileBackupsDesktop = ({ application, backupsService }: Props) => {
const toggleBackups = useCallback(async () => {
if (backupsEnabled) {
await backupsService.disableFilesBackups()
backupsService.disableFilesBackups()
} else {
await backupsService.enableFilesBackups()
}
setBackupsEnabled(await backupsService.isFilesBackupsEnabled())
setBackupsEnabled(backupsService.isFilesBackupsEnabled())
setBackupsLocation(backupsService.getFilesBackupsLocation())
}, [backupsService, backupsEnabled])
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>File Backups</Title>
<Title>Automatic File Backups</Title>
<div className="flex items-center justify-between">
<div className="mr-10 flex flex-col">
<Subtitle>
Automatically save encrypted backups of files uploaded on any device to this computer.
</Subtitle>
<Subtitle>Automatically save encrypted backups of your uploaded files to this computer.</Subtitle>
</div>
<Switch onChange={toggleBackups} checked={backupsEnabled} />
</div>
@@ -85,14 +74,14 @@ const FileBackupsDesktop = ({ application, backupsService }: Props) => {
</Text>
<EncryptionStatusItem
status={backupsLocation}
status={backupsLocation || 'Not Set'}
icon={<Icon type="attachment-file" className="min-h-5 min-w-5" />}
checkmark={false}
/>
<div className="mt-2.5 flex flex-row">
<Button label="Open Backups Location" className={'mr-3 text-xs'} onClick={openBackupsLocation} />
<Button label="Change Backups Location" className={'mr-3 text-xs'} onClick={changeBackupsLocation} />
<Button label="Open Location" className={'mr-3 text-xs'} onClick={openBackupsLocation} />
<Button label="Change Location" className={'mr-3 text-xs'} onClick={changeBackupsLocation} />
</div>
</>
</PreferencesSegment>

View File

@@ -0,0 +1,28 @@
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { useMemo } from 'react'
import PlaintextBackupsDesktop from './PlaintextBackupsDesktop'
import { useApplication } from '@/Components/ApplicationProvider'
const PlaintextBackupsCrossPlatform = () => {
const application = useApplication()
const fileBackupsService = useMemo(() => application.fileBackups, [application])
return fileBackupsService ? (
<PlaintextBackupsDesktop backupsService={fileBackupsService} />
) : (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>Automatic Plaintext Backups</Title>
<Subtitle>Automatically save backups of all your notes into plaintext, non-encrypted folders.</Subtitle>
<Text className="mt-3">To enable plaintext backups, use the Standard Notes desktop application.</Text>
</PreferencesSegment>
</PreferencesGroup>
</>
)
}
export default PlaintextBackupsCrossPlatform

View File

@@ -0,0 +1,89 @@
import { observer } from 'mobx-react-lite'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { useCallback, useState } from 'react'
import Button from '@/Components/Button/Button'
import Switch from '@/Components/Switch/Switch'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Icon from '@/Components/Icon/Icon'
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { BackupServiceInterface } from '@standardnotes/snjs'
type Props = {
backupsService: BackupServiceInterface
}
const PlaintextBackupsDesktop = ({ backupsService }: Props) => {
const [backupsEnabled, setBackupsEnabled] = useState(backupsService.isPlaintextBackupsEnabled())
const [backupsLocation, setBackupsLocation] = useState(backupsService.getPlaintextBackupsLocation())
const changeBackupsLocation = useCallback(async () => {
const newLocation = await backupsService.changePlaintextBackupsLocation()
setBackupsLocation(newLocation)
}, [backupsService])
const openBackupsLocation = useCallback(async () => {
await backupsService.openPlaintextBackupsLocation()
}, [backupsService])
const toggleBackups = useCallback(async () => {
if (backupsEnabled) {
backupsService.disablePlaintextBackups()
} else {
await backupsService.enablePlaintextBackups()
}
setBackupsEnabled(backupsService.isPlaintextBackupsEnabled())
setBackupsLocation(backupsService.getPlaintextBackupsLocation())
}, [backupsEnabled, backupsService])
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>Automatic Plaintext Backups</Title>
<div className="flex items-center justify-between">
<div className="mr-10 flex flex-col">
<Subtitle>
Automatically save backups of all your notes to this computer into plaintext, non-encrypted folders.
</Subtitle>
</div>
<Switch onChange={toggleBackups} checked={backupsEnabled} />
</div>
{!backupsEnabled && (
<>
<HorizontalSeparator classes="mt-2.5 mb-4" />
<Text>Plaintext backups are not enabled. Enable to choose where your data is backed up.</Text>
</>
)}
</PreferencesSegment>
{backupsEnabled && (
<>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<>
<Text className="mb-3">Plaintext backups are enabled and saved to:</Text>
<EncryptionStatusItem
status={backupsLocation || 'Not Set'}
icon={<Icon type="attachment-file" className="min-h-5 min-w-5" />}
checkmark={false}
/>
<div className="mt-2.5 flex flex-row">
<Button label="Open Location" className={'mr-3 text-xs'} onClick={openBackupsLocation} />
<Button label="Change Location" className={'mr-3 text-xs'} onClick={changeBackupsLocation} />
</div>
</>
</PreferencesSegment>
</>
)}
</PreferencesGroup>
</>
)
}
export default observer(PlaintextBackupsDesktop)

View File

@@ -0,0 +1,30 @@
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { WebApplication } from '@/Application/Application'
import { useMemo } from 'react'
import TextBackupsDesktop from './TextBackupsDesktop'
type Props = {
application: WebApplication
}
const TextBackupsCrossPlatform = ({ application }: Props) => {
const fileBackupsService = useMemo(() => application.fileBackups, [application])
return fileBackupsService ? (
<TextBackupsDesktop backupsService={fileBackupsService} />
) : (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>Automatic Text Backups</Title>
<Subtitle>Automatically save encrypted and decrypted backups of your note and tag data.</Subtitle>
<Text className="mt-3">To enable text backups, use the Standard Notes desktop application.</Text>
</PreferencesSegment>
</PreferencesGroup>
</>
)
}
export default TextBackupsCrossPlatform

View File

@@ -0,0 +1,106 @@
import { observer } from 'mobx-react-lite'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { useCallback, useState } from 'react'
import Button from '@/Components/Button/Button'
import Switch from '@/Components/Switch/Switch'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Icon from '@/Components/Icon/Icon'
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { BackupServiceInterface } from '@standardnotes/snjs'
import { useApplication } from '@/Components/ApplicationProvider'
type Props = {
backupsService: BackupServiceInterface
}
const TextBackupsDesktop = ({ backupsService }: Props) => {
const application = useApplication()
const [backupsEnabled, setBackupsEnabled] = useState(backupsService.isTextBackupsEnabled())
const [backupsLocation, setBackupsLocation] = useState(backupsService.getTextBackupsLocation())
const changeBackupsLocation = useCallback(async () => {
const newLocation = await backupsService.changeTextBackupsLocation()
setBackupsLocation(newLocation)
}, [backupsService])
const openBackupsLocation = useCallback(async () => {
await backupsService.openTextBackupsLocation()
}, [backupsService])
const toggleBackups = useCallback(async () => {
if (backupsEnabled) {
backupsService.disableTextBackups()
} else {
await backupsService.enableTextBackups()
}
setBackupsEnabled(backupsService.isTextBackupsEnabled())
setBackupsLocation(backupsService.getTextBackupsLocation())
}, [backupsEnabled, backupsService])
const performBackup = useCallback(async () => {
void application.getDesktopService()?.saveDesktopBackup()
}, [application])
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>Automatic Encrypted Text Backups</Title>
<div className="flex items-center justify-between">
<div className="mr-10 flex flex-col">
<Subtitle>
Automatically save encrypted text backups of all your note and tag data to this computer.
</Subtitle>
</div>
<Switch onChange={toggleBackups} checked={backupsEnabled} />
</div>
{!backupsEnabled && (
<>
<HorizontalSeparator classes="mt-2.5 mb-4" />
<Text>Text backups are not enabled. Enable to choose where your data is backed up.</Text>
</>
)}
</PreferencesSegment>
{backupsEnabled && (
<>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<>
<Text className="mb-3">Text backups are enabled and saved to:</Text>
<EncryptionStatusItem
status={backupsLocation || 'Not Set'}
icon={<Icon type="attachment-file" className="min-h-5 min-w-5" />}
checkmark={false}
/>
<div className="mt-2.5 flex flex-row">
<Button label="Open Location" className={'mr-3 text-xs'} onClick={openBackupsLocation} />
<Button label="Change Location" className={'mr-3 text-xs'} onClick={changeBackupsLocation} />
</div>
</>
<HorizontalSeparator classes="my-4" />
<Text className="mb-3">
Backups are saved automatically throughout the day. You can perform a one-time backup now below.
</Text>
<div className="flex flex-row">
<Button label="Perform Backup" className={'mr-3 text-xs'} onClick={performBackup} />
</div>
</PreferencesSegment>
</>
)}
</PreferencesGroup>
</>
)
}
export default observer(TextBackupsDesktop)

View File

@@ -12,7 +12,7 @@ const TwoFactorTitle: FunctionComponent<Props> = ({ auth }) => {
return <Title>Two-factor authentication not available</Title>
}
return <Title>Two-factor authentication</Title>
return <Title>Two-Factor Authentication</Title>
}
export default observer(TwoFactorTitle)