import { observer } from 'mobx-react-lite'; import { toDirective } from '@/components/utils'; import { AppState } from '@/ui_models/app_state'; import { WebApplication } from '@/ui_models/application'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { isDesktopApplication, isSameDay } from '@/utils'; import { storage, StorageKey } from '@Services/localStorage'; import { disableErrorReporting, enableErrorReporting, errorReportingId } from '@Services/errorReporting'; import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED, StringUtils } from '@/strings'; import { ContentType } from '@node_modules/@standardnotes/snjs'; // eslint-disable-next-line @typescript-eslint/no-empty-interface // interface Props {} // TODO: Vardan: implement props and remove `eslint-disable` type Props = { appState: AppState; application: WebApplication; }; // const HistoryMenu = observer((props: Props) => { // const AccountMenu = observer((props) => { const AccountMenu = observer(({ appState, application }: Props) => { const getProtectionsDisabledUntil = (): string | null => { const protectionExpiry = application.getProtectionSessionExpiryDate(); const now = new Date(); if (protectionExpiry > now) { let f: Intl.DateTimeFormat; if (isSameDay(protectionExpiry, now)) { f = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: 'numeric' }); } else { f = new Intl.DateTimeFormat(undefined, { weekday: 'long', day: 'numeric', month: 'short', hour: 'numeric', minute: 'numeric' }); } return f.format(protectionExpiry); } return null; }; const passcodeInput = useRef(); // TODO: implement what is missing for `passcodeInput`, e.g. - autofocus // TODO: Vardan `showLogin` and `showRegister` were in `formData` in Angular code, check whether I need to write similarly const [showLogin, setShowLogin] = useState(false); const [showRegister, setShowRegister] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); const [isAuthenticating, setIsAuthenticating] = useState(false); const [password, setPassword] = useState(undefined); const [passwordConfirmation, setPasswordConfirmation] = useState(undefined); const [status, setStatus] = useState(''); const [syncError, setSyncError] = useState(undefined); const [server, setServer] = useState(undefined); const [showPasscodeForm, setShowPasscodeForm] = useState(false); const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isErrorReportingEnabled, setIsErrorReportingEnabled] = useState(false); const [appVersion, setAppVersion] = useState(''); // TODO: Vardan: figure out how to get `appVersion` similar to original code const user = application.getUser(); const hasUser = application.hasAccount(); const hasPasscode = application.hasPasscode(); const isEncryptionEnabled = hasUser || hasPasscode; const encryptionStatusString = hasUser ? STRING_E2E_ENABLED : hasPasscode ? STRING_LOCAL_ENC_ENABLED : STRING_ENC_NOT_ENABLED; // TODO: Vardan: in original code initial value of `backupEncrypted` is `hasUser || hasPasscode` - // once I have those values here, set them as initial value const [isBackupEncrypted, setIsBackupEncrypted] = useState(isEncryptionEnabled); const [isSyncInProgress, setIsSyncInProgress] = useState(false); const reloadAutoLockInterval = useCallback(async () => { const interval = await application.getAutolockService().getAutoLockInterval(); setSelectedAutoLockInterval(interval); }, [application]); const errorReportingIdValue = errorReportingId(); const protectionsDisabledUntil = getProtectionsDisabledUntil(); const canAddPasscode = !application.isEphemeralSession(); const keyStorageInfo = StringUtils.keyStorageInfo(application); const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions(); const showBetaWarning = appState.showBetaWarning; /* const displayRegistrationForm = () => { console.log('display registration form!'); }; */ const handleFormSubmit = () => { // TODO: Vardan: there is `novalidate` in Angular code, need to understand how to apply it here console.log('form submit'); }; const handleHostInputChange = () => { console.log('handle host input change'); }; const handleKeyPressKeyDown = (event: KeyboardEvent) => { if (event.key === 'Enter') { handleFormSubmit(); } }; const handleChangeStrictSignIn = () => { console.log('handleChangeStrictSignIn'); }; const handlePasswordChange = () => { console.log('handlePasswordChange'); }; const handlePasswordConfirmationChange = () => { console.log('handlePasswordConfirmationChange'); }; const handleChangeEphemeral = () => { console.log('change ephemeral'); // TODO: Vardan: perhaps need to set some "global" value here }; const handleMergeLocalData = () => { console.log('handleMergeLocalData'); }; const openPasswordWizard = () => { console.log('openPasswordWizard'); }; const openSessionsModal = () => { console.log('openSessionsModal'); }; const getEncryptionStatusForNotes = () => { const length = notesAndTagsCount; return `${length}/${length} notes and tags encrypted`; }; const enableProtections = () => { console.log('enableProtections'); }; const handleAddPassCode = () => { console.log('handleAddPassCode'); }; const submitPasscodeForm = () => { console.log('submitPasscodeForm'); }; const handlePasscodeChange = () => { console.log('handlePasscodeChange'); }; const handleConfirmPasscodeChange = () => { console.log('handleConfirmPasscodeChange'); }; const selectAutoLockInterval = (interval: number) => { console.log('selectAutoLockInterval', interval); }; const disableBetaWarning = () => { console.log('disableBetaWarning'); }; const hidePasswordForm = () => { setShowLogin(false); setShowRegister(false); setPassword(undefined); setPasswordConfirmation(undefined); }; const signOut = () => { console.log('signOut'); }; // TODO: Vardan: the name `changePasscodePressed` comes from original code; it is very similar to my `handlePasscodeChange`. // Check if `handlePasscodeChange` is not required, remove it and rename `changePasscodePressed` to `handlePasscodeChange` const changePasscodePressed = () => { console.log('changePasscodePressed'); }; // TODO: Vardan: the name `removePasscodePressed` comes from original code; // Check if I rename`changePasscodePressed` to `handlePasscodeChange`, also rename `removePasscodePressed` to `handleRemovePasscode` const removePasscodePressed = () => { console.log('removePasscodePressed'); }; const downloadDataArchive = () => { console.log('downloadDataArchive'); }; const importFileSelected = () => { console.log('importFileSelected'); }; const toggleErrorReportingEnabled = () => { if (isErrorReportingEnabled) { disableErrorReporting(); } else { enableErrorReporting(); } if (!isSyncInProgress) { window.location.reload(); } }; const openErrorReportingDialog = () => { console.log('openErrorReportingDialog'); }; const handleClose = () => { console.log('close this'); }; // TODO: check whether this works fine (e.g. remove all tags and notes and then add one and check whether UI behaves appropriately) const notesAndTagsCount = application.getItems([ContentType.Note, ContentType.Tag]).length; const hasProtections = application.hasProtectionSources(); // TODO: Vardan: this is as per `this.autorun` from `$onInit`, check whether it works // I'm mostly concerned about having dependency, since I think it is running only once in original code // (I suppose it runs here only once, too. But need to recheck) useEffect(() => { setSyncError(appState.sync.errorMessage); setIsSyncInProgress(appState.sync.inProgress); }, [appState.sync.errorMessage, appState.sync.inProgress]); useEffect(() => { setIsErrorReportingEnabled(storage.get(StorageKey.DisableErrorReporting) === false); }, []); useEffect(() => { // TODO: in original `AccountMenu`, the `appVersion` is available in constructor (the `window.electronAppVersion` is `undefined`). // But I can't find where `appVersion` is passed to AccountMenu's constructor... The only place I found is `app.ts`, where // it sets constant `appVersion` from `bridge.appVersion` - maybe constructor takes that value from there? // Ask someone to explain that part. // Here I just take the version from `application.bridge.appVersion`, as it is done in `app.ts`. setAppVersion(`v${((window as any).electronAppVersion || application.bridge.appVersion)}`); }, [appVersion, application.bridge.appVersion]); useEffect(() => { reloadAutoLockInterval(); }, [reloadAutoLockInterval]); useEffect(() => { setIsErrorReportingEnabled(storage.get(StorageKey.DisableErrorReporting) === false); }, []); useEffect(() => { const host = application.getHost(); setServer(host); }, [application]); /* const { searchOptions } = appState; const { includeProtectedContents, includeArchived, includeTrashed, } = searchOptions; */ return (
Account
Close
{!user && !showLogin && !showRegister && (
Sign in or register to enable sync and end-to-end encryption.
Standard Notes is free on every platform, and comes standard with sync and encryption.
)} {(showLogin || showRegister) && (
{showLogin ? 'Sign In' : 'Register'}
{/* TODO: Vardan: there are `should-focus` and `sn-autofocus`, implement them */} {showRegister && } {showAdvanced && (
Advanced Options
{showLogin && ( )}
)} {!isAuthenticating && (
)} {showRegister && (
No Password Reset.
Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.
)} {status && (
{status}
)} {!isAuthenticating && (
{notesAndTagsCount > 0 && ( )}
)}
)} {!showLogin && !showRegister && (
{user && (
{syncError && (
Sync Unreachable
Hmm...we can't seem to sync your account. The reason: {syncError}
Need help?
)}
{user.email}
{server}
)}
Encryption
{isEncryptionEnabled && (
{getEncryptionStatusForNotes()}
)}

{encryptionStatusString}

{hasProtections && (
Protections
{protectionsDisabledUntil && (
Protections are disabled until {protectionsDisabledUntil}
)} {!protectionsDisabledUntil && (
Protections are enabled
)}

Actions like viewing protected notes, exporting decrypted backups, or revoking an active session, require additional authentication like entering your account password or application passcode.

{protectionsDisabledUntil && (
)}
)}
Passcode Lock
{!hasPasscode && (
{canAddPasscode && ( <> {!showPasscodeForm && (
)}

Add a passcode to lock the application and encrypt on-device key storage.

{keyStorageInfo && (

{keyStorageInfo}

)} )} {!canAddPasscode && (

Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the "Stay signed in" option checked.

)}
)} {showPasscodeForm && (
{/* TODO: Vardan: there are `should-focus` and `sn-autofocus`, implement them */} )} {hasPasscode && !showPasscodeForm && ( <>
Passcode lock is enabled
Options
Autolock
{passcodeAutoLockOptions.map(option => { return ( selectAutoLockInterval(option.value)}> {option.label} ); })}
The autolock timer begins when the window or tab loses focus.
)}
{!isLoading && (
Data Backups
Download a backup of all your data.
{isEncryptionEnabled && (
)}
{isDesktopApplication() && (

Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.

)}
{isLoading && (
)}
)}
Error Reporting
Automatic error reporting is {isErrorReportingEnabled ? 'enabled' : 'disabled'}

Help us improve Standard Notes by automatically submitting anonymized error reports.

{errorReportingIdValue && ( <>

Your random identifier is strong {errorReportingIdValue}

Disabling error reporting will remove that identifier from your local storage, and a new identifier will be created should you decide to enable error reporting again in the future.

)}
)}
{appVersion} {showBetaWarning && ( Hide beta warning )}
{(showLogin || showRegister) && ( Cancel )} {!showLogin && !showRegister && ( {user ? 'Sign out' : 'Clear session data'} )}
); }); export const AccountMenu2 = toDirective( AccountMenu );