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

View file

@ -11,25 +11,23 @@ import { useState } from 'preact/hooks';
import { WebApplication } from '@/ui_models/application';
import { JSXInternal } from 'preact/src/jsx';
import TargetedEvent = JSXInternal.TargetedEvent;
import { StateUpdater } from 'preact/hooks';
import { FunctionalComponent } from 'preact';
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
type Props = {
application: WebApplication;
isBackupEncrypted: boolean;
isEncryptionEnabled: boolean;
setIsBackupEncrypted: StateUpdater<boolean>;
appState: AppState;
}
const DataBackup: FunctionalComponent<Props> = ({
application,
isBackupEncrypted,
isEncryptionEnabled,
setIsBackupEncrypted
}) => {
const DataBackup = observer(({
application,
appState
}: Props) => {
const [isImportDataLoading, setIsImportDataLoading] = useState(false);
const { isBackupEncrypted, isEncryptionEnabled, setIsBackupEncrypted } = appState.accountMenu;
const downloadDataArchive = () => {
application.getArchiveService().downloadBackup(isBackupEncrypted);
};
@ -152,6 +150,6 @@ const DataBackup: FunctionalComponent<Props> = ({
)}
</>
);
};
});
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 = {
isEncryptionEnabled: boolean;
appState: AppState;
notesAndTagsCount: number;
encryptionStatusString: string | undefined;
}
const Encryption: FunctionalComponent<Props> = ({
isEncryptionEnabled,
notesAndTagsCount,
encryptionStatusString
}) => {
const Encryption = observer(({
appState,
notesAndTagsCount
}: Props) => {
const { isEncryptionEnabled, encryptionStatusString } = appState.accountMenu;
const getEncryptionStatusForNotes = () => {
const length = notesAndTagsCount;
return `${length}/${length} notes and tags encrypted`;
@ -31,6 +32,6 @@ const Encryption: FunctionalComponent<Props> = ({
</p>
</div>
);
};
});
export default Encryption;

View file

@ -1,49 +1,43 @@
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 { User } from '@standardnotes/snjs/dist/@types/services/api/responses';
import { observer } from 'mobx-react-lite';
type Props = {
appState: AppState;
application: WebApplication;
showLogin: boolean;
setShowLogin: StateUpdater<boolean>;
showRegister: boolean;
setShowRegister: StateUpdater<boolean>;
appState: AppState;
user: User | undefined;
}
const Footer = observer(({
appState,
application,
showLogin,
setShowLogin,
showRegister,
setShowRegister,
appState,
user
}: 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 disableBetaWarning = () => {
appState.disableBetaWarning();
disableAppStateBetaWarning();
};
const signOut = () => {
appState.accountMenu.setSigningOut(true);
setSigningOut(true);
};
const hidePasswordForm = () => {
setShowLogin(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 (

View file

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

View file

@ -1,19 +1,65 @@
import { WebApplication } from '@/ui_models/application';
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 = {
application: WebApplication;
protectionsDisabledUntil: string | null;
};
const Protections: FunctionalComponent<Props> = ({
application,
protectionsDisabledUntil
}) => {
const Protections: FunctionalComponent<Props> = ({ application }) => {
const enableProtections = () => {
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 (
<div className="sk-panel-section">
<div className="sk-panel-section-title">Protections</div>

View file

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

View file

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

View file

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