diff --git a/.env.sample b/.env.sample index 42ed60a38..cd705c8e0 100644 --- a/.env.sample +++ b/.env.sample @@ -8,7 +8,6 @@ RAILS_LOG_LEVEL=INFO RAILS_SERVE_STATIC_FILES=true SECRET_KEY_BASE=test -BUGSNAG_API_KEY= APP_HOST=http://localhost:3001 PURCHASE_URL=https://standardnotes.com/purchase diff --git a/.husky/pre-commit b/.husky/pre-commit index f70d06a12..36af21989 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -# npx pretty-quick --staged # trying lint-staged for now, it is slower but uses eslint. npx lint-staged diff --git a/Dockerfile b/Dockerfile index e787fcdbf..c27692fad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,6 @@ WORKDIR /app/ COPY package.json yarn.lock Gemfile Gemfile.lock /app/ -COPY vendor /app/vendor - RUN yarn install --pure-lockfile RUN gem install bundler && bundle install diff --git a/app/assets/javascripts/app.tsx b/app/assets/javascripts/app.tsx index b8848a528..6a9b88137 100644 --- a/app/assets/javascripts/app.tsx +++ b/app/assets/javascripts/app.tsx @@ -2,7 +2,6 @@ declare global { interface Window { - bugsnagApiKey?: string; dashboardUrl?: string; defaultSyncServer: string; devAccountEmail?: string; @@ -22,7 +21,6 @@ import { render } from 'preact'; import { ApplicationGroupView } from './components/ApplicationGroupView'; import { Bridge } from './services/bridge'; import { BrowserBridge } from './services/browserBridge'; -import { startErrorReporting } from './services/errorReporting'; import { StartApplication } from './startApplication'; import { ApplicationGroup } from './ui_models/application_group'; import { isDev } from './utils'; @@ -34,7 +32,7 @@ const startApplication: StartApplication = async function startApplication( webSocketUrl: string ) { SNLog.onLog = console.log; - startErrorReporting(); + SNLog.onError = console.error; const mainApplicationGroup = new ApplicationGroup( defaultSyncServerHost, diff --git a/app/assets/javascripts/components/Abstract/PureComponent.tsx b/app/assets/javascripts/components/Abstract/PureComponent.tsx index 9a56d834d..ef6fac1f4 100644 --- a/app/assets/javascripts/components/Abstract/PureComponent.tsx +++ b/app/assets/javascripts/components/Abstract/PureComponent.tsx @@ -78,7 +78,7 @@ export abstract class PureComponent< ); } - onAppStateEvent(eventName: any, data: any) { + onAppStateEvent(_eventName: any, _data: any) { /** Optional override */ } diff --git a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx index 1a75878df..a09335b36 100644 --- a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx +++ b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx @@ -2,7 +2,7 @@ import { WebApplication } from '@/ui_models/application'; import { AppState } from '@/ui_models/app_state'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; -import { useState } from 'preact/hooks'; +import { useEffect, useState } from 'preact/hooks'; import { Checkbox } from '../Checkbox'; import { Icon } from '../Icon'; import { InputWithIcon } from '../InputWithIcon'; @@ -11,14 +11,70 @@ type Props = { application: WebApplication; appState: AppState; disabled?: boolean; + onVaultChange?: (isVault: boolean, vaultedEmail?: string) => void; + onStrictSignInChange?: (isStrictSignIn: boolean) => void; }; export const AdvancedOptions: FunctionComponent = observer( - ({ appState, application, disabled = false, children }) => { + ({ + appState, + application, + disabled = false, + onVaultChange, + onStrictSignInChange, + children, + }) => { const { server, setServer, enableServerOption, setEnableServerOption } = appState.accountMenu; const [showAdvanced, setShowAdvanced] = useState(false); + const [isVault, setIsVault] = useState(false); + const [vaultName, setVaultName] = useState(''); + const [vaultUserphrase, setVaultUserphrase] = useState(''); + + const [isStrictSignin, setIsStrictSignin] = useState(false); + + useEffect(() => { + const recomputeVaultedEmail = async () => { + const vaultedEmail = await application.vaultToEmail( + vaultName, + vaultUserphrase + ); + + if (!vaultedEmail) { + if (vaultName?.length > 0 && vaultUserphrase?.length > 0) { + application.alertService.alert('Unable to compute vault name.'); + } + return; + } + onVaultChange?.(true, vaultedEmail); + }; + + if (vaultName && vaultUserphrase) { + recomputeVaultedEmail(); + } + }, [vaultName, vaultUserphrase, application, onVaultChange]); + + useEffect(() => { + onVaultChange?.(isVault); + }, [isVault, onVaultChange]); + + const handleIsVaultChange = () => { + setIsVault(!isVault); + }; + + const handleVaultNameChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setVaultName(e.target.value); + } + }; + + const handleVaultUserphraseChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setVaultUserphrase(e.target.value); + } + }; + const handleServerOptionChange = (e: Event) => { if (e.target instanceof HTMLInputElement) { setEnableServerOption(e.target.checked); @@ -32,6 +88,12 @@ export const AdvancedOptions: FunctionComponent = observer( } }; + const handleStrictSigninChange = () => { + const newValue = !isStrictSignin; + setIsStrictSignin(newValue); + onStrictSignInChange?.(newValue); + }; + const toggleShowAdvanced = () => { setShowAdvanced(!showAdvanced); }; @@ -50,6 +112,70 @@ export const AdvancedOptions: FunctionComponent = observer( {showAdvanced ? (
{children} + + {appState.enableUnfinishedFeatures && ( +
+ + + + +
+ )} + + {appState.enableUnfinishedFeatures && isVault && ( + <> + + + + )} + + {onStrictSignInChange && ( +
+ + + + +
+ )} + { - const [showAdvanced, setShowAdvanced] = useState(false); - const [isAuthenticating, setIsAuthenticating] = useState(false); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [passwordConfirmation, setPasswordConfirmation] = useState(''); - const [status, setStatus] = useState(undefined); - const [isEmailFocused, setIsEmailFocused] = useState(false); - - const [isEphemeral, setIsEphemeral] = useState(false); - const [isStrictSignIn, setIsStrictSignIn] = useState(false); - const [shouldMergeLocal, setShouldMergeLocal] = useState(true); - - const { - server, - notesAndTagsCount, - showSignIn, - showRegister, - setShowSignIn, - setShowRegister, - setServer, - closeAccountMenu, - } = appState.accountMenu; - - useEffect(() => { - if (isEmailFocused) { - emailInputRef.current!.focus(); - setIsEmailFocused(false); - } - }, [isEmailFocused]); - - // Reset password and confirmation fields when hiding the form - useEffect(() => { - if (!showSignIn && !showRegister) { - setPassword(''); - setPasswordConfirmation(''); - } - }, [showSignIn, showRegister]); - - const handleHostInputChange = (event: TargetedEvent) => { - const { value } = event.target as HTMLInputElement; - setServer(value); - application.setCustomHost(value); - }; - - const emailInputRef = useRef(null); - const passwordInputRef = useRef(null); - const passwordConfirmationInputRef = useRef(null); - - const handleSignInClick = () => { - setShowSignIn(true); - setIsEmailFocused(true); - }; - - const handleRegisterClick = () => { - setShowRegister(true); - setIsEmailFocused(true); - }; - - const blurAuthFields = () => { - emailInputRef.current!.blur(); - passwordInputRef.current!.blur(); - passwordConfirmationInputRef.current?.blur(); - }; - - const signin = async () => { - setStatus(STRING_GENERATING_LOGIN_KEYS); - setIsAuthenticating(true); - - const response = await application.signIn( - email, - password, - isStrictSignIn, - isEphemeral, - shouldMergeLocal - ); - const error = response.error; - if (!error) { - setIsAuthenticating(false); - setPassword(''); - setShowSignIn(false); - - closeAccountMenu(); - return; - } - - setShowSignIn(true); - setStatus(undefined); - setPassword(''); - - if (error.message) { - await application.alertService.alert(error.message); - } - - setIsAuthenticating(false); - }; - - const register = async () => { - if (passwordConfirmation !== password) { - application.alertService.alert(STRING_NON_MATCHING_PASSWORDS); - return; - } - setStatus(STRING_GENERATING_REGISTER_KEYS); - setIsAuthenticating(true); - - const response = await application.register( - email, - password, - isEphemeral, - shouldMergeLocal - ); - - const error = response.error; - if (error) { - setStatus(undefined); - setIsAuthenticating(false); - - application.alertService.alert(error.message); - } else { - setIsAuthenticating(false); - setShowRegister(false); - closeAccountMenu(); - } - }; - - const handleAuthFormSubmit = ( - event: - | TargetedEvent - | TargetedMouseEvent - | TargetedKeyboardEvent - ) => { - event.preventDefault(); - - if (!email || !password) { - return; - } - - blurAuthFields(); - - if (showSignIn) { - signin(); - } else { - register(); - } - }; - - const handleKeyPressKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Enter') { - handleAuthFormSubmit(event as TargetedKeyboardEvent); - } - }; - - const handlePasswordChange = (event: TargetedEvent) => { - const { value } = event.target as HTMLInputElement; - setPassword(value); - }; - - const handleEmailChange = (event: TargetedEvent) => { - const { value } = event.target as HTMLInputElement; - setEmail(value); - }; - - const handlePasswordConfirmationChange = ( - event: TargetedEvent - ) => { - const { value } = event.target as HTMLInputElement; - setPasswordConfirmation(value); - }; - - const handleMergeLocalData = async ( - event: TargetedEvent - ) => { - const { checked } = event.target as HTMLInputElement; - - setShouldMergeLocal(checked); - if (!checked) { - const confirmResult = await confirmDialog({ - text: STRING_ACCOUNT_MENU_UNCHECK_MERGE, - confirmButtonStyle: 'danger', - }); - setShouldMergeLocal(!confirmResult); - } - }; - - return ( - <> - {!application.hasAccount() && !showSignIn && !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. -
-
- )} - {(showSignIn || showRegister) && ( -
-
- {showSignIn ? 'Sign In' : 'Register'} -
-
-
- - - {showRegister && ( - - )} -
- -
- {showAdvanced && ( -
-
-
- Advanced Options -
-
- - -
- {showSignIn && ( - - )} -
-
- )} - {!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 && ( - - )} -
- )} - -
- )} - - ); -}); - -export default Authentication; diff --git a/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx b/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx index 25dd2c317..0a5575cb6 100644 --- a/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx +++ b/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx @@ -162,12 +162,6 @@ export const ConfirmPassword: FunctionComponent = observer( /> ) : null} -
- ); } diff --git a/app/assets/javascripts/components/AccountMenu/CreateAccount.tsx b/app/assets/javascripts/components/AccountMenu/CreateAccount.tsx index 3388d6aa0..46d2f0621 100644 --- a/app/assets/javascripts/components/AccountMenu/CreateAccount.tsx +++ b/app/assets/javascripts/components/AccountMenu/CreateAccount.tsx @@ -33,6 +33,7 @@ export const CreateAccount: FunctionComponent = observer( const emailInputRef = useRef(null); const passwordInputRef = useRef(null); + const [isVault, setIsVault] = useState(false); useEffect(() => { if (emailInputRef.current) { @@ -82,6 +83,13 @@ export const CreateAccount: FunctionComponent = observer( setPassword(''); }; + const onVaultChange = (isVault: boolean, vaultedEmail?: string) => { + setIsVault(isVault); + if (isVault && vaultedEmail) { + setEmail(vaultedEmail); + } + }; + return ( <>
@@ -101,6 +109,7 @@ export const CreateAccount: FunctionComponent = observer( inputType="email" placeholder="Email" value={email} + disabled={isVault} onChange={handleEmailChange} onKeyDown={handleKeyDown} ref={emailInputRef} @@ -130,7 +139,11 @@ export const CreateAccount: FunctionComponent = observer( />
- + ); } diff --git a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx index 526ddf8d9..be83c04fa 100644 --- a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx +++ b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx @@ -2,18 +2,21 @@ import { WebApplication } from '@/ui_models/application'; import { AppState } from '@/ui_models/app_state'; import { observer } from 'mobx-react-lite'; import { Icon } from '../Icon'; -import { formatLastSyncDate } from '@/preferences/panes/account/Sync'; +import { formatLastSyncDate } from '@/components/Preferences/panes/account/Sync'; import { SyncQueueStrategy } from '@standardnotes/snjs'; import { STRING_GENERIC_SYNC_ERROR } from '@/strings'; import { useState } from 'preact/hooks'; import { AccountMenuPane } from '.'; import { FunctionComponent } from 'preact'; -import { Menu } from '../menu/Menu'; -import { MenuItem, MenuItemSeparator, MenuItemType } from '../menu/MenuItem'; +import { Menu } from '../Menu/Menu'; +import { MenuItem, MenuItemSeparator, MenuItemType } from '../Menu/MenuItem'; +import { WorkspaceSwitcherOption } from './WorkspaceSwitcher/WorkspaceSwitcherOption'; +import { ApplicationGroup } from '@/ui_models/application_group'; type Props = { appState: AppState; application: WebApplication; + mainApplicationGroup: ApplicationGroup; setMenuPane: (pane: AccountMenuPane) => void; closeMenu: () => void; }; @@ -21,7 +24,7 @@ type Props = { const iconClassName = 'color-neutral mr-2'; export const GeneralAccountMenu: FunctionComponent = observer( - ({ application, appState, setMenuPane, closeMenu }) => { + ({ application, appState, setMenuPane, closeMenu, mainApplicationGroup }) => { const [isSyncingInProgress, setIsSyncingInProgress] = useState(false); const [lastSyncDate, setLastSyncDate] = useState( formatLastSyncDate(application.sync.getLastSyncDate() as Date) @@ -54,9 +57,12 @@ export const GeneralAccountMenu: FunctionComponent = observer( const user = application.getUser(); + const CREATE_ACCOUNT_INDEX = 1; + const SWITCHER_INDEX = 0; + return ( <> -
+
Account
@@ -66,10 +72,10 @@ export const GeneralAccountMenu: FunctionComponent = observer( <>
You're signed in as:
-
{user.email}
+
{user.email}
{application.getHost()}
-
+
{isSyncingInProgress ? (
@@ -106,12 +112,20 @@ export const GeneralAccountMenu: FunctionComponent = observer(
)} -
+ + + {user ? ( = observer( }} > - Sign out and clear local data + Sign out workspace ) : null} diff --git a/app/assets/javascripts/components/AccountMenu/SignIn.tsx b/app/assets/javascripts/components/AccountMenu/SignIn.tsx index 7886ab769..74578be4f 100644 --- a/app/assets/javascripts/components/AccountMenu/SignIn.tsx +++ b/app/assets/javascripts/components/AccountMenu/SignIn.tsx @@ -3,7 +3,7 @@ import { AppState } from '@/ui_models/app_state'; import { isDev } from '@/utils'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; -import { useEffect, useRef, useState } from 'preact/hooks'; +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { AccountMenuPane } from '.'; import { Button } from '../Button'; import { Checkbox } from '../Checkbox'; @@ -25,10 +25,12 @@ export const SignInPane: FunctionComponent = observer( const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [isEphemeral, setIsEphemeral] = useState(false); + const [isStrictSignin, setIsStrictSignin] = useState(false); const [isSigningIn, setIsSigningIn] = useState(false); const [showPassword, setShowPassword] = useState(false); const [shouldMergeLocal, setShouldMergeLocal] = useState(true); + const [isVault, setIsVault] = useState(false); const emailInputRef = useRef(null); const passwordInputRef = useRef(null); @@ -106,6 +108,16 @@ export const SignInPane: FunctionComponent = observer( } }; + const onVaultChange = useCallback( + (newIsVault: boolean, vaultedEmail?: string) => { + setIsVault(newIsVault); + if (newIsVault && vaultedEmail) { + setEmail(vaultedEmail); + } + }, + [setEmail] + ); + const handleSignInFormSubmit = (e: Event) => { e.preventDefault(); @@ -145,7 +157,7 @@ export const SignInPane: FunctionComponent = observer( onChange={handleEmailChange} onFocus={resetInvalid} onKeyDown={handleKeyDown} - disabled={isSigningIn} + disabled={isSigningIn || isVault} ref={emailInputRef} /> = observer( appState={appState} application={application} disabled={isSigningIn} - > -
- - - - -
- + onVaultChange={onVaultChange} + onStrictSignInChange={handleStrictSigninChange} + /> ); } diff --git a/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceMenuItem.tsx b/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceMenuItem.tsx new file mode 100644 index 000000000..5f222726f --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceMenuItem.tsx @@ -0,0 +1,82 @@ +import { Icon } from '@/components/Icon'; +import { MenuItem, MenuItemType } from '@/components/Menu/MenuItem'; +import { KeyboardKey } from '@/services/ioService'; +import { ApplicationDescriptor } from '@standardnotes/snjs/dist/@types'; +import { FunctionComponent } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; + +type Props = { + descriptor: ApplicationDescriptor; + onClick: () => void; + onDelete: () => void; + renameDescriptor: (label: string) => void; +}; + +export const WorkspaceMenuItem: FunctionComponent = ({ + descriptor, + onClick, + onDelete, + renameDescriptor, +}) => { + const [isRenaming, setIsRenaming] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (isRenaming) { + inputRef.current?.focus(); + } + }, [isRenaming]); + + const handleInputKeyDown = (event: KeyboardEvent) => { + if (event.key === KeyboardKey.Enter) { + inputRef.current?.blur(); + } + }; + + const handleInputBlur = (event: FocusEvent) => { + const name = (event.target as HTMLInputElement).value; + renameDescriptor(name); + setIsRenaming(false); + }; + + return ( + +
+ {isRenaming ? ( + + ) : ( +
{descriptor.label}
+ )} + {descriptor.primary && ( +
+ + +
+ )} +
+
+ ); +}; diff --git a/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx b/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx new file mode 100644 index 000000000..1679cf80f --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx @@ -0,0 +1,69 @@ +import { ApplicationGroup } from '@/ui_models/application_group'; +import { AppState } from '@/ui_models/app_state'; +import { ApplicationDescriptor } from '@standardnotes/snjs'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useEffect, useState } from 'preact/hooks'; +import { Icon } from '../../Icon'; +import { Menu } from '../../Menu/Menu'; +import { MenuItem, MenuItemSeparator, MenuItemType } from '../../Menu/MenuItem'; +import { WorkspaceMenuItem } from './WorkspaceMenuItem'; + +type Props = { + mainApplicationGroup: ApplicationGroup; + appState: AppState; + isOpen: boolean; +}; + +export const WorkspaceSwitcherMenu: FunctionComponent = observer( + ({ mainApplicationGroup, appState, isOpen }) => { + const [applicationDescriptors, setApplicationDescriptors] = useState< + ApplicationDescriptor[] + >([]); + + useEffect(() => { + const removeAppGroupObserver = + mainApplicationGroup.addApplicationChangeObserver(() => { + const applicationDescriptors = mainApplicationGroup.getDescriptors(); + setApplicationDescriptors(applicationDescriptors); + }); + + return () => { + removeAppGroupObserver(); + }; + }, [mainApplicationGroup]); + + return ( + + {applicationDescriptors.map((descriptor) => ( + { + appState.accountMenu.setSigningOut(true); + }} + onClick={() => { + mainApplicationGroup.loadApplicationForDescriptor(descriptor); + }} + renameDescriptor={(label: string) => + mainApplicationGroup.renameDescriptor(descriptor, label) + } + /> + ))} + + { + mainApplicationGroup.addNewApplication(); + }} + > + + Add another workspace + + + ); + } +); diff --git a/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherOption.tsx b/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherOption.tsx new file mode 100644 index 000000000..d369c3977 --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherOption.tsx @@ -0,0 +1,83 @@ +import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/constants'; +import { ApplicationGroup } from '@/ui_models/application_group'; +import { AppState } from '@/ui_models/app_state'; +import { + calculateSubmenuStyle, + SubmenuStyle, +} from '@/utils/calculateSubmenuStyle'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { Icon } from '../../Icon'; +import { WorkspaceSwitcherMenu } from './WorkspaceSwitcherMenu'; + +type Props = { + mainApplicationGroup: ApplicationGroup; + appState: AppState; +}; + +export const WorkspaceSwitcherOption: FunctionComponent = observer( + ({ mainApplicationGroup, appState }) => { + const buttonRef = useRef(null); + const menuRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const [menuStyle, setMenuStyle] = useState(); + + const toggleMenu = () => { + if (!isOpen) { + const menuPosition = calculateSubmenuStyle(buttonRef.current); + if (menuPosition) { + setMenuStyle(menuPosition); + } + } + + setIsOpen(!isOpen); + }; + + useEffect(() => { + if (isOpen) { + setTimeout(() => { + const newMenuPosition = calculateSubmenuStyle( + buttonRef.current, + menuRef.current + ); + + if (newMenuPosition) { + setMenuStyle(newMenuPosition); + } + }); + } + }, [isOpen]); + + return ( + <> + + {isOpen && ( +
+ +
+ )} + + ); + } +); diff --git a/app/assets/javascripts/components/AccountMenu/index.tsx b/app/assets/javascripts/components/AccountMenu/index.tsx index 8a99b0b93..9e2e1381a 100644 --- a/app/assets/javascripts/components/AccountMenu/index.tsx +++ b/app/assets/javascripts/components/AccountMenu/index.tsx @@ -9,6 +9,7 @@ import { SignInPane } from './SignIn'; import { CreateAccount } from './CreateAccount'; import { ConfirmPassword } from './ConfirmPassword'; import { JSXInternal } from 'preact/src/jsx'; +import { ApplicationGroup } from '@/ui_models/application_group'; export enum AccountMenuPane { GeneralMenu, @@ -21,18 +22,27 @@ type Props = { appState: AppState; application: WebApplication; onClickOutside: () => void; + mainApplicationGroup: ApplicationGroup; }; type PaneSelectorProps = { appState: AppState; application: WebApplication; + mainApplicationGroup: ApplicationGroup; menuPane: AccountMenuPane; setMenuPane: (pane: AccountMenuPane) => void; closeMenu: () => void; }; const MenuPaneSelector: FunctionComponent = observer( - ({ application, appState, menuPane, setMenuPane, closeMenu }) => { + ({ + application, + appState, + menuPane, + setMenuPane, + closeMenu, + mainApplicationGroup, + }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -42,6 +52,7 @@ const MenuPaneSelector: FunctionComponent = observer( @@ -81,7 +92,7 @@ const MenuPaneSelector: FunctionComponent = observer( ); export const AccountMenu: FunctionComponent = observer( - ({ application, appState, onClickOutside }) => { + ({ application, appState, onClickOutside, mainApplicationGroup }) => { const { currentPane, setCurrentPane, @@ -123,6 +134,7 @@ export const AccountMenu: FunctionComponent = observer( { - private removeAppGroupObserver: any; - activeApplication!: WebApplication; - - constructor(props: Props) { - super(props, props.application); - this.removeAppGroupObserver = - props.mainApplicationGroup.addApplicationChangeObserver(() => { - this.activeApplication = props.mainApplicationGroup - .primaryApplication as WebApplication; - this.reloadApplications(); - }); - } - - reloadApplications() { - this.setState({ - descriptors: this.props.mainApplicationGroup.getDescriptors(), - }); - } - - addNewApplication = () => { - this.dismiss(); - this.props.mainApplicationGroup.addNewApplication(); - }; - - selectDescriptor = (descriptor: ApplicationDescriptor) => { - this.dismiss(); - this.props.mainApplicationGroup.loadApplicationForDescriptor(descriptor); - }; - - inputForDescriptor(descriptor: ApplicationDescriptor) { - return document.getElementById(`input-${descriptor.identifier}`); - } - - renameDescriptor = (event: Event, descriptor: ApplicationDescriptor) => { - event.stopPropagation(); - - this.setState({ editingDescriptor: descriptor }); - - setTimeout(() => { - this.inputForDescriptor(descriptor)?.focus(); - }); - }; - - submitRename = () => { - this.props.mainApplicationGroup.renameDescriptor( - this.state.editingDescriptor!, - this.state.editingDescriptor!.label - ); - this.setState({ editingDescriptor: undefined }); - }; - - deinit() { - super.deinit(); - this.removeAppGroupObserver(); - this.removeAppGroupObserver = undefined; - } - - onDescriptorInputChange = ( - descriptor: ApplicationDescriptor, - { currentTarget }: JSX.TargetedEvent - ) => { - descriptor.label = currentTarget.value; - }; - - dismiss = () => { - this.dismissModal(); - }; - - render() { - return ( -
-
-
-
- -
-
- ); - } -} diff --git a/app/assets/javascripts/components/ApplicationView.tsx b/app/assets/javascripts/components/ApplicationView.tsx index 6210d8da1..9f5191c1c 100644 --- a/app/assets/javascripts/components/ApplicationView.tsx +++ b/app/assets/javascripts/components/ApplicationView.tsx @@ -1,5 +1,5 @@ import { ApplicationGroup } from '@/ui_models/application_group'; -import { getPlatformString } from '@/utils'; +import { getPlatformString, getWindowUrlParams } from '@/utils'; import { AppStateEvent, PanelResizedData } from '@/ui_models/app_state'; import { ApplicationEvent, @@ -16,10 +16,10 @@ import { NotesView } from '@/components/NotesView'; import { NoteGroupView } from '@/components/NoteGroupView'; import { Footer } from '@/components/Footer'; import { SessionsModal } from '@/components/SessionsModal'; -import { PreferencesViewWrapper } from '@/preferences/PreferencesViewWrapper'; +import { PreferencesViewWrapper } from '@/components/Preferences/PreferencesViewWrapper'; import { ChallengeModal } from '@/components/ChallengeModal'; import { NotesContextMenu } from '@/components/NotesContextMenu'; -import { PurchaseFlowWrapper } from '@/purchaseFlow/PurchaseFlowWrapper'; +import { PurchaseFlowWrapper } from '@/components/PurchaseFlow/PurchaseFlowWrapper'; import { render } from 'preact'; import { PermissionsModal } from './PermissionsModal'; import { RevisionHistoryModalWrapper } from './RevisionHistoryModal/RevisionHistoryModalWrapper'; @@ -27,6 +27,7 @@ import { PremiumModalProvider } from './Premium'; import { ConfirmSignoutContainer } from './ConfirmSignoutModal'; import { TagsContextMenu } from './Tags/TagContextMenu'; import { ToastContainer } from '@standardnotes/stylekit'; +import { FilePreviewModalProvider } from './Files/FilePreviewModalProvider'; type Props = { application: WebApplication; @@ -143,15 +144,12 @@ export class ApplicationView extends PureComponent { } async handleDemoSignInFromParams() { - if ( - window.location.href.includes('demo') && - !this.application.hasAccount() - ) { - await this.application.setCustomHost( - 'https://syncing-server-demo.standardnotes.com' - ); - this.application.signIn('demo@standardnotes.org', 'password'); + const token = getWindowUrlParams().get('demo-token'); + if (!token || this.application.hasAccount()) { + return; } + + await this.application.sessions.populateSessionFromDemoShareToken(token); } presentPermissionsDialog = (dialog: PermissionDialog) => { @@ -175,88 +173,78 @@ export class ApplicationView extends PureComponent { const renderAppContents = !this.state.needsUnlock && this.state.launched; return ( - -
- {renderAppContents && ( -
- - - - - -
- )} - - {renderAppContents && ( - <> -