refactor: move all applicable parts to mobx instead of passing from parent to children components

This commit is contained in:
VardanHakobyan
2021-06-16 15:16:45 +04:00
parent cd5388d89f
commit 96aaff5ff8
9 changed files with 186 additions and 160 deletions

View File

@@ -9,36 +9,27 @@ import { JSXInternal } from 'preact/src/jsx';
import TargetedEvent = JSXInternal.TargetedEvent; import TargetedEvent = JSXInternal.TargetedEvent;
import TargetedKeyboardEvent = JSXInternal.TargetedKeyboardEvent; import TargetedKeyboardEvent = JSXInternal.TargetedKeyboardEvent;
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { StateUpdater, useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent; import TargetedMouseEvent = JSXInternal.TargetedMouseEvent;
import { FunctionalComponent } from 'preact';
import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; import { User } from '@standardnotes/snjs/dist/@types/services/api/responses';
import { observer } from 'mobx-react-lite';
import { AppState } from '@/ui_models/app_state';
type Props = { type Props = {
application: WebApplication; application: WebApplication;
server: string | undefined; appState: AppState;
setServer: StateUpdater<string | undefined>;
closeAccountMenu: () => void; closeAccountMenu: () => void;
notesAndTagsCount: number; notesAndTagsCount: number;
showLogin: boolean;
setShowLogin: StateUpdater<boolean>;
showRegister: boolean;
setShowRegister: StateUpdater<boolean>;
user: User | undefined; user: User | undefined;
} }
const Authentication: FunctionalComponent<Props> = ({ const Authentication = observer(({
application, application,
server, appState,
setServer, closeAccountMenu,
closeAccountMenu, notesAndTagsCount,
notesAndTagsCount, user
showLogin, }: Props) => {
setShowLogin,
showRegister,
setShowRegister,
user
}: Props) => {
const [showAdvanced, setShowAdvanced] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false);
const [isAuthenticating, setIsAuthenticating] = useState(false); const [isAuthenticating, setIsAuthenticating] = useState(false);
@@ -52,6 +43,15 @@ const Authentication: FunctionalComponent<Props> = ({
const [isStrictSignIn, setIsStrictSignIn] = useState(false); const [isStrictSignIn, setIsStrictSignIn] = useState(false);
const [shouldMergeLocal, setShouldMergeLocal] = useState(true); const [shouldMergeLocal, setShouldMergeLocal] = useState(true);
const {
server,
showLogin,
showRegister,
setShowLogin,
setShowRegister,
setServer
} = appState.accountMenu;
useEffect(() => { useEffect(() => {
if (isEmailFocused) { if (isEmailFocused) {
emailInputRef.current.focus(); emailInputRef.current.focus();
@@ -108,6 +108,7 @@ const Authentication: FunctionalComponent<Props> = ({
if (!error) { if (!error) {
setIsAuthenticating(false); setIsAuthenticating(false);
setPassword(''); setPassword('');
setShowLogin(false);
closeAccountMenu(); closeAccountMenu();
return; return;
@@ -119,7 +120,6 @@ const Authentication: FunctionalComponent<Props> = ({
if (error.message) { if (error.message) {
await application.alertService.alert(error.message); await application.alertService.alert(error.message);
// await handleAlert(error.message);
} }
setIsAuthenticating(false); setIsAuthenticating(false);
@@ -128,7 +128,6 @@ const Authentication: FunctionalComponent<Props> = ({
const register = async () => { const register = async () => {
if (passwordConfirmation !== password) { if (passwordConfirmation !== password) {
application.alertService.alert(STRING_NON_MATCHING_PASSWORDS); application.alertService.alert(STRING_NON_MATCHING_PASSWORDS);
// handleAlert(STRING_NON_MATCHING_PASSWORDS);
return; return;
} }
setStatus(STRING_GENERATING_REGISTER_KEYS); setStatus(STRING_GENERATING_REGISTER_KEYS);
@@ -147,9 +146,9 @@ const Authentication: FunctionalComponent<Props> = ({
setIsAuthenticating(false); setIsAuthenticating(false);
application.alertService.alert(error.message); application.alertService.alert(error.message);
// handleAlert(error.message);
} else { } else {
setIsAuthenticating(false); setIsAuthenticating(false);
setShowRegister(false);
closeAccountMenu(); closeAccountMenu();
} }
}; };
@@ -280,6 +279,7 @@ const Authentication: FunctionalComponent<Props> = ({
/>} />}
<div className="sk-panel-row" /> <div className="sk-panel-row" />
<button <button
type="button"
className="sn-button small info" className="sn-button small info"
onClick={() => { onClick={() => {
setShowAdvanced(!showAdvanced); setShowAdvanced(!showAdvanced);
@@ -310,6 +310,7 @@ const Authentication: FunctionalComponent<Props> = ({
<input <input
className="sk-input" className="sk-input"
type="checkbox" type="checkbox"
checked={isStrictSignIn}
onChange={() => setIsStrictSignIn(prevState => !prevState)} onChange={() => setIsStrictSignIn(prevState => !prevState)}
/> />
<p className="sk-p">Use strict sign in</p> <p className="sk-p">Use strict sign in</p>
@@ -383,6 +384,6 @@ const Authentication: FunctionalComponent<Props> = ({
</div> </div>
)}</> )}</>
); );
}; });
export default Authentication; export default Authentication;

View File

@@ -11,25 +11,23 @@ import { useState } from 'preact/hooks';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { JSXInternal } from 'preact/src/jsx'; import { JSXInternal } from 'preact/src/jsx';
import TargetedEvent = JSXInternal.TargetedEvent; import TargetedEvent = JSXInternal.TargetedEvent;
import { StateUpdater } from 'preact/hooks'; import { AppState } from '@/ui_models/app_state';
import { FunctionalComponent } from 'preact'; import { observer } from 'mobx-react-lite';
type Props = { type Props = {
application: WebApplication; application: WebApplication;
isBackupEncrypted: boolean; appState: AppState;
isEncryptionEnabled: boolean;
setIsBackupEncrypted: StateUpdater<boolean>;
} }
const DataBackup: FunctionalComponent<Props> = ({ const DataBackup = observer(({
application, application,
isBackupEncrypted, appState
isEncryptionEnabled, }: Props) => {
setIsBackupEncrypted
}) => {
const [isImportDataLoading, setIsImportDataLoading] = useState(false); const [isImportDataLoading, setIsImportDataLoading] = useState(false);
const { isBackupEncrypted, isEncryptionEnabled, setIsBackupEncrypted } = appState.accountMenu;
const downloadDataArchive = () => { const downloadDataArchive = () => {
application.getArchiveService().downloadBackup(isBackupEncrypted); application.getArchiveService().downloadBackup(isBackupEncrypted);
}; };
@@ -152,6 +150,6 @@ const DataBackup: FunctionalComponent<Props> = ({
)} )}
</> </>
); );
}; });
export default DataBackup; export default DataBackup;

View File

@@ -1,16 +1,17 @@
import { FunctionalComponent } from 'preact'; import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
type Props = { type Props = {
isEncryptionEnabled: boolean; appState: AppState;
notesAndTagsCount: number; notesAndTagsCount: number;
encryptionStatusString: string | undefined;
} }
const Encryption: FunctionalComponent<Props> = ({ const Encryption = observer(({
isEncryptionEnabled, appState,
notesAndTagsCount, notesAndTagsCount
encryptionStatusString }: Props) => {
}) => { const { isEncryptionEnabled, encryptionStatusString } = appState.accountMenu;
const getEncryptionStatusForNotes = () => { const getEncryptionStatusForNotes = () => {
const length = notesAndTagsCount; const length = notesAndTagsCount;
return `${length}/${length} notes and tags encrypted`; return `${length}/${length} notes and tags encrypted`;
@@ -31,6 +32,6 @@ const Encryption: FunctionalComponent<Props> = ({
</p> </p>
</div> </div>
); );
}; });
export default Encryption; export default Encryption;

View File

@@ -1,49 +1,43 @@
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { StateUpdater, useState } from 'preact/hooks'; import { useState } from 'preact/hooks';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; import { User } from '@standardnotes/snjs/dist/@types/services/api/responses';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
type Props = { type Props = {
appState: AppState;
application: WebApplication; application: WebApplication;
showLogin: boolean; appState: AppState;
setShowLogin: StateUpdater<boolean>;
showRegister: boolean;
setShowRegister: StateUpdater<boolean>;
user: User | undefined; user: User | undefined;
} }
const Footer = observer(({ const Footer = observer(({
appState,
application, application,
showLogin, appState,
setShowLogin,
showRegister,
setShowRegister,
user user
}: Props) => { }: Props) => {
const {
showLogin,
showRegister,
setShowLogin,
setShowRegister,
setSigningOut
} = appState.accountMenu;
const showBetaWarning = appState.showBetaWarning; const { showBetaWarning, disableBetaWarning: disableAppStateBetaWarning } = appState;
const [appVersion] = useState(() => `v${((window as any).electronAppVersion || application.bridge.appVersion)}`); const [appVersion] = useState(() => `v${((window as any).electronAppVersion || application.bridge.appVersion)}`);
const disableBetaWarning = () => { const disableBetaWarning = () => {
appState.disableBetaWarning(); disableAppStateBetaWarning();
}; };
const signOut = () => { const signOut = () => {
appState.accountMenu.setSigningOut(true); setSigningOut(true);
}; };
const hidePasswordForm = () => { const hidePasswordForm = () => {
setShowLogin(false); setShowLogin(false);
setShowRegister(false); setShowRegister(false);
// TODO: Vardan: this comes from main `index.tsx` and the below commented parts should reset password and confirmation.
// Check whether it works when I don't call them explicitly.
// setPassword('');
// setPasswordConfirmation(undefined);
}; };
return ( return (

View File

@@ -12,25 +12,23 @@ import { alertDialog } from '@Services/alertService';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { ApplicationEvent } from '@standardnotes/snjs'; import { ApplicationEvent } from '@standardnotes/snjs';
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent; import TargetedMouseEvent = JSXInternal.TargetedMouseEvent;
import { StateUpdater } from 'preact/hooks'; import { observer } from 'mobx-react-lite';
import { FunctionalComponent } from 'preact'; import { AppState } from '@/ui_models/app_state';
type Props = { type Props = {
application: WebApplication; application: WebApplication;
setEncryptionStatusString: StateUpdater<string | undefined>; appState: AppState;
setIsEncryptionEnabled: StateUpdater<boolean>;
setIsBackupEncrypted: StateUpdater<boolean>;
}; };
const PasscodeLock: FunctionalComponent<Props> = ({ const PasscodeLock = observer(({
application, application,
setEncryptionStatusString, appState,
setIsEncryptionEnabled, }: Props) => {
setIsBackupEncrypted
}) => {
const keyStorageInfo = StringUtils.keyStorageInfo(application); const keyStorageInfo = StringUtils.keyStorageInfo(application);
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions(); const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions();
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } = appState.accountMenu;
const passcodeInputRef = useRef<HTMLInputElement>(); const passcodeInputRef = useRef<HTMLInputElement>();
const [passcode, setPasscode] = useState<string | undefined>(undefined); const [passcode, setPasscode] = useState<string | undefined>(undefined);
@@ -263,6 +261,6 @@ const PasscodeLock: FunctionalComponent<Props> = ({
)} )}
</div> </div>
); );
}; });
export default PasscodeLock; export default PasscodeLock;

View File

@@ -1,19 +1,65 @@
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { FunctionalComponent } from 'preact'; import { FunctionalComponent } from 'preact';
import { useCallback, useState } from '@node_modules/preact/hooks';
import { useEffect } from 'preact/hooks';
import { ApplicationEvent } from '@node_modules/@standardnotes/snjs';
import { isSameDay } from '@/utils';
type Props = { type Props = {
application: WebApplication; application: WebApplication;
protectionsDisabledUntil: string | null;
}; };
const Protections: FunctionalComponent<Props> = ({ const Protections: FunctionalComponent<Props> = ({ application }) => {
application,
protectionsDisabledUntil
}) => {
const enableProtections = () => { const enableProtections = () => {
application.clearProtectionSession(); application.clearProtectionSession();
}; };
const hasProtections = application.hasProtectionSources();
const getProtectionsDisabledUntil = useCallback((): 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;
}, [application]);
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil());
useEffect(() => {
const removeProtectionSessionExpiryDateChangedObserver = application.addEventObserver(
async () => {
setProtectionsDisabledUntil(getProtectionsDisabledUntil());
},
ApplicationEvent.ProtectionSessionExpiryDateChanged
);
return () => {
removeProtectionSessionExpiryDateChangedObserver();
};
}, [application, getProtectionsDisabledUntil]);
if (!hasProtections) {
return null;
}
return ( return (
<div className="sk-panel-section"> <div className="sk-panel-section">
<div className="sk-panel-section-title">Protections</div> <div className="sk-panel-section-title">Protections</div>

View File

@@ -5,7 +5,6 @@ import { WebApplication } from '@/ui_models/application';
type Props = { type Props = {
email: string; email: string;
server: string | undefined;
appState: AppState; appState: AppState;
application: WebApplication; application: WebApplication;
closeAccountMenu: () => void; closeAccountMenu: () => void;
@@ -13,11 +12,12 @@ type Props = {
const User = observer(({ const User = observer(({
email, email,
server,
appState, appState,
application, application,
closeAccountMenu closeAccountMenu
}: Props) => { }: Props) => {
const { server } = appState.accountMenu;
const openPasswordWizard = () => { const openPasswordWizard = () => {
closeAccountMenu(); closeAccountMenu();
application.presentPasswordWizard(PasswordWizardType.ChangePassword); application.presentPasswordWizard(PasswordWizardType.ChangePassword);

View File

@@ -3,7 +3,6 @@ import { toDirective } from '@/components/utils';
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from 'preact/hooks';
import { isSameDay } from '@/utils';
import { ApplicationEvent } from '@standardnotes/snjs'; import { ApplicationEvent } from '@standardnotes/snjs';
import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal'; import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal';
import Authentication from '@/components/AccountMenu/Authentication'; import Authentication from '@/components/AccountMenu/Authentication';
@@ -14,7 +13,6 @@ import Protections from '@/components/AccountMenu/Protections';
import PasscodeLock from '@/components/AccountMenu/PasscodeLock'; import PasscodeLock from '@/components/AccountMenu/PasscodeLock';
import DataBackup from '@/components/AccountMenu/DataBackup'; import DataBackup from '@/components/AccountMenu/DataBackup';
import ErrorReporting from '@/components/AccountMenu/ErrorReporting'; import ErrorReporting from '@/components/AccountMenu/ErrorReporting';
import { useCallback } from 'preact/hooks';
type Props = { type Props = {
appState: AppState; appState: AppState;
@@ -22,45 +20,17 @@ type Props = {
}; };
const AccountMenu = observer(({ application, appState }: Props) => { const AccountMenu = observer(({ application, appState }: Props) => {
const getProtectionsDisabledUntil = useCallback((): 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;
}, [application]);
const [showLogin, setShowLogin] = useState(false);
const [showRegister, setShowRegister] = useState(false);
const [encryptionStatusString, setEncryptionStatusString] = useState<string | undefined>(undefined);
const [isEncryptionEnabled, setIsEncryptionEnabled] = useState(false);
const [server, setServer] = useState<string | undefined>(application.getHost());
const [isBackupEncrypted, setIsBackupEncrypted] = useState(isEncryptionEnabled);
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil());
const [user, setUser] = useState(application.getUser()); const [user, setUser] = useState(application.getUser());
const [hasProtections] = useState(application.hasProtectionSources());
const { notesAndTagsCount } = appState.accountMenu; const {
notesAndTagsCount,
showLogin,
showRegister,
closeAccountMenu: closeAppStateAccountMenu
} = appState.accountMenu;
const closeAccountMenu = () => { const closeAccountMenu = () => {
appState.accountMenu.closeAccountMenu(); closeAppStateAccountMenu();
}; };
// Add the required event observers // Add the required event observers
@@ -72,18 +42,10 @@ const AccountMenu = observer(({ application, appState }: Props) => {
ApplicationEvent.KeyStatusChanged ApplicationEvent.KeyStatusChanged
); );
const removeProtectionSessionExpiryDateChangedObserver = application.addEventObserver(
async () => {
setProtectionsDisabledUntil(getProtectionsDisabledUntil());
},
ApplicationEvent.ProtectionSessionExpiryDateChanged
);
return () => { return () => {
removeKeyStatusChangedObserver(); removeKeyStatusChangedObserver();
removeProtectionSessionExpiryDateChangedObserver();
}; };
}, [application, getProtectionsDisabledUntil]); }, [application]);
return ( return (
<div className="sn-component"> <div className="sn-component">
@@ -95,62 +57,45 @@ const AccountMenu = observer(({ application, appState }: Props) => {
<div className="sk-panel-content"> <div className="sk-panel-content">
<Authentication <Authentication
application={application} application={application}
server={server} appState={appState}
setServer={setServer}
closeAccountMenu={closeAccountMenu} closeAccountMenu={closeAccountMenu}
notesAndTagsCount={notesAndTagsCount} notesAndTagsCount={notesAndTagsCount}
showLogin={showLogin}
setShowLogin={setShowLogin}
showRegister={showRegister}
setShowRegister={setShowRegister}
user={user} user={user}
/> />
{!showLogin && !showRegister && ( {!showLogin && !showRegister && (
<div> <div>
{user && ( {user && (
<User <User
email={user.email}
server={server}
appState={appState}
application={application} application={application}
appState={appState}
email={user.email}
closeAccountMenu={closeAccountMenu} closeAccountMenu={closeAccountMenu}
/> />
)} )}
<Encryption <Encryption
isEncryptionEnabled={isEncryptionEnabled} appState={appState}
notesAndTagsCount={notesAndTagsCount} notesAndTagsCount={notesAndTagsCount}
encryptionStatusString={encryptionStatusString}
/> />
{hasProtections && ( <Protections application={application} />
<Protections
application={application}
protectionsDisabledUntil={protectionsDisabledUntil}
/>
)}
<PasscodeLock <PasscodeLock
application={application} application={application}
setEncryptionStatusString={setEncryptionStatusString} appState={appState}
setIsEncryptionEnabled={setIsEncryptionEnabled}
setIsBackupEncrypted={setIsBackupEncrypted}
/> />
<DataBackup <DataBackup
application={application} application={application}
isBackupEncrypted={isBackupEncrypted} appState={appState}
isEncryptionEnabled={isEncryptionEnabled}
setIsBackupEncrypted={setIsBackupEncrypted}
/> />
<ErrorReporting appState={appState} /> <ErrorReporting appState={appState} />
</div> </div>
)} )}
</div> </div>
<ConfirmSignoutContainer application={application} appState={appState} /> <ConfirmSignoutContainer
<Footer
appState={appState}
application={application} application={application}
showLogin={showLogin} appState={appState}
setShowLogin={setShowLogin} />
showRegister={showRegister} <Footer
setShowRegister={setShowRegister} application={application}
appState={appState}
user={user} user={user}
/> />
</div> </div>

View File

@@ -6,7 +6,13 @@ import { SNItem } from '@standardnotes/snjs/dist/@types/models/core/item';
export class AccountMenuState { export class AccountMenuState {
show = false; show = false;
signingOut = false; signingOut = false;
server: string | undefined = undefined;
notesAndTags: SNItem[] = []; notesAndTags: SNItem[] = [];
isEncryptionEnabled = false;
encryptionStatusString = '';
isBackupEncrypted = false;
showLogin = false;
showRegister = false;
constructor( constructor(
private application: WebApplication, private application: WebApplication,
@@ -15,21 +21,34 @@ export class AccountMenuState {
makeObservable(this, { makeObservable(this, {
show: observable, show: observable,
signingOut: observable, signingOut: observable,
server: observable,
notesAndTags: observable, notesAndTags: observable,
isEncryptionEnabled: observable,
encryptionStatusString: observable,
isBackupEncrypted: observable,
showLogin: observable,
showRegister: observable,
setShow: action, setShow: action,
toggleShow: action, toggleShow: action,
setSigningOut: action, setSigningOut: action,
setIsEncryptionEnabled: action,
setEncryptionStatusString: action,
setIsBackupEncrypted: action,
notesAndTagsCount: computed notesAndTagsCount: computed
}); });
appEventListeners.push( appEventListeners.push(
this.application.streamItems( this.application.streamItems(
[ContentType.Note, ContentType.Tag], [
ContentType.Note, ContentType.Tag,
ContentType.Component // TODO: is this correct for streaming `server`?
],
() => { () => {
runInAction(() => { runInAction(() => {
this.notesAndTags = this.application.getItems([ContentType.Note, ContentType.Tag]); this.notesAndTags = this.application.getItems([ContentType.Note, ContentType.Tag]);
this.setServer(this.application.getHost());
}); });
} }
) )
@@ -48,6 +67,30 @@ export class AccountMenuState {
this.signingOut = signingOut; this.signingOut = signingOut;
}; };
setServer = (server: string | undefined): void => {
this.server = server;
};
setIsEncryptionEnabled = (isEncryptionEnabled: boolean): void => {
this.isEncryptionEnabled = isEncryptionEnabled;
};
setEncryptionStatusString = (encryptionStatusString: string): void => {
this.encryptionStatusString = encryptionStatusString;
};
setIsBackupEncrypted = (isBackupEncrypted: boolean): void => {
this.isBackupEncrypted = isBackupEncrypted;
};
setShowLogin = (showLogin: boolean): void => {
this.showLogin = showLogin;
};
setShowRegister = (showRegister: boolean): void => {
this.showRegister = showRegister;
};
toggleShow = (): void => { toggleShow = (): void => {
this.show = !this.show; this.show = !this.show;
}; };