import { WebApplicationGroup } from '@/Application/WebApplicationGroup' import { getPlatformString } from '@/Utils' import { ApplicationEvent, Challenge, removeFromArray, WebAppEvent } from '@standardnotes/snjs' import { alertDialog, isIOS, RouteType } from '@standardnotes/ui-services' import { WebApplication } from '@/Application/WebApplication' import Footer from '@/Components/Footer/Footer' import SessionsModal from '@/Components/SessionsModal/SessionsModal' import PreferencesViewWrapper from '@/Components/Preferences/PreferencesViewWrapper' import ChallengeModal from '@/Components/ChallengeModal/ChallengeModal' import NotesContextMenu from '@/Components/NotesContextMenu/NotesContextMenu' import PurchaseFlowWrapper from '@/Components/PurchaseFlow/PurchaseFlowWrapper' import { FunctionComponent, useCallback, useEffect, useMemo, useState, lazy, useRef } from 'react' import RevisionHistoryModal from '@/Components/RevisionHistoryModal/RevisionHistoryModal' import PremiumModalProvider from '@/Hooks/usePremiumModal' import ConfirmSignoutContainer from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal' import { addToast, ToastContainer, ToastType } from '@standardnotes/toast' import FilePreviewModalWrapper from '@/Components/FilePreview/FilePreviewModal' import FileContextMenuWrapper from '@/Components/FileContextMenu/FileContextMenu' import PermissionsModalWrapper from '@/Components/PermissionsModal/PermissionsModalWrapper' import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper' import FileDragNDropProvider from '../FileDragNDropProvider' import ResponsivePaneProvider from '../Panes/ResponsivePaneProvider' import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler' import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal' import ApplicationProvider from '../ApplicationProvider' import CommandProvider from '../CommandProvider' import PanesSystemComponent from '../Panes/PanesSystemComponent' import DotOrgNotice from './DotOrgNotice' import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider' import ImportModal from '../ImportModal/ImportModal' import IosKeyboardClose from '../IosKeyboardClose/IosKeyboardClose' import EditorWidthSelectionModalWrapper from '../EditorWidthSelectionModal/EditorWidthSelectionModal' import { ProtectionEvent } from '@standardnotes/services' import KeyboardShortcutsModal from '../KeyboardShortcutsHelpModal/KeyboardShortcutsHelpModal' type Props = { application: WebApplication mainApplicationGroup: WebApplicationGroup } const LazyLoadedClipperView = lazy(() => import('../ClipperView/ClipperView')) const ApplicationView: FunctionComponent = ({ application, mainApplicationGroup }) => { const platformString = getPlatformString() const [launched, setLaunched] = useState(false) const [needsUnlock, setNeedsUnlock] = useState(true) const [challenges, setChallenges] = useState([]) const currentWriteErrorDialog = useRef | null>(null) const currentLoadErrorDialog = useRef | null>(null) useEffect(() => { const desktopService = application.desktopManager if (desktopService) { application.componentManager.setDesktopManager(desktopService) } application .prepareForLaunch({ receiveChallenge: async (challenge) => { const challengesCopy = challenges.slice() challengesCopy.push(challenge) setChallenges(challengesCopy) }, }) .then(() => { void application.launch() }) .catch(console.error) // eslint-disable-next-line react-hooks/exhaustive-deps }, [application]) const removeChallenge = useCallback( (challenge: Challenge) => { const challengesCopy = challenges.slice() removeFromArray(challengesCopy, challenge) setChallenges(challengesCopy) }, [challenges], ) const onAppStart = useCallback(() => { setNeedsUnlock(application.hasPasscode()) }, [application]) const handleDemoSignInFromParamsIfApplicable = useCallback(() => { const route = application.routeService.getRoute() if (route.type !== RouteType.Demo) { return } const token = route.demoParams.token if (!token || application.hasAccount()) { return } const status = application.status.addMessage('Preparing demo...') void application.user.populateSessionFromDemoShareToken(token).then(() => { application.status.removeMessage(status) application.hideAccountMenu() }) }, [application]) const onAppLaunch = useCallback(() => { setLaunched(true) setNeedsUnlock(false) handleDemoSignInFromParamsIfApplicable() }, [handleDemoSignInFromParamsIfApplicable]) useEffect(() => { if (application.isStarted()) { onAppStart() } if (application.isLaunched()) { onAppLaunch() } const removeAppObserver = application.addEventObserver(async (eventName) => { if (eventName === ApplicationEvent.Started) { onAppStart() } else if (eventName === ApplicationEvent.Launched) { onAppLaunch() } else if (eventName === ApplicationEvent.LocalDatabaseReadError) { if (!currentLoadErrorDialog.current) { alertDialog({ text: 'Unable to load local database. Please restart the app and try again.', }) .then(() => { currentLoadErrorDialog.current = null }) .catch(console.error) } } else if (eventName === ApplicationEvent.LocalDatabaseWriteError) { if (!currentWriteErrorDialog.current) { currentWriteErrorDialog.current = alertDialog({ text: 'Unable to write to local database. Please restart the app and try again.', }) .then(() => { currentWriteErrorDialog.current = null }) .catch(console.error) } } else if (eventName === ApplicationEvent.SyncTooManyRequests) { addToast({ type: ToastType.Error, message: 'Too many requests. Please try again later.', }) } }) return () => { removeAppObserver() } }, [application, onAppLaunch, onAppStart]) useEffect(() => { const disposer = application.protections.addEventObserver(async (eventName) => { if (eventName === ProtectionEvent.BiometricsSoftLockEngaged) { setNeedsUnlock(true) } else if (eventName === ProtectionEvent.BiometricsSoftLockDisengaged) { setNeedsUnlock(false) } }) return disposer }, [application]) useEffect(() => { const removeObserver = application.addWebEventObserver(async (eventName) => { if (eventName === WebAppEvent.WindowDidFocus || eventName === WebAppEvent.WindowDidBlur) { if (!(await application.protections.isLocked())) { application.sync.sync().catch(console.error) } } }) return () => { removeObserver() } }, [application]) const renderAppContents = useMemo(() => { return !needsUnlock && launched }, [needsUnlock, launched]) const renderChallenges = useCallback(() => { return challenges.map((challenge) => (
)) }, [challenges, mainApplicationGroup, removeChallenge, application]) if (!renderAppContents) { return ( {renderChallenges()} ) } const route = application.routeService.getRoute() if (route.type === RouteType.AppViewRoute && route.appViewRouteParam === 'extension') { return ( {renderChallenges()} ) } return (
<>
) } export default ApplicationView