From 844b43e2fb9a659396e0f22066af372ee2e0a7f0 Mon Sep 17 00:00:00 2001 From: VardanHakobyan Date: Wed, 26 May 2021 22:30:32 +0400 Subject: [PATCH 01/44] refactor: migrate `account-menu` to react - initial UI --- app/assets/javascripts/app.ts | 4 + .../javascripts/components/AccountMenu2.tsx | 675 ++++++++++++++++++ .../components/ConfirmSignoutModal.tsx | 7 +- .../components/NoAccountWarning.tsx | 1 + .../components/NoProtectionsNoteWarning.tsx | 1 + .../app_state/account_menu_2_state.ts | 33 + .../ui_models/app_state/app_state.ts | 7 + .../javascripts/views/footer/footer-view.pug | 1 + .../javascripts/views/footer/footer_view.ts | 7 + app/assets/stylesheets/_footer.scss | 1 + .../templates/directives/input-modal.pug | 6 +- 11 files changed, 739 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/components/AccountMenu2.tsx create mode 100644 app/assets/javascripts/ui_models/app_state/account_menu_2_state.ts diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 8adffd559..f5e230f82 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -1,5 +1,7 @@ 'use strict'; +// import { AccountMenu2 } from '@/components/AccountMenu'; + declare const __VERSION__: string; declare const __WEB__: boolean; @@ -59,6 +61,7 @@ import { SessionsModalDirective } from './components/SessionsModal'; import { NoAccountWarningDirective } from './components/NoAccountWarning'; import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning'; import { SearchOptionsDirective } from './components/SearchOptions'; +import { AccountMenu2 } from './components/AccountMenu2'; import { ConfirmSignoutDirective } from './components/ConfirmSignoutModal'; import { MultipleSelectedNotesDirective } from './components/MultipleSelectedNotes'; import { NotesContextMenuDirective } from './components/NotesContextMenu'; @@ -150,6 +153,7 @@ const startApplication: StartApplication = async function startApplication( .directive('historyMenu', () => new HistoryMenu()) .directive('syncResolutionMenu', () => new SyncResolutionMenu()) .directive('sessionsModal', SessionsModalDirective) + .directive('accountMenu2', AccountMenu2) .directive('noAccountWarning', NoAccountWarningDirective) .directive('protectedNotePanel', NoProtectionsdNoteWarningDirective) .directive('searchOptions', SearchOptionsDirective) diff --git a/app/assets/javascripts/components/AccountMenu2.tsx b/app/assets/javascripts/components/AccountMenu2.tsx new file mode 100644 index 000000000..6b15c38e4 --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu2.tsx @@ -0,0 +1,675 @@ +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 { useEffect, useRef, useState } from 'preact/hooks'; + +import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; +import { isDesktopApplication } from '@/utils'; +import { storage, StorageKey } from '@Services/localStorage'; +import { disableErrorReporting, enableErrorReporting, errorReportingId } from '@Services/errorReporting'; +import { ConfirmSignoutDirective } from '@/components/ConfirmSignoutModal'; + +// 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 passcodeInput = useRef(); + // const { user, formData } = application; + // const [user, setUser] = useState(null); // TODO: Vardan: set correct type and initial value + const [user, setUser] = useState(undefined); // TODO: Vardan: set correct type and initial value + // 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(''); + const [server, setServer] = useState(''); + const [notesAndTagsCount, setNotesAndTagsCount] = useState(0); + const [isEncryptionEnabled, setIsEncryptionEnabled] = useState(false); + const [encryptionStatusString, setEncryptionStatusString] = useState(''); + const [hasProtections, setHasProtections] = useState(false); + const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(null); + const [hasPasscode, setHasPasscode] = useState(false); + const [canAddPasscode, setCanAddPasscode] = useState(false); + const [showPasscodeForm, setShowPasscodeForm] = useState(false); + const [keyStorageInfo, setKeyStorageInfo] = useState(null); + const [passcodeAutoLockOptions, setPasscodeAutoLockOptions] = useState<{value: number; label: string}[]>([]); + 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 [showBetaWarning, setShowBetaWarning] = useState(false); + + + // 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(false); + const [isSyncInProgress, setIsSyncInProgress] = useState(false); + + + const errorReportingIdValue = errorReportingId(); + + /* + 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 = () => { + console.log('implement `getEncryptionStatusForNotes`'); + return ''; + }; + + 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'); + }; + + + + + useEffect(() => { + // TODO: Vardan: get the real count + setNotesAndTagsCount(1); + }, []); + + useEffect(() => { + setIsErrorReportingEnabled( storage.get(StorageKey.DisableErrorReporting) === false); + }, []); + + /* + useEffect(() => { + setAppVersion(`v${((window as any).electronAppVersion || appVersion)}`); + }, [appVersion]); + */ + + /* + 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. +

+ + )} +
+ +
+ +
+ + )} +
+ )} +
+ { + // TODO: Vardan: check whether this works + () => ConfirmSignoutDirective + } +
+
+
+ {appVersion} + {showBetaWarning && ( + + Hide beta warning + + )} +
+ {(showLogin || showRegister) && ( + Cancel + )} + {!showLogin && !showRegister && ( + + {user ? "Sign out" : "Clear session data"} + + )} +
+
+
+
+
+ ); +}); + +export const AccountMenu2 = toDirective( + AccountMenu +); diff --git a/app/assets/javascripts/components/ConfirmSignoutModal.tsx b/app/assets/javascripts/components/ConfirmSignoutModal.tsx index b432caf83..a40c9bdc9 100644 --- a/app/assets/javascripts/components/ConfirmSignoutModal.tsx +++ b/app/assets/javascripts/components/ConfirmSignoutModal.tsx @@ -19,6 +19,9 @@ const ConfirmSignoutContainer = observer((props: Props) => { if (!props.appState.accountMenu.signingOut) { return null; } + if (!props.appState.accountMenu2.signingOut) { + return null; + } return ; }); @@ -30,13 +33,15 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => { const cancelRef = useRef(); function close() { appState.accountMenu.setSigningOut(false); + appState.accountMenu2.setSigningOut(false); } const [localBackupsCount, setLocalBackupsCount] = useState(0); useEffect(() => { application.bridge.localBackupsCount().then(setLocalBackupsCount); - }, [appState.accountMenu.signingOut, application.bridge]); + // }, [appState.accountMenu.signingOut, application.bridge]); + }, [appState.accountMenu.signingOut, appState.accountMenu2.signingOut, application.bridge]); return ( diff --git a/app/assets/javascripts/components/NoAccountWarning.tsx b/app/assets/javascripts/components/NoAccountWarning.tsx index a462b4920..cd2cfdb48 100644 --- a/app/assets/javascripts/components/NoAccountWarning.tsx +++ b/app/assets/javascripts/components/NoAccountWarning.tsx @@ -21,6 +21,7 @@ const NoAccountWarning = observer(({ appState }: Props) => { onClick={(event) => { event.stopPropagation(); appState.accountMenu.setShow(true); + appState.accountMenu2.setShow(true); }} > Open Account menu diff --git a/app/assets/javascripts/components/NoProtectionsNoteWarning.tsx b/app/assets/javascripts/components/NoProtectionsNoteWarning.tsx index e3e9ec291..4d9ace02e 100644 --- a/app/assets/javascripts/components/NoProtectionsNoteWarning.tsx +++ b/app/assets/javascripts/components/NoProtectionsNoteWarning.tsx @@ -16,6 +16,7 @@ function NoProtectionsNoteWarning({ appState, onViewNote }: Props) { className="sn-button small info" onClick={() => { appState.accountMenu.setShow(true); + appState.accountMenu2.setShow(true); }} > Open account menu diff --git a/app/assets/javascripts/ui_models/app_state/account_menu_2_state.ts b/app/assets/javascripts/ui_models/app_state/account_menu_2_state.ts new file mode 100644 index 000000000..fe42cf0bd --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/account_menu_2_state.ts @@ -0,0 +1,33 @@ +import { action, makeObservable, observable } from "mobx"; +import { WebApplication } from '@/ui_models/application'; + +export class AccountMenuState2 { + show = false; + signingOut = false; + + constructor( + // private application: WebApplication, + // appEventListeners: (() => void)[] + ) { + makeObservable(this, { + show: observable, + signingOut: observable, + + setShow: action, + toggleShow: action, + setSigningOut: action, + }); + } + + setShow = (show: boolean): void => { + this.show = show; + } + + setSigningOut = (signingOut: boolean): void => { + this.signingOut = signingOut; + } + + toggleShow = (): void => { + this.show = !this.show; + } +} diff --git a/app/assets/javascripts/ui_models/app_state/app_state.ts b/app/assets/javascripts/ui_models/app_state/app_state.ts index ba8c7ea11..873ecb69d 100644 --- a/app/assets/javascripts/ui_models/app_state/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state/app_state.ts @@ -21,6 +21,7 @@ import { SyncState } from './sync_state'; import { SearchOptionsState } from './search_options_state'; import { NotesState } from './notes_state'; import { TagsState } from './tags_state'; +import { AccountMenuState2 } from '@/ui_models/app_state/account_menu_2_state'; export enum AppStateEvent { TagChanged, @@ -61,6 +62,8 @@ export class AppState { selectedTag?: SNTag; showBetaWarning: boolean; readonly accountMenu = new AccountMenuState(); + // readonly accountMenu2 = new AccountMenu_2_State(); + readonly accountMenu2: AccountMenuState2; readonly actionsMenu = new ActionsMenuState(); readonly noAccountWarning: NoAccountWarningState; readonly sync = new SyncState(); @@ -96,6 +99,10 @@ export class AppState { application, this.appEventObserverRemovers ); + this.accountMenu2 = new AccountMenuState2( + // application, + // this.appEventObserverRemovers + ); this.searchOptions = new SearchOptionsState( application, this.appEventObserverRemovers diff --git a/app/assets/javascripts/views/footer/footer-view.pug b/app/assets/javascripts/views/footer/footer-view.pug index 0630cbdb3..65ede821d 100644 --- a/app/assets/javascripts/views/footer/footer-view.pug +++ b/app/assets/javascripts/views/footer/footer-view.pug @@ -18,6 +18,7 @@ ng-if='ctrl.showAccountMenu', application='ctrl.application' ) + account-menu2() .sk-app-bar-item a.no-decoration.sk-label.title( href='https://standardnotes.org/help', diff --git a/app/assets/javascripts/views/footer/footer_view.ts b/app/assets/javascripts/views/footer/footer_view.ts index 64a1b2703..9436596d1 100644 --- a/app/assets/javascripts/views/footer/footer_view.ts +++ b/app/assets/javascripts/views/footer/footer_view.ts @@ -62,6 +62,7 @@ class FooterViewCtrl extends PureViewCtrl { const showBetaWarning = this.appState.showBetaWarning; this.showAccountMenu = this.appState.accountMenu.show; + this.showAccountMenu2 = this.appState.accountMenu2.show; this.setState({ showBetaWarning: showBetaWarning, showDataUpgrade: !showBetaWarning @@ -254,6 +256,7 @@ class FooterViewCtrl extends PureViewCtrl Date: Fri, 28 May 2021 19:25:44 +0400 Subject: [PATCH 02/44] refactor: migrate `account-menu` to react - implement functionality - link the new React component to the app's store - setup correct initial values - small fixes --- .../javascripts/components/AccountMenu2.tsx | 660 ++++++++++-------- .../javascripts/views/footer/footer-view.pug | 6 +- .../javascripts/views/footer/footer_view.ts | 1 - 3 files changed, 360 insertions(+), 307 deletions(-) diff --git a/app/assets/javascripts/components/AccountMenu2.tsx b/app/assets/javascripts/components/AccountMenu2.tsx index 6b15c38e4..6f6dd0299 100644 --- a/app/assets/javascripts/components/AccountMenu2.tsx +++ b/app/assets/javascripts/components/AccountMenu2.tsx @@ -2,13 +2,13 @@ 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 { useEffect, useRef, useState } from 'preact/hooks'; +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; -import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; -import { isDesktopApplication } from '@/utils'; +import { isDesktopApplication, isSameDay } from '@/utils'; import { storage, StorageKey } from '@Services/localStorage'; import { disableErrorReporting, enableErrorReporting, errorReportingId } from '@Services/errorReporting'; -import { ConfirmSignoutDirective } from '@/components/ConfirmSignoutModal'; +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` @@ -20,11 +20,33 @@ type Props = { // 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 - const passcodeInput = useRef(); - // const { user, formData } = application; - // const [user, setUser] = useState(null); // TODO: Vardan: set correct type and initial value - const [user, setUser] = useState(undefined); // TODO: Vardan: set correct type and initial value // 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); @@ -33,32 +55,41 @@ const AccountMenu = observer(({ appState, application }: Props) => { const [password, setPassword] = useState(undefined); const [passwordConfirmation, setPasswordConfirmation] = useState(undefined); const [status, setStatus] = useState(''); - const [syncError, setSyncError] = useState(''); - const [server, setServer] = useState(''); - const [notesAndTagsCount, setNotesAndTagsCount] = useState(0); - const [isEncryptionEnabled, setIsEncryptionEnabled] = useState(false); - const [encryptionStatusString, setEncryptionStatusString] = useState(''); - const [hasProtections, setHasProtections] = useState(false); - const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(null); - const [hasPasscode, setHasPasscode] = useState(false); - const [canAddPasscode, setCanAddPasscode] = useState(false); + const [syncError, setSyncError] = useState(undefined); + + const [server, setServer] = useState(undefined); const [showPasscodeForm, setShowPasscodeForm] = useState(false); - const [keyStorageInfo, setKeyStorageInfo] = useState(null); - const [passcodeAutoLockOptions, setPasscodeAutoLockOptions] = useState<{value: number; label: string}[]>([]); 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 [showBetaWarning, setShowBetaWarning] = useState(false); + 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(false); + 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 = () => { @@ -110,8 +141,8 @@ const AccountMenu = observer(({ appState, application }: Props) => { }; const getEncryptionStatusForNotes = () => { - console.log('implement `getEncryptionStatusForNotes`'); - return ''; + const length = notesAndTagsCount; + return `${length}/${length} notes and tags encrypted`; }; const enableProtections = () => { @@ -192,23 +223,45 @@ const AccountMenu = observer(({ appState, application }: Props) => { 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(() => { - // TODO: Vardan: get the real count - setNotesAndTagsCount(1); + setIsErrorReportingEnabled(storage.get(StorageKey.DisableErrorReporting) === false); }, []); useEffect(() => { - setIsErrorReportingEnabled( storage.get(StorageKey.DisableErrorReporting) === false); + // 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(() => { - setAppVersion(`v${((window as any).electronAppVersion || appVersion)}`); - }, [appVersion]); - */ + const host = application.getHost(); + setServer(host); + }, [application]); + /* const { searchOptions } = appState; @@ -222,7 +275,7 @@ const AccountMenu = observer(({ appState, application }: Props) => { return (
{
- Standard Notes is free on every platform, and comes
+ Standard Notes is free on every platform, and comes standard with sync and encryption.
@@ -275,7 +328,8 @@ const AccountMenu = observer(({ appState, application }: Props) => { type='email' placeholder='Email' required - spellCheck={false} /> + spellCheck={false} + /> { onChange={handlePasswordConfirmationChange} />} {showAdvanced && ( -
-
-
+
+
+
Advanced Options
-
- - + +
{showLogin && ( -