mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
web: improve app startup
This commit is contained in:
committed by
Abdullah Atta
parent
ea9b10ff23
commit
1f3e7574f5
1867
apps/web/package-lock.json
generated
1867
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -37,13 +37,14 @@ import {
|
||||
ImperativePanelHandle
|
||||
} from "react-resizable-panels";
|
||||
import GlobalMenuWrapper from "./components/global-menu-wrapper";
|
||||
import AppEffects from "./app-effects";
|
||||
|
||||
new WebExtensionRelay();
|
||||
|
||||
// const GlobalMenuWrapper = React.lazy(
|
||||
// () => import("./components/global-menu-wrapper")
|
||||
// );
|
||||
const AppEffects = React.lazy(() => import("./app-effects"));
|
||||
// const AppEffects = React.lazy(() => import("./app-effects"));
|
||||
const MobileAppEffects = React.lazy(() => import("./app-effects.mobile"));
|
||||
const HashRouter = React.lazy(() => import("./components/hash-router"));
|
||||
|
||||
@@ -51,6 +52,7 @@ function App() {
|
||||
const isMobile = useMobile();
|
||||
const [show, setShow] = useState(true);
|
||||
const isFocusMode = useStore((store) => store.isFocusMode);
|
||||
console.timeEnd("loading app");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -58,7 +60,6 @@ function App() {
|
||||
<div id="menu-wrapper">
|
||||
<GlobalMenuWrapper />
|
||||
</div>
|
||||
<AppEffects setShow={setShow} />
|
||||
{isMobile && (
|
||||
<MobileAppEffects
|
||||
sliderId="slider"
|
||||
@@ -67,6 +68,7 @@ function App() {
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
<AppEffects setShow={setShow} />
|
||||
|
||||
<Flex
|
||||
id="app"
|
||||
@@ -126,7 +128,7 @@ function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
|
||||
|
||||
useEffect(() => {
|
||||
const size = navPane.current?.getSize();
|
||||
// Toggle `isNarrow` to true if panel size isn't set to narrow by user
|
||||
// Toggle `isNarrow` to true if panel size isn't set to narrow by user
|
||||
setIsNarrow((size && size <= 5) || isTablet);
|
||||
}, [isTablet]);
|
||||
|
||||
@@ -224,7 +226,9 @@ function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
|
||||
bg: "background"
|
||||
}}
|
||||
>
|
||||
{<HashRouter />}
|
||||
<Suspense fallback={<EditorLoader />}>
|
||||
<HashRouter />
|
||||
</Suspense>
|
||||
</Flex>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
|
||||
@@ -19,10 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
||||
import Config from "./utils/config";
|
||||
|
||||
// import { initializeLogger, logger } from "./utils/logger";
|
||||
import type { AuthProps } from "./views/auth";
|
||||
import { initializeFeatureChecks } from "./utils/feature-check";
|
||||
import { initializeLogger } from "./utils/logger";
|
||||
|
||||
type Route<TProps = null> = {
|
||||
component: () => Promise<{
|
||||
@@ -146,12 +145,14 @@ function isSessionExpired(path: Routes): RouteWithPath<AuthProps> | null {
|
||||
export async function init() {
|
||||
await initializeFeatureChecks();
|
||||
|
||||
await import("./utils/logger").then(({ initializeLogger }) =>
|
||||
initializeLogger()
|
||||
);
|
||||
|
||||
const { path, route } = getRoute();
|
||||
return { ...route, path };
|
||||
|
||||
const [{ default: Component }] = await Promise.all([
|
||||
route.component(),
|
||||
initializeLogger()
|
||||
]);
|
||||
|
||||
return { Component, path, props: route.props };
|
||||
}
|
||||
|
||||
function shouldSkipInitiation() {
|
||||
|
||||
@@ -29,6 +29,9 @@ import {
|
||||
import { useKeyStore } from "../../interfaces/key-store";
|
||||
import { isFeatureSupported } from "../../utils/feature-check";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { db } from "../../common/db";
|
||||
import { createDialect } from "../../common/sqlite";
|
||||
import { getDeviceInfo } from "../../utils/platform";
|
||||
|
||||
export function GlobalErrorHandler(props: PropsWithChildren) {
|
||||
const { showBoundary } = useErrorBoundary();
|
||||
@@ -164,9 +167,6 @@ export function ErrorComponent({ error, resetErrorBoundary }: FallbackProps) {
|
||||
variant="secondary"
|
||||
sx={{ alignSelf: "start", px: 30, mt: 1 }}
|
||||
onClick={async () => {
|
||||
const { getDeviceInfo } = await import(
|
||||
"../../dialogs/issue-dialog"
|
||||
);
|
||||
const mailto = new URL("mailto:support@streetwriters.co");
|
||||
mailto.searchParams.set(
|
||||
"body",
|
||||
@@ -223,7 +223,6 @@ function getErrorHelp(props: FallbackProps) {
|
||||
explanation: strings.searchIndexCorrupt(),
|
||||
action: strings.searchIndexCorruptFix(),
|
||||
fix: async () => {
|
||||
const { db } = await import("../../common/db");
|
||||
await db.lookup.rebuild();
|
||||
resetErrorBoundary();
|
||||
}
|
||||
@@ -240,7 +239,6 @@ function errorToString(error: unknown) {
|
||||
}
|
||||
|
||||
async function resetDatabase() {
|
||||
const { createDialect } = await import("../../common/sqlite");
|
||||
const multiTab = !!globalThis.SharedWorker && isFeatureSupported("opfs");
|
||||
await useKeyStore.getState().clear();
|
||||
const dialect = createDialect({
|
||||
|
||||
@@ -18,12 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Flex, Link, Text } from "@theme-ui/components";
|
||||
import { appVersion } from "../utils/version";
|
||||
import Field from "../components/field";
|
||||
import Dialog from "../components/dialog";
|
||||
import platform from "platform";
|
||||
import { useState } from "react";
|
||||
import { isUserPremium } from "../hooks/use-is-user-premium";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
import { store as userstore } from "../stores/user-store";
|
||||
|
||||
@@ -32,6 +29,8 @@ import { Debug } from "@notesnook/core";
|
||||
import { ConfirmDialog } from "./confirm";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { getDeviceInfo } from "../utils/platform";
|
||||
import { isUserPremium } from "../hooks/use-is-user-premium";
|
||||
|
||||
const PLACEHOLDERS = {
|
||||
title: strings.issueTitlePlaceholder(),
|
||||
@@ -136,7 +135,7 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
||||
/>
|
||||
</Text>
|
||||
<Text variant="subBody" mt={1}>
|
||||
{getDeviceInfo()
|
||||
{getDeviceInfo([`Pro: ${isUserPremium()}`])
|
||||
.split("\n")
|
||||
.map((t) => (
|
||||
<>
|
||||
@@ -161,19 +160,10 @@ function showIssueReportedDialog({ url }: { url: string }) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getDeviceInfo() {
|
||||
const version = appVersion.formatted;
|
||||
const os = platform.os;
|
||||
const browser = `${platform.name} ${platform.version}`;
|
||||
|
||||
return `App version: ${version}
|
||||
OS: ${os}
|
||||
Browser: ${browser}
|
||||
Pro: ${isUserPremium()}`;
|
||||
}
|
||||
|
||||
const BODY_TEMPLATE = (body: string) => {
|
||||
const info = `**Device information:**\n${getDeviceInfo()}`;
|
||||
const info = `**Device information:**\n${getDeviceInfo([
|
||||
`Pro: ${isUserPremium()}`
|
||||
])}`;
|
||||
if (!body) return info;
|
||||
return `${body}\n\n${info}`;
|
||||
};
|
||||
|
||||
@@ -19,12 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { initializeDatabase } from "../common/db";
|
||||
|
||||
const memory = {
|
||||
isDatabaseLoaded: false
|
||||
};
|
||||
export async function loadDatabase(persistence: "db" | "memory" = "db") {
|
||||
if (memory.isDatabaseLoaded) return;
|
||||
|
||||
await initializeDatabase(persistence);
|
||||
memory.isDatabaseLoaded = true;
|
||||
let isDatabaseLoaded: false | Promise<any> = false;
|
||||
export function loadDatabase(persistence: "db" | "memory" = "db") {
|
||||
isDatabaseLoaded = isDatabaseLoaded || initializeDatabase(persistence);
|
||||
return isDatabaseLoaded;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useEffect, useState } from "react";
|
||||
import config from "../utils/config";
|
||||
|
||||
export function usePersistentState<T>(key: string, def: T) {
|
||||
const defState = config.get<T>(key, def);
|
||||
export function usePersistentState<T>(key: string | undefined, def: T) {
|
||||
const defState = key ? config.get<T>(key, def) : def;
|
||||
const [value, setValue] = useState(defState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!key) return;
|
||||
config.set<T>(key, value);
|
||||
}, [key, value]);
|
||||
|
||||
|
||||
@@ -176,6 +176,16 @@
|
||||
<svg style="height: 120px">
|
||||
<use href="#themed-logo" />
|
||||
</svg>
|
||||
<span
|
||||
style="
|
||||
margin-top: 10px;
|
||||
font-family: monospace;
|
||||
color: var(--paragraph);
|
||||
font-size: 0.8rem;
|
||||
"
|
||||
>
|
||||
Decrypting your notes
|
||||
</span>
|
||||
</div>
|
||||
<div id="dialogContainer"></div>
|
||||
<div id="floatingViewContainer"></div>
|
||||
|
||||
@@ -28,17 +28,6 @@ import Config from "./utils/config";
|
||||
import { setI18nGlobal, Messages } from "@notesnook/intl";
|
||||
import { i18n } from "@lingui/core";
|
||||
|
||||
const locale = import.meta.env.DEV
|
||||
? import("@notesnook/intl/locales/$pseudo-LOCALE.json")
|
||||
: import("@notesnook/intl/locales/$en.json");
|
||||
locale.then(({ default: locale }) => {
|
||||
i18n.load({
|
||||
en: locale.messages as unknown as Messages
|
||||
});
|
||||
i18n.activate("en");
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
const colorScheme = JSON.parse(
|
||||
window.localStorage.getItem("colorScheme") || '"light"'
|
||||
);
|
||||
@@ -55,6 +44,21 @@ if (theme) {
|
||||
if (stylesheet) stylesheet.innerHTML = css;
|
||||
} else stylesheet?.remove();
|
||||
|
||||
const locale = import.meta.env.DEV
|
||||
? import("@notesnook/intl/locales/$pseudo-LOCALE.json")
|
||||
: import("@notesnook/intl/locales/$en.json");
|
||||
locale.then(({ default: locale }) => {
|
||||
i18n.load({
|
||||
en: locale.messages as unknown as Messages
|
||||
});
|
||||
i18n.activate("en");
|
||||
|
||||
import("./root").then(({ startApp }) => {
|
||||
startApp();
|
||||
});
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
if (!IS_DESKTOP_APP && !IS_TESTING) {
|
||||
// logger.info("Initializing service worker...");
|
||||
|
||||
@@ -76,7 +80,3 @@ if (!IS_DESKTOP_APP && !IS_TESTING) {
|
||||
|
||||
// window.addEventListener("beforeinstallprompt", () => showInstallNotice());
|
||||
}
|
||||
|
||||
import("./root").then(({ startApp }) => {
|
||||
startApp();
|
||||
});
|
||||
|
||||
@@ -25,12 +25,14 @@ import {
|
||||
ErrorComponent,
|
||||
GlobalErrorHandler
|
||||
} from "./components/error-boundary";
|
||||
import { TitleBar } from "./components/title-bar";
|
||||
import { desktop } from "./common/desktop-bridge";
|
||||
import { useKeyStore } from "./interfaces/key-store";
|
||||
import Config from "./utils/config";
|
||||
import { usePromise } from "@notesnook/common";
|
||||
import { AuthProps } from "./views/auth";
|
||||
import { loadDatabase } from "./hooks/use-database";
|
||||
import AppLock from "./views/app-lock";
|
||||
import { Text } from "@theme-ui/components";
|
||||
|
||||
export async function startApp() {
|
||||
const rootElement = document.getElementById("root");
|
||||
@@ -43,16 +45,24 @@ export async function startApp() {
|
||||
.query()
|
||||
?.then((s) => s.nativeTitlebar));
|
||||
|
||||
const TitleBar = window.hasNativeTitlebar
|
||||
? () => <></>
|
||||
: await import("./components/title-bar").then((m) => m.TitleBar);
|
||||
|
||||
try {
|
||||
const { component, props, path } = await init();
|
||||
const { Component, props, path } = await init();
|
||||
|
||||
await useKeyStore.getState().init();
|
||||
|
||||
const { default: AppLock } = await import("./views/app-lock");
|
||||
loadDatabase(
|
||||
path !== "/sessionexpired" || Config.get("sessionExpired", false)
|
||||
? "db"
|
||||
: "memory"
|
||||
);
|
||||
|
||||
root.render(
|
||||
<>
|
||||
{hasNativeTitlebar ? null : <TitleBar />}
|
||||
<TitleBar />
|
||||
<ErrorBoundary>
|
||||
<GlobalErrorHandler>
|
||||
<BaseThemeProvider
|
||||
@@ -61,7 +71,7 @@ export async function startApp() {
|
||||
>
|
||||
<AppLock>
|
||||
<RouteWrapper
|
||||
component={component}
|
||||
Component={Component}
|
||||
path={path}
|
||||
routeProps={props}
|
||||
/>
|
||||
@@ -75,7 +85,7 @@ export async function startApp() {
|
||||
console.error(e);
|
||||
root.render(
|
||||
<>
|
||||
{hasNativeTitlebar ? null : <TitleBar />}
|
||||
<TitleBar />
|
||||
<ErrorComponent
|
||||
error={e}
|
||||
resetErrorBoundary={() => window.location.reload()}
|
||||
@@ -86,22 +96,18 @@ export async function startApp() {
|
||||
}
|
||||
|
||||
function RouteWrapper(props: {
|
||||
component: () => Promise<{
|
||||
default: (props: AuthProps) => JSX.Element;
|
||||
}>;
|
||||
Component: (props: AuthProps) => JSX.Element;
|
||||
path: Routes;
|
||||
routeProps: AuthProps | null;
|
||||
}) {
|
||||
const { component, path, routeProps } = props;
|
||||
const { Component, path, routeProps } = props;
|
||||
const result = usePromise(async () => {
|
||||
const { loadDatabase } = await import("./hooks/use-database");
|
||||
await loadDatabase(
|
||||
path !== "/sessionexpired" || Config.get("sessionExpired", false)
|
||||
? "db"
|
||||
: "memory"
|
||||
);
|
||||
return (await component()).default;
|
||||
}, [component, path]);
|
||||
}, [path]);
|
||||
|
||||
if (result.status === "rejected") {
|
||||
throw result.reason instanceof Error
|
||||
@@ -124,12 +130,22 @@ function RouteWrapper(props: {
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<svg style={{ height: 120 }}>
|
||||
<svg
|
||||
style={{
|
||||
height: 120,
|
||||
transform: "scale(1)",
|
||||
animation: "pulse 2s infinite",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<use href="#themed-logo" />
|
||||
</svg>
|
||||
<Text variant="body" sx={{ fontFamily: "monospace" }}>
|
||||
Decrypting your notes
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
return <result.value route={routeProps?.route || "login:email"} />;
|
||||
return <Component route={routeProps?.route || "login:email"} />;
|
||||
}
|
||||
|
||||
if (import.meta.hot) import.meta.hot.accept();
|
||||
|
||||
@@ -17,6 +17,9 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import platform from "platform";
|
||||
import { appVersion } from "../utils/version";
|
||||
|
||||
export function getPlatform() {
|
||||
if (window.os) return window.os();
|
||||
|
||||
@@ -131,3 +134,14 @@ export function isMac() {
|
||||
export function isMacStoreApp() {
|
||||
return window.os ? window.os() === "mas" : false;
|
||||
}
|
||||
|
||||
export function getDeviceInfo(extras: string[] = []) {
|
||||
const version = appVersion.formatted;
|
||||
const os = platform.os;
|
||||
const browser = `${platform.name} ${platform.version}`;
|
||||
|
||||
return `App version: ${version}
|
||||
OS: ${os}
|
||||
Browser: ${browser}
|
||||
${extras.join("\n")}`;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,10 @@ export default defineConfig({
|
||||
"@mdi/js",
|
||||
"@mdi/react",
|
||||
"@emotion/react",
|
||||
"katex"
|
||||
"katex",
|
||||
"react-modal",
|
||||
"dayjs",
|
||||
"@streetwriters/kysely"
|
||||
],
|
||||
|
||||
alias: [
|
||||
|
||||
Reference in New Issue
Block a user