diff --git a/app/assets/javascripts/components/AccountMenu.tsx b/app/assets/javascripts/components/AccountMenu.tsx index 4ec65cc6f..de8a23ab8 100644 --- a/app/assets/javascripts/components/AccountMenu.tsx +++ b/app/assets/javascripts/components/AccountMenu.tsx @@ -24,7 +24,7 @@ import { StringImportError, StringUtils } from '@/strings'; -import { BackupFile, ContentType } from '@node_modules/@standardnotes/snjs'; +import { ApplicationEvent, BackupFile } from '@node_modules/@standardnotes/snjs'; import { PasswordWizardType } from '@/types'; import { JSXInternal } from '@node_modules/preact/src/jsx'; import TargetedEvent = JSXInternal.TargetedEvent; @@ -37,10 +37,9 @@ import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal'; type Props = { appState: AppState; application: WebApplication; - closeAccountMenu: () => void; }; -const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props) => { +const AccountMenu = observer(({ application, appState }: Props) => { const getProtectionsDisabledUntil = (): string | null => { const protectionExpiry = application.getProtectionSessionExpiryDate(); const now = new Date(); @@ -69,6 +68,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const passcodeInputRef = useRef(); const emailInputRef = useRef(); const passwordInputRef = useRef(); + const passwordConfirmationInputRef = useRef(); const [showLogin, setShowLogin] = useState(false); const [showRegister, setShowRegister] = useState(false); @@ -78,7 +78,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const [password, setPassword] = useState(''); const [passwordConfirmation, setPasswordConfirmation] = useState(undefined); const [status, setStatus] = useState(undefined); - const [syncError, setSyncError] = useState(undefined); const [isEphemeral, setIsEphemeral] = useState(false); const [isStrictSignIn, setIsStrictSignIn] = useState(false); @@ -90,26 +89,60 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const [isEncryptionEnabled, setIsEncryptionEnabled] = useState(false); const [shouldMergeLocal, setShouldMergeLocal] = useState(true); - const [server, setServer] = useState(undefined); - const [url, setUrl] = useState(undefined); + const [server, setServer] = useState(application.getHost()); + const [url, setUrl] = useState(application.getHost()); const [showPasscodeForm, setShowPasscodeForm] = useState(false); const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState(null); const [isImportDataLoading, setIsImportDataLoading] = useState(false); - const [isErrorReportingEnabled, setIsErrorReportingEnabled] = useState(false); - const [appVersion, setAppVersion] = useState(''); + + const [isErrorReportingEnabled] = useState(() => storage.get(StorageKey.DisableErrorReporting) === false); + const [appVersion] = useState(() => `v${((window as any).electronAppVersion || application.bridge.appVersion)}`); const [hasPasscode, setHasPasscode] = useState(application.hasPasscode()); const [isBackupEncrypted, setIsBackupEncrypted] = useState(isEncryptionEnabled); - const [isSyncInProgress, setIsSyncInProgress] = useState(false); const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil()); + const [user, setUser] = useState(application.getUser()); + const [canAddPasscode, setCanAddPasscode] = useState(!application.isEphemeralSession()); + const [hasProtections] = useState(application.hasProtectionSources()); + const [notesAndTagsCount] = useState(appState.accountMenu.notesAndTagsCount); + + const refreshedCredentialState = () => { + setUser(application.getUser()); + setCanAddPasscode(!application.isEphemeralSession()); + setHasPasscode(application.hasPasscode()); + setShowPasscodeForm(false); + }; + + const loadHost = () => { + const host = application.getHost(); + setServer(host); + setUrl(host); + }; const reloadAutoLockInterval = useCallback(async () => { const interval = await application.getAutolockService().getAutoLockInterval(); setSelectedAutoLockInterval(interval); }, [application]); - const user = application.getUser(); + const refreshEncryptionStatus = () => { + const hasUser = application.hasAccount(); + const hasPasscode = application.hasPasscode(); + + setHasPasscode(hasPasscode); + + const encryptionEnabled = hasUser || hasPasscode; + + const newEncryptionStatusString = hasUser + ? STRING_E2E_ENABLED + : hasPasscode + ? STRING_LOCAL_ENC_ENABLED + : STRING_ENC_NOT_ENABLED; + + setEncryptionStatusString(newEncryptionStatusString); + setIsEncryptionEnabled(encryptionEnabled); + setIsBackupEncrypted(encryptionEnabled); + }; + const errorReportingIdValue = errorReportingId(); - const canAddPasscode = !application.isEphemeralSession(); const keyStorageInfo = StringUtils.keyStorageInfo(application); const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions(); const showBetaWarning = appState.showBetaWarning; @@ -122,6 +155,10 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props }, 0); }; + const closeAccountMenu = () => { + appState.accountMenu.closeAccountMenu(); + }; + const handleSignInClick = () => { setShowLogin(true); focusWithTimeout(emailInputRef); @@ -135,6 +172,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const blurAuthFields = () => { emailInputRef.current.blur(); passwordInputRef.current.blur(); + passwordConfirmationInputRef.current?.blur(); }; const login = async () => { @@ -143,7 +181,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const response = await application.signIn( email as string, - password as string, + password, isStrictSignIn, isEphemeral, shouldMergeLocal @@ -178,7 +216,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const response = await application.register( email as string, - password as string, + password, isEphemeral, shouldMergeLocal ); @@ -273,30 +311,8 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const enableProtections = () => { application.clearProtectionSession(); - - // Get the latest the protection status - setProtectionsDisabledUntil(getProtectionsDisabledUntil()); }; - const refreshEncryptionStatus = useCallback(() => { - const hasUser = application.hasAccount(); - const hasPasscode = application.hasPasscode(); - - setHasPasscode(hasPasscode); - - const encryptionEnabled = hasUser || hasPasscode; - - const newEncryptionStatusString = hasUser - ? STRING_E2E_ENABLED - : hasPasscode - ? STRING_LOCAL_ENC_ENABLED - : STRING_ENC_NOT_ENABLED; - - setEncryptionStatusString(newEncryptionStatusString); - setIsEncryptionEnabled(encryptionEnabled); - setIsBackupEncrypted(encryptionEnabled); - }, [application]); - const handleAddPassCode = () => { setShowPasscodeForm(true); @@ -333,8 +349,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props setPasscodeConfirmation(undefined); setShowPasscodeForm(false); - setProtectionsDisabledUntil(getProtectionsDisabledUntil()); - refreshEncryptionStatus(); }; @@ -357,7 +371,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props }; const disableBetaWarning = () => { - console.log('disableBetaWarning'); + appState.disableBetaWarning(); }; const hidePasswordForm = () => { @@ -388,8 +402,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props } } ); - - setProtectionsDisabledUntil(getProtectionsDisabledUntil()); }; const downloadDataArchive = () => { @@ -468,7 +480,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props } else { enableErrorReporting(); } - if (!isSyncInProgress) { + if (!appState.sync.inProgress) { window.location.reload(); } }; @@ -492,36 +504,39 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props }); }; - const notesAndTagsCount = application.getItems([ContentType.Note, ContentType.Tag]).length; - const hasProtections = application.hasProtectionSources(); - + // Add the required event observers useEffect(() => { - setSyncError(appState.sync.errorMessage); - setIsSyncInProgress(appState.sync.inProgress); - }, [appState.sync.errorMessage, appState.sync.inProgress]); + application.addEventObserver( + async () => { + refreshedCredentialState(); + loadHost(); + reloadAutoLockInterval(); + refreshEncryptionStatus(); + }, + ApplicationEvent.Launched + ); - useEffect(() => { - setIsErrorReportingEnabled(storage.get(StorageKey.DisableErrorReporting) === false); + application.addEventObserver( + async () => { + refreshedCredentialState(); + }, + ApplicationEvent.KeyStatusChanged + ); + + application.addEventObserver( + async () => { + setProtectionsDisabledUntil(getProtectionsDisabledUntil()) + }, + ApplicationEvent.ProtectionSessionExpiryDateChanged + ) }, []); - useEffect(() => { - setAppVersion(`v${((window as any).electronAppVersion || application.bridge.appVersion)}`); - }, [appVersion, application.bridge.appVersion]); - + // `reloadAutoLockInterval` gets interval asynchronously, therefore we call `useEffect` to set initial + // value of `selectedAutoLockInterval` useEffect(() => { reloadAutoLockInterval(); }, [reloadAutoLockInterval]); - useEffect(() => { - setIsErrorReportingEnabled(storage.get(StorageKey.DisableErrorReporting) === false); - }, []); - - useEffect(() => { - const host = application.getHost(); - setServer(host); - setUrl(host); - }, [application]); - useEffect(() => { refreshEncryptionStatus(); }, [refreshEncryptionStatus]); @@ -596,10 +611,11 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props onKeyDown={handleKeyPressKeyDown} value={passwordConfirmation} onChange={handlePasswordConfirmationChange} + ref={passwordConfirmationInputRef} />}
{ - setShowAdvanced(showAdvanced => !showAdvanced); + setShowAdvanced(!showAdvanced); }}> Advanced Options @@ -702,12 +718,12 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
{user && (
- {syncError && ( + {appState.sync.errorMessage && (
Sync Unreachable
Hmm...we can't seem to sync your account. - The reason: {syncError} + The reason: {appState.sync.errorMessage}
)}
- {!isImportDataLoading && ( + {isImportDataLoading ? ( +
+ ) : (
Data Backups
Download a backup of all your data.
@@ -913,9 +931,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props

)}
- {isImportDataLoading && ( -
- )}
)}
{(showLogin || showRegister) && ( @@ -979,6 +996,5 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props }); export const AccountMenuDirective = toDirective( - AccountMenu, - { closeAccountMenu: '&' } + AccountMenu ); diff --git a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts index 2ef9eb719..2a5fae28b 100644 --- a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts +++ b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts @@ -1,29 +1,58 @@ -import { action, makeObservable, observable } from "mobx"; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; +import { ContentType } from '@node_modules/@standardnotes/snjs'; +import { WebApplication } from '@/ui_models/application'; +import { SNItem } from '@node_modules/@standardnotes/snjs/dist/@types/models/core/item'; export class AccountMenuState { show = false; signingOut = false; + notesAndTags: SNItem[] = []; - constructor() { + constructor( + private application: WebApplication, + appEventListeners: (() => void)[] + ) { makeObservable(this, { show: observable, signingOut: observable, + notesAndTags: observable, setShow: action, toggleShow: action, setSigningOut: action, + + notesAndTagsCount: computed, }); + + appEventListeners.push( + this.application.streamItems( + [ContentType.Note, ContentType.Tag], + () => { + runInAction(() => { + this.notesAndTags = this.application.getItems([ContentType.Note, ContentType.Tag]); + }); + } + ) + ); } setShow = (show: boolean): void => { this.show = show; - } + }; + + closeAccountMenu = (): void => { + this.setShow(false); + }; setSigningOut = (signingOut: boolean): void => { this.signingOut = signingOut; - } + }; toggleShow = (): void => { this.show = !this.show; + }; + + get notesAndTagsCount(): number { + return this.notesAndTags.length; } } 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 f1a88faf8..8b1e352ee 100644 --- a/app/assets/javascripts/ui_models/app_state/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state/app_state.ts @@ -96,7 +96,10 @@ export class AppState { application, this.appEventObserverRemovers ); - this.accountMenu = new AccountMenuState(); + this.accountMenu = new AccountMenuState( + 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 1cc3d4441..e9a43c939 100644 --- a/app/assets/javascripts/views/footer/footer-view.pug +++ b/app/assets/javascripts/views/footer/footer-view.pug @@ -17,7 +17,6 @@ app-state='ctrl.appState' application='ctrl.application' ng-if='ctrl.showAccountMenu', - close-account-menu='ctrl.closeAccountMenu()', ) .sk-app-bar-item a.no-decoration.sk-label.title(