refactor: improve app startup perf many times

This commit is contained in:
thecodrr
2021-10-23 10:58:44 +05:00
parent 98998e27a4
commit 45d33d90f6
66 changed files with 1120 additions and 784 deletions

View File

@@ -7,8 +7,8 @@ const gitHash = execSync("git rev-parse --short HEAD").toString().trim();
module.exports = { module.exports = {
all: { all: {
UV_THREADPOOL_SIZE: IS_CI ? NUM_CPUS : 2, UV_THREADPOOL_SIZE: IS_CI ? NUM_CPUS : 2,
GENERATE_SOURCEMAP: false, // GENERATE_SOURCEMAP: false,
INLINE_RUNTIME_CHUNK: false, // INLINE_RUNTIME_CHUNK: false,
DISABLE_ESLINT_PLUGIN: true, DISABLE_ESLINT_PLUGIN: true,
REACT_APP_GIT_HASH: gitHash, REACT_APP_GIT_HASH: gitHash,
}, },

View File

@@ -5,10 +5,11 @@ const { createNote, NOTE, getTestId } = require("./utils");
*/ */
var page = null; var page = null;
global.page = null; global.page = null;
test.beforeEach(async ({ page: _page }) => { test.beforeEach(async ({ page: _page, baseURL }) => {
global.page = _page; global.page = _page;
page = _page; page = _page;
await page.goto("http://localhost:3000/"); await page.goto(baseURL);
await page.waitForSelector(getTestId("routeHeader"));
}); });
test("focus mode", async () => { test("focus mode", async () => {

View File

@@ -5,6 +5,7 @@ const { getTestId } = require("./utils");
test.beforeEach(async ({ page, baseURL }) => { test.beforeEach(async ({ page, baseURL }) => {
await page.goto(baseURL); await page.goto(baseURL);
await page.waitForSelector(getTestId("routeHeader"));
}); });
function createRoute(key, header) { function createRoute(key, header) {

View File

@@ -17,9 +17,10 @@ const { checkNotePresence, isPresent } = require("./utils/conditions");
*/ */
global.page = null; global.page = null;
test.beforeEach(async ({ page: _page }) => { test.beforeEach(async ({ page: _page, baseURL }) => {
global.page = _page; global.page = _page;
await page.goto("http://localhost:3000/"); await page.goto(baseURL);
await page.waitForSelector(getTestId("routeHeader"));
}); });
async function fillNotebookDialog(notebook) { async function fillNotebookDialog(notebook) {

View File

@@ -203,9 +203,10 @@ test.describe("run tests independently", () => {
* @type {Page} * @type {Page}
*/ */
global.page = null; global.page = null;
test.beforeEach(async ({ page: _page }) => { test.beforeEach(async ({ page: _page, baseURL }) => {
global.page = _page; global.page = _page;
await page.goto("http://localhost:3000/"); await page.goto(baseURL);
await page.waitForSelector(getTestId("routeHeader"));
}); });
test("create a note", async () => { test("create a note", async () => {

View File

@@ -15,6 +15,7 @@ test.beforeEach(async ({ page: _page, baseURL }) => {
global.page = _page; global.page = _page;
page = _page; page = _page;
await page.goto(baseURL); await page.goto(baseURL);
await page.waitForSelector(getTestId("routeHeader"));
}); });
const USER = { const USER = {

View File

@@ -14,7 +14,6 @@ async function configureAutoUpdater() {
sendMessageToRenderer(EVENTS.updateAvailable, info); sendMessageToRenderer(EVENTS.updateAvailable, info);
}); });
autoUpdater.addListener("download-progress", (progress) => { autoUpdater.addListener("download-progress", (progress) => {
console.log("Downloading", progress);
sendMessageToRenderer(EVENTS.updateDownloadProgress, progress); sendMessageToRenderer(EVENTS.updateDownloadProgress, progress);
}); });
autoUpdater.addListener("update-downloaded", (info) => { autoUpdater.addListener("update-downloaded", (info) => {

View File

@@ -23,7 +23,7 @@
"emotion-theming": "^10.0.19", "emotion-theming": "^10.0.19",
"fast-sort": "^2.1.1", "fast-sort": "^2.1.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"framer-motion": "^3.3.0", "framer-motion": "^4.1.17",
"hash-wasm": "^4.9.0", "hash-wasm": "^4.9.0",
"hotkeys-js": "^3.8.3", "hotkeys-js": "^3.8.3",
"immer": "^9.0.6", "immer": "^9.0.6",
@@ -77,6 +77,7 @@
"patch-package": "^6.4.7", "patch-package": "^6.4.7",
"source-map-explorer": "^2.5.2", "source-map-explorer": "^2.5.2",
"typescript": "^4.1.5", "typescript": "^4.1.5",
"webpack-bundle-analyzer": "^4.5.0",
"worker-loader": "^3.0.8", "worker-loader": "^3.0.8",
"zx": "^2.0.0" "zx": "^2.0.0"
}, },

View File

@@ -1,68 +0,0 @@
/* open-sans-regular - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: normal;
font-weight: 400;
src: local(""),
url("/fonts/open-sans-v20-latin-regular.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("/fonts/open-sans-v20-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-italic - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: italic;
font-weight: 400;
src: local(""), url("/fonts/open-sans-v20-latin-italic.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("/fonts/open-sans-v20-latin-italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-600 - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: normal;
font-weight: 600;
src: local(""), url("/fonts/open-sans-v20-latin-600.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("/fonts/open-sans-v20-latin-600.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-600italic - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: italic;
font-weight: 600;
src: local(""),
url("/fonts/open-sans-v20-latin-600italic.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("/fonts/open-sans-v20-latin-600italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-700 - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: normal;
font-weight: 700;
src: local(""), url("/fonts/open-sans-v20-latin-700.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("/fonts/open-sans-v20-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-700italic - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: italic;
font-weight: 700;
src: local(""),
url("/fonts/open-sans-v20-latin-700italic.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("/fonts/open-sans-v20-latin-700italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

View File

@@ -17,7 +17,7 @@
rel="icon" rel="icon"
type="image/png" type="image/png"
sizes="32x32" sizes="32x32"
href="%PUBLIC_URL%/favicon-32x32.png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAHUUExURUxpcf////////////////////////////////////////////////////////////////////////////////////39/f39/f////////////////////////////////z8/O3s7P7+/vTz8wAAAPz7++/v7/Py8u/u7vDv7+7t7dfX1/79/ezr66Kiovv6+vf29vX09OTj4w0NDfv7++Ti4vr6+vT09Ovq6vn5+f39/fb19dnX1/b29tvb29/f3xMTE/j4+Pn4+O7s7MnJyerq6uno6M/Pz8jIyN3d3dnZ2d7e3kZGRvj39/38/Pf39+zs7PDw8OPi4ubl5cvLy+rp6czMzPLy8vPz887Ozujo6MrKyuDf3+fn59PT0+bm5uXl5c3NzfX19evr6wsLC+/t7ePj4/Ty8u3r6/Lx8eXk5OPh4dDQ0Nza2tjX19vZ2fHw8Nva2tLR0djW1tbW1tra2tLS0uHh4evp6eLi4tXV1djY2L+/vysrK7q6utHR0aGhoa6urrm5uQICAigoKCEhIZCQkFxcXBgYGFRUVBISEm1tbQ4ODklJSbu7uyAgID09PZqamkVFRXh4eLGxsTg4OOTk5FFRUQEBAQoKCnx8fBcXF7/+rP8AAAAfdFJOUwBOqO2h6KT9Mtyr5+w3Str+6TTZOjXb31M24O8BOd0PapSsAAABxElEQVQ4y2NgYOBgYWWXxwLYBdmEGICAjx9Z1NJJVQfB42QE6hdG0RaoaaCqhKSCl4EF1VxNVAXyTAzcaArUUBXwMLBjKDBBViDAII+uwBZFgTy6AjU1awIKNKz1aa0g1VBfB58CDS08CuIb40AKzLAqsJKXN2tXacChwKS+rTtB3tJHRU9ewxmbFZ6tKiqTrSAKdIO1LDEUaPeoqMzsUwAp0NIt8Uj0ijJXQFVgnDJVZcK0XrACXcXY0sjoHM8AfyUkBbOn9KvMmgRSYFEW4hUVqJagHeaShqTAwX7ijJQusAkW5VXRHu6eruFqaigKkqarqAAVKFbYxWiYq1XGedijuMHBWz6xo7m2Rl7RwtvdJaw6NjzCFl2BVVNdshJQgbqBsqtdpIu7nasjXIFXS2cyRL2CW6i6JTCgFCLitUPs4QrM7cDmKZi7FRYVm6o5QeLbDGICLNEqJAVk59rk2djY5Me4FThawQOKFSzrrxmaZWpqBAamQUGmwRaakNTNzMAm7+ekrKmlqOjsrIgAGcAA1ci0NVPiYhAV8zVURgbp6nBg7isuwsDAKKfkp4AEVOHASklKApS9JZmY5bECaS5ZGQYA3vqpy6NoYh0AAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII="
/> />
<link <link
rel="icon" rel="icon"
@@ -25,7 +25,11 @@
sizes="16x16" sizes="16x16"
href="%PUBLIC_URL%/favicon-16x16.png" href="%PUBLIC_URL%/favicon-16x16.png"
/> />
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" /> <link
rel="manifest"
href='data:application/manifest+json,{"name":"Notesnook - Your private note taking space","short_name":"Notesnook","icons":[{"src":"favicon.ico","sizes":"64x64 32x32 24x24 16x16","type":"image/x-icon"},{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"start_url":".","theme_color":"rgb(255,255,255)","background_color":"rgb(255,255,255)","display":"standalone"}'
/>
<!-- <link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" /> -->
<link <link
rel="mask-icon" rel="mask-icon"
href="%PUBLIC_URL%/safari-pinned-tab.svg" href="%PUBLIC_URL%/safari-pinned-tab.svg"
@@ -110,8 +114,73 @@
transform: scale(0.95); transform: scale(0.95);
} }
} }
html,
body {
font-size: 16px;
}
@media only screen and (max-width: 480px) {
html,
body {
font-size: 18px;
background-color: var(--background);
}
}
:root,
body,
#root {
margin: 0 !important;
height: 100%;
overflow: hidden;
}
* {
font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
@keyframes fadeUp {
0% {
transform: translateY(500px);
opacity: 0;
}
80% {
transform: translateY(0px);
opacity: 0.7;
}
100% {
opacity: 1;
}
}
/* svg {
width: 1.5rem;
} */
.ReactModal__Overlay {
opacity: 0;
transition: opacity 200ms ease-in-out;
}
.ReactModal__Overlay--after-open {
opacity: 1;
}
.ReactModal__Overlay--before-close {
opacity: 0;
}
.slide {
background-color: transparent !important;
}
.carousel.carousel-slider {
display: flex;
flex-direction: row;
}
</style> </style>
<link rel="stylesheet" href="/fonts.css" />
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -8,16 +8,21 @@ import { db } from "./common/db";
import { CHECK_IDS, EV, EVENTS } from "notes-core/common"; import { CHECK_IDS, EV, EVENTS } from "notes-core/common";
import { registerKeyMap } from "./common/key-map"; import { registerKeyMap } from "./common/key-map";
import { isUserPremium } from "./hooks/use-is-user-premium"; import { isUserPremium } from "./hooks/use-is-user-premium";
import { loadTrackerScript } from "./utils/analytics";
import Modal from "react-modal";
function AppEffects({ isMobile, isTablet, setShow, slideToIndex }) { Modal.setAppElement("#root");
if (process.env.NODE_ENV === "production") {
loadTrackerScript();
console.log = () => {};
}
export default function AppEffects({ setShow }) {
const refreshColors = useStore((store) => store.refreshColors); const refreshColors = useStore((store) => store.refreshColors);
const refreshMenuPins = useStore((store) => store.refreshMenuPins); const refreshMenuPins = useStore((store) => store.refreshMenuPins);
const updateLastSynced = useStore((store) => store.updateLastSynced); const updateLastSynced = useStore((store) => store.updateLastSynced);
const setProcessingStatus = useStore((store) => store.setProcessingStatus); const setProcessingStatus = useStore((store) => store.setProcessingStatus);
const isFocusMode = useStore((store) => store.isFocusMode); const isFocusMode = useStore((store) => store.isFocusMode);
const isEditorOpen = useStore((store) => store.isEditorOpen);
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const isSideMenuOpen = useStore((store) => store.isSideMenuOpen);
const addReminder = useStore((store) => store.addReminder); const addReminder = useStore((store) => store.addReminder);
const initUser = useUserStore((store) => store.init); const initUser = useUserStore((store) => store.init);
const initNotes = useNotesStore((store) => store.init); const initNotes = useNotesStore((store) => store.init);
@@ -115,25 +120,8 @@ function AppEffects({ isMobile, isTablet, setShow, slideToIndex }) {
}; };
}, []); }, []);
useEffect(() => {
if (!isMobile) return;
slideToIndex(isSideMenuOpen ? 0 : 1);
}, [isMobile, slideToIndex, isSideMenuOpen]);
useEffect(() => {
if (!isMobile) return;
slideToIndex(isEditorOpen ? 2 : 1);
}, [isMobile, slideToIndex, isEditorOpen]);
useEffect(() => {
toggleSideMenu(!isMobile);
if (!isMobile && !isTablet && !isFocusMode) setShow(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMobile, isTablet, isFocusMode, toggleSideMenu]);
return <React.Fragment />; return <React.Fragment />;
} }
export default AppEffects;
function getProcessingStatusFromType(type) { function getProcessingStatusFromType(type) {
switch (type) { switch (type) {

View File

@@ -0,0 +1,64 @@
import { useEffect } from "react";
import { useStore } from "./stores/app-store";
import useSlider from "./hooks/use-slider";
import useMobile from "./utils/use-mobile";
import useTablet from "./utils/use-tablet";
export default function MobileAppEffects({ sliderId, overlayId, setShow }) {
const isMobile = useMobile();
const isTablet = useTablet();
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const setIsEditorOpen = useStore((store) => store.setIsEditorOpen);
const isEditorOpen = useStore((store) => store.isEditorOpen);
const isSideMenuOpen = useStore((store) => store.isSideMenuOpen);
const isFocusMode = useStore((store) => store.isFocusMode);
const [slideToIndex] = useSlider(sliderId, {
onSliding: (e, { lastSlide, position, lastPosition }) => {
if (!isMobile) return;
const offset = 70;
const width = 180;
const percent = offset - (position / width) * offset;
const overlay = document.getElementById("overlay");
if (percent > 0) {
overlay.style.opacity = `${percent}%`;
overlay.style.pointerEvents = "all";
} else {
overlay.style.pointerEvents = "none";
}
},
onChange: (e, { slide, lastSlide }) => {
if (!lastSlide || !isMobile) return;
toggleSideMenu(slide?.index === 0 ? true : false);
setIsEditorOpen(slide?.index === 2 ? true : false);
},
});
useEffect(() => {
if (!isMobile) return;
slideToIndex(isSideMenuOpen ? 0 : 1);
}, [isMobile, slideToIndex, isSideMenuOpen]);
useEffect(() => {
if (!isMobile) return;
slideToIndex(isEditorOpen ? 2 : 1);
}, [isMobile, slideToIndex, isEditorOpen]);
useEffect(() => {
toggleSideMenu(!isMobile);
if (!isMobile && !isTablet && !isFocusMode) setShow(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMobile, isTablet, isFocusMode, toggleSideMenu]);
useEffect(() => {
if (!overlayId) return;
const overlay = document.getElementById(overlayId);
overlay.onclick = () => toggleSideMenu(false);
return () => {
overlay.onclick = null;
};
}, [overlayId, toggleSideMenu]);
return null;
}

View File

@@ -1,3 +1,75 @@
/* open-sans-regular - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: normal;
font-weight: 400;
src: local(""),
url("./assets/fonts/open-sans-v20-latin-regular.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("./assets/fonts/open-sans-v20-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-italic - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: italic;
font-weight: 400;
src: local(""),
url("./assets/fonts/open-sans-v20-latin-italic.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("./assets/fonts/open-sans-v20-latin-italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-600 - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: normal;
font-weight: 600;
src: local(""),
url("./assets/fonts/open-sans-v20-latin-600.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("./assets/fonts/open-sans-v20-latin-600.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-600italic - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: italic;
font-weight: 600;
src: local(""),
url("./assets/fonts/open-sans-v20-latin-600italic.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("./assets/fonts/open-sans-v20-latin-600italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-700 - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: normal;
font-weight: 700;
src: local(""),
url("./assets/fonts/open-sans-v20-latin-700.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("./assets/fonts/open-sans-v20-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* open-sans-700italic - latin */
@font-face {
font-family: "Open Sans";
font-display: swap;
font-style: italic;
font-weight: 700;
src: local(""),
url("./assets/fonts/open-sans-v20-latin-700italic.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url("./assets/fonts/open-sans-v20-latin-700italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
.unselectable { .unselectable {
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-user-select: none; -webkit-user-select: none;

View File

@@ -1,81 +1,48 @@
import React, { useState, useEffect } from "react"; import React, { useState, Suspense } from "react";
import "./app.css"; import { Box, Flex, Text } from "rebass";
import { Box, Flex } from "rebass";
import { MotionConfig, AnimationFeature, GesturesFeature } from "framer-motion";
import ThemeProvider from "./components/theme-provider"; import ThemeProvider from "./components/theme-provider";
import StatusBar from "./components/statusbar"; import { AnimatedFlex } from "./components/animated";
import Animated from "./components/animated"; import NavigationMenuPlaceholder from "./components/navigationmenu/index.lite";
import NavigationMenu from "./components/navigationmenu"; import StatusBarPlaceholder from "./components/statusbar/index.lite";
import GlobalMenuWrapper from "./components/globalmenuwrapper";
import { NavigationEvents } from "./navigation";
import rootroutes from "./navigation/rootroutes";
import { useStore } from "./stores/app-store";
import { Suspense } from "react";
import useMobile from "./utils/use-mobile"; import useMobile from "./utils/use-mobile";
import useTablet from "./utils/use-tablet"; import useTablet from "./utils/use-tablet";
import HashRouter from "./components/hashrouter"; import { Loading } from "./components/icons";
import useSlider from "./hooks/use-slider"; import { LazyMotion, domAnimation } from "framer-motion";
import useRoutes from "./utils/use-routes"; import useDatabase from "./hooks/use-database";
import { clearRouteCache } from "./components/cachedrouter"; import Loader from "./components/loader";
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 CachedRouter = React.lazy(() => import("./components/cached-router")); const CachedRouter = React.lazy(() => import("./components/cached-router"));
const HashRouter = React.lazy(() => import("./components/hash-router"));
const NavigationMenu = React.lazy(() => import("./components/navigation-menu"));
const StatusBar = React.lazy(() => import("./components/status-bar"));
function App() { function App() {
const [show, setShow] = useState(true); const [show, setShow] = useState(true);
const isMobile = useMobile(); const isMobile = useMobile();
const isTablet = useTablet(); const isTablet = useTablet();
const [isAppLoaded, setIsAppLoaded] = useState(false); const [isAppLoaded] = useDatabase();
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const setIsEditorOpen = useStore((store) => store.setIsEditorOpen);
const [sliderRef, slideToIndex] = useSlider({
onSliding: (e, { lastSlide, position, lastPosition }) => {
if (!isMobile) return;
const offset = 70;
const width = 180;
const percent = offset - (position / width) * offset;
const overlay = document.getElementById("overlay");
if (percent > 0) {
overlay.style.opacity = `${percent}%`;
overlay.style.pointerEvents = "all";
} else {
overlay.style.pointerEvents = "none";
}
},
onChange: (e, { slide, lastSlide }) => {
if (!lastSlide || !isMobile) return;
toggleSideMenu(slide?.index === 0 ? true : false);
setIsEditorOpen(slide?.index === 2 ? true : false);
},
});
useEffect(() => {
function onNavigate() {
NavigationEvents.unsubscribe("onNavigate", onNavigate);
setIsAppLoaded(true);
}
NavigationEvents.subscribe("onNavigate", onNavigate);
return () => {
NavigationEvents.unsubscribe("onNavigate", onNavigate);
};
}, []);
return ( return (
<MotionConfig features={[AnimationFeature, GesturesFeature]}> <LazyMotion features={domAnimation} strict>
<ThemeProvider> <ThemeProvider>
{isAppLoaded && ( {isAppLoaded && (
<Suspense fallback={<div style={{ display: "none" }} />}> <Suspense fallback={<div style={{ display: "none" }} />}>
<AppEffects <GlobalMenuWrapper />
slideToIndex={slideToIndex} <AppEffects setShow={setShow} />
setShow={setShow} {isMobile && (
isMobile={isMobile} <MobileAppEffects
isTablet={isTablet} sliderId="slider"
/> overlayId="overlay"
setShow={setShow}
/>
)}
</Suspense> </Suspense>
)} )}
<GlobalMenuWrapper />
<Flex <Flex
flexDirection="column" flexDirection="column"
id="app" id="app"
@@ -84,7 +51,7 @@ function App() {
sx={{ overflow: "hidden" }} sx={{ overflow: "hidden" }}
> >
<Flex <Flex
ref={sliderRef} id="slider"
variant="rowFill" variant="rowFill"
overflowX={["auto", "hidden"]} overflowX={["auto", "hidden"]}
sx={{ sx={{
@@ -99,13 +66,18 @@ function App() {
scrollSnapAlign: "start", scrollSnapAlign: "start",
}} }}
> >
<NavigationMenu <SuspenseLoader
toggleNavigationContainer={(state) => { condition={isAppLoaded}
if (!isMobile) setShow(state || !show); component={NavigationMenu}
props={{
toggleNavigationContainer: (state) => {
if (!isMobile) setShow(state || !show);
},
}} }}
fallback={<NavigationMenuPlaceholder />}
/> />
</Flex> </Flex>
<Animated.Flex <AnimatedFlex
className="listMenu" className="listMenu"
variant="columnFill" variant="columnFill"
initial={{ initial={{
@@ -134,9 +106,16 @@ function App() {
}} }}
flexShrink={0} flexShrink={0}
> >
<Suspense fallback={<div />}> <SuspenseLoader
<CachedRouter /> condition={isAppLoaded}
</Suspense> component={CachedRouter}
fallback={
<Loader
title="Did you know?"
text="All your notes are encrypted on your device."
/>
}
/>
{isMobile && ( {isMobile && (
<Box <Box
id="overlay" id="overlay"
@@ -152,12 +131,9 @@ function App() {
pointerEvents: "none", pointerEvents: "none",
}} }}
bg="black" bg="black"
onClick={() => {
toggleSideMenu(false);
}}
/> />
)} )}
</Animated.Flex> </AnimatedFlex>
<Flex <Flex
width={["100vw", "100%"]} width={["100vw", "100%"]}
flexShrink={[0, 1]} flexShrink={[0, 1]}
@@ -166,20 +142,37 @@ function App() {
}} }}
flexDirection="column" flexDirection="column"
> >
<HashRouter /> <SuspenseLoader
fallback={
<Loader
title="Fun fact"
text="Notesnook was released in January 2021 by a team of only 3 people."
/>
}
component={HashRouter}
condition={isAppLoaded}
/>
</Flex> </Flex>
</Flex> </Flex>
<StatusBar /> <SuspenseLoader
fallback={<StatusBarPlaceholder />}
component={StatusBar}
condition={isAppLoaded}
/>
</Flex> </Flex>
</ThemeProvider> </ThemeProvider>
</MotionConfig> </LazyMotion>
); );
} }
function Root() { export default App;
const route = useRoutes(rootroutes);
if (route) clearRouteCache();
return route?.component || <App />;
}
export default Root; function SuspenseLoader({ condition, props, component: Component, fallback }) {
if (!condition) return fallback;
return (
<Suspense fallback={fallback}>
<Component {...props} />
</Suspense>
);
}

View File

@@ -1,11 +1,3 @@
import StorageInterface from "../interfaces/storage";
import FS from "../interfaces/fs";
import Config from "../utils/config";
import http from "notes-core/utils/http";
import { EV, EVENTS } from "notes-core/common";
import { getCurrentHash, hashNavigate } from "../navigation";
import { isTesting } from "../utils/platform";
global.HTMLParser = new DOMParser().parseFromString( global.HTMLParser = new DOMParser().parseFromString(
"<body></body>", "<body></body>",
"text/html" "text/html"
@@ -16,67 +8,31 @@ global.HTMLParser = new DOMParser().parseFromString(
var db; var db;
async function initializeDatabase() { async function initializeDatabase() {
const { default: Database } = await import("notes-core/api"); const { default: Database } = await import("notes-core/api");
db = new Database(StorageInterface, EventSource, FS); const { default: Storage } = await import("../interfaces/storage");
const { default: FS } = await import("../interfaces/fs");
db = new Database(Storage, EventSource, FS);
if (isTesting()) { // if (isTesting()) {
db.host({ // db.host({
API_HOST: "https://api.notesnook.com", // API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co", // AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co", // SSE_HOST: "https://events.streetwriters.co",
}); // });
} else { // } else {
db.host({ // db.host({
API_HOST: "http://localhost:5264", // API_HOST: "http://localhost:5264",
AUTH_HOST: "http://localhost:8264", // AUTH_HOST: "http://localhost:8264",
SSE_HOST: "http://localhost:7264", // SSE_HOST: "http://localhost:7264",
}); // });
// db.host({ // // db.host({
// API_HOST: "http://192.168.10.29:5264", // // API_HOST: "http://192.168.10.29:5264",
// AUTH_HOST: "http://192.168.10.29:8264", // // AUTH_HOST: "http://192.168.10.29:8264",
// SSE_HOST: "http://192.168.10.29:7264", // // SSE_HOST: "http://192.168.10.29:7264",
// }); // // });
} // }
await db.init(); await db.init();
if (!isAppHydrated() && !isTesting()) {
try {
loadDefaultNotes(db);
} catch (e) {}
}
return db; return db;
} }
export { db, initializeDatabase }; export { db, initializeDatabase };
function isAppHydrated() {
return Config.get("hydrated", false);
}
function setAppHydrated() {
return Config.set("hydrated", true);
}
async function loadDefaultNotes(db) {
const notes = await http.get("/notes/index_v14.json");
if (!notes) return;
let autoOpenId;
const hash = getCurrentHash().replaceAll("#", "");
for (let note of notes) {
const content = await http.get(note.webContent);
let id = await db.notes.add({
title: note.title,
headline: note.headline,
localOnly: true,
content: { type: "tiny", data: content },
});
if (note.autoOpen) autoOpenId = id;
}
if (autoOpenId) {
hashNavigate(`/notes/${autoOpenId}/edit`);
if (hash) setTimeout(() => hashNavigate(hash), 100);
}
setAppHydrated();
EV.publish(EVENTS.appRefreshRequested);
}

View File

@@ -1,6 +1,6 @@
const SINGLE_LINE_HEIGHT = 1.4; const SINGLE_LINE_HEIGHT = 1.4;
const DEFAULT_FONT_SIZE = document.getElementById("p").clientHeight - 1; const DEFAULT_FONT_SIZE = document.getElementById("p").clientHeight - 1;
console.log("HEIGHT", DEFAULT_FONT_SIZE);
const MAX_HEIGHTS = { const MAX_HEIGHTS = {
note: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE, note: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE,
notebook: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE, notebook: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE,

View File

@@ -1,12 +1,9 @@
import { m as motion } from "framer-motion"; import { m } from "framer-motion";
import { Flex, Box, Image, Text } from "rebass"; import { Flex, Box, Image, Text } from "rebass";
import { Input } from "@rebass/forms"; import { Input } from "@rebass/forms";
const Animated = { export const AnimatedFlex = m(Flex);
Flex: motion.custom(Flex), export const AnimatedBox = m(Box);
Box: motion.custom(Box), export const AnimatedImage = m(Image);
Image: motion.custom(Image), export const AnimatedText = m(Text);
Text: motion.custom(Text), export const AnimatedInput = m(Input);
Input: motion.custom(Input),
};
export default Animated;

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { NavigationEvents } from "../../navigation"; import { getHomeRoute, NavigationEvents } from "../../navigation";
import useRoutes from "../../utils/use-routes"; import useRoutes from "../../utils/use-routes";
import RouteContainer from "../route-container"; import RouteContainer from "../route-container";
import ThemeProvider from "../theme-provider"; import ThemeProvider from "../theme-provider";
@@ -8,10 +8,12 @@ import routes from "../../navigation/routes";
var cache = {}; var cache = {};
function CachedRouter() { function CachedRouter() {
const RouteResult = useRoutes(routes, { fallbackRoute: "/" }); const [RouteResult, location] = useRoutes(routes, {
fallbackRoute: getHomeRoute(),
});
useEffect(() => { useEffect(() => {
if (!RouteResult) return; if (!RouteResult) return;
NavigationEvents.publish("onNavigate", RouteResult); NavigationEvents.publish("onNavigate", RouteResult, location);
window.currentViewType = RouteResult.type; window.currentViewType = RouteResult.type;
window.currentViewKey = RouteResult.key; window.currentViewKey = RouteResult.key;
@@ -45,7 +47,7 @@ function CachedRouter() {
route route
); );
} }
}, [RouteResult]); }, [RouteResult, location]);
return ( return (
<RouteContainer <RouteContainer

View File

@@ -4,13 +4,6 @@ import Dialog from "./dialog";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { useStore as useUserStore } from "../../stores/user-store"; import { useStore as useUserStore } from "../../stores/user-store";
import { getCouponData, upgrade } from "../../common/checkout"; import { getCouponData, upgrade } from "../../common/checkout";
import { ReactComponent as Personalization } from "../../assets/accent.svg";
import { ReactComponent as Backups } from "../../assets/backup.svg";
import { ReactComponent as Export } from "../../assets/export.svg";
import { ReactComponent as Organize } from "../../assets/organize.svg";
import { ReactComponent as RichText } from "../../assets/richtext.svg";
import { ReactComponent as Sync } from "../../assets/sync.svg";
import { ReactComponent as Vault } from "../../assets/vault.svg";
import getSymbolFromCurrency from "currency-symbol-map"; import getSymbolFromCurrency from "currency-symbol-map";
import { ANALYTICS_EVENTS, trackEvent } from "../../utils/analytics"; import { ANALYTICS_EVENTS, trackEvent } from "../../utils/analytics";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
@@ -19,77 +12,41 @@ const premiumDetails = [
{ {
title: "Unlimited attachments", title: "Unlimited attachments",
description: "Your notes will be automatically synced to all your devices.", description: "Your notes will be automatically synced to all your devices.",
illustration: {
icon: Sync,
width: "40%",
},
}, },
{ {
title: "Unlimited storage", title: "Unlimited storage",
description: "Your notes will be automatically synced to all your devices.", description: "Your notes will be automatically synced to all your devices.",
illustration: {
icon: Sync,
width: "40%",
},
}, },
{ {
title: "Unlimited notebooks & tags", title: "Unlimited notebooks & tags",
description: description:
"Make unlimited notebooks and tags, and assign colors to your notes for quick access.", "Make unlimited notebooks and tags, and assign colors to your notes for quick access.",
illustration: {
icon: Organize,
width: "40%",
},
}, },
{ {
title: "Automatic syncing", title: "Automatic syncing",
description: "Your notes will be automatically synced to all your devices.", description: "Your notes will be automatically synced to all your devices.",
illustration: {
icon: Sync,
width: "40%",
},
}, },
{ {
title: "Secure vault for notes", title: "Secure vault for notes",
description: description:
"Lock any note with a password and keep sensitive data under lock and key.", "Lock any note with a password and keep sensitive data under lock and key.",
illustration: {
icon: Vault,
width: "35%",
},
}, },
{ {
title: "Full rich text editor + markdown support", title: "Full rich text editor + markdown support",
description: description:
"Add images, links, tables and lists to your notes, and use markdown for fast editing.", "Add images, links, tables and lists to your notes, and use markdown for fast editing.",
illustration: {
icon: RichText,
width: "50%",
},
}, },
{ {
title: "Multi-format exports", title: "Multi-format exports",
description: "Export your notes in PDF, Markdown, or HTML formats.", description: "Export your notes in PDF, Markdown, or HTML formats.",
illustration: {
icon: Export,
width: "25%",
},
}, },
{ {
title: "Automatic encrypted backups", title: "Automatic encrypted backups",
description: "Enable daily or weekly backups with automatic encryption.", description: "Enable daily or weekly backups with automatic encryption.",
illustration: {
icon: Backups,
width: "25%",
},
}, },
{ {
title: "Customize Notesnook", title: "Customize Notesnook",
description: "Change app colors and turn on automatic theme switching.", description: "Change app colors and turn on automatic theme switching.",
illustration: {
icon: Personalization,
width: "50%",
},
}, },
{ {
title: ( title: (
@@ -100,10 +57,6 @@ const premiumDetails = [
), ),
description: description:
"Pro users get access to special channels and priority support on our Discord server.", "Pro users get access to special channels and priority support on our Discord server.",
illustration: {
icon: Personalization,
width: "50%",
},
}, },
]; ];

View File

@@ -7,7 +7,7 @@ import {
store as editorstore, store as editorstore,
} from "../../stores/editor-store"; } from "../../stores/editor-store";
import { useStore as useAppStore } from "../../stores/app-store"; import { useStore as useAppStore } from "../../stores/app-store";
import Animated from "../animated"; import { AnimatedFlex } from "../animated";
import Header from "./header"; import Header from "./header";
import useMobile from "../../utils/use-mobile"; import useMobile from "../../utils/use-mobile";
import useTablet from "../../utils/use-tablet"; import useTablet from "../../utils/use-tablet";
@@ -126,7 +126,7 @@ function Editor({ noteId, nonce }) {
alignItems: "center", alignItems: "center",
}} }}
/> />
<Animated.Flex <AnimatedFlex
variant="columnFill" variant="columnFill"
className="editor" className="editor"
sx={{ sx={{
@@ -179,7 +179,7 @@ function Editor({ noteId, nonce }) {
</> </>
) : null} ) : null}
</Suspense> </Suspense>
</Animated.Flex> </AnimatedFlex>
</Flex> </Flex>
<Properties noteId={noteId} /> <Properties noteId={noteId} />
</Flex> </Flex>

View File

@@ -6,7 +6,7 @@ import { useStore as useAppStore } from "../../stores/app-store";
import { useStore as useThemeStore } from "../../stores/theme-store"; import { useStore as useThemeStore } from "../../stores/theme-store";
import { useStore, store } from "../../stores/editor-store"; import { useStore, store } from "../../stores/editor-store";
import { showToast } from "../../utils/toast"; import { showToast } from "../../utils/toast";
import Animated from "../animated"; import { AnimatedInput } from "../animated";
import { showPublishView } from "../publish-view"; import { showPublishView } from "../publish-view";
import { db } from "../../common/db"; import { db } from "../../common/db";
@@ -163,7 +163,7 @@ function Toolbar(props) {
clearSession(); clearSession();
}} }}
/> />
<Animated.Input <AnimatedInput
ml={[2, 2, 0]} ml={[2, 2, 0]}
initial={{ initial={{
opacity: isTitleVisible ? 1 : 0, opacity: isTitleVisible ? 1 : 0,

View File

@@ -1,7 +1,7 @@
import * as Icon from "../icons"; import * as Icon from "../icons";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { Button, Flex, Text } from "rebass"; import { Button, Flex, Text } from "rebass";
import Animated from "../animated"; import { AnimatedFlex } from "../animated";
import { db } from "../../common/db"; import { db } from "../../common/db";
import { useOpenContextMenu } from "../../utils/useContextMenu"; import { useOpenContextMenu } from "../../utils/useContextMenu";
import { useStore as useNoteStore } from "../../stores/note-store"; import { useStore as useNoteStore } from "../../stores/note-store";
@@ -99,7 +99,7 @@ function GroupHeader(props) {
if (!title) return null; if (!title) return null;
return ( return (
<Animated.Flex <AnimatedFlex
transition={{ duration: 0.3, repeatType: "reverse", repeat: 3 }} transition={{ duration: 0.3, repeatType: "reverse", repeat: 3 }}
onClick={(e) => { onClick={(e) => {
if (groups.length <= 0) return; if (groups.length <= 0) return;
@@ -165,7 +165,7 @@ function GroupHeader(props) {
)} )}
</Flex> </Flex>
)} )}
</Animated.Flex> </AnimatedFlex>
); );
} }
export default GroupHeader; export default GroupHeader;

View File

@@ -1,8 +1,101 @@
import React, { useState } from "react"; import React, { useState } from "react";
import MDIIcon from "@mdi/react"; import MDIIcon from "@mdi/react";
import * as Icons from "@mdi/js"; import {
mdiPlus,
mdiHomeVariantOutline,
mdiMinus,
mdiBookOutline,
mdiNotebookOutline,
mdiArrowLeft,
mdiArrowRight,
mdiArrowDown,
mdiBookPlusMultipleOutline,
mdiBookmarkOutline,
mdiAlert,
mdiShieldOutline,
mdiLockOpenOutline,
mdiLockOutline,
mdiStar,
mdiStarOutline,
mdiCircle,
mdiCircleOutline,
mdiUpdate,
mdiCheck,
mdiClose,
mdiDotsVertical,
mdiTrashCanOutline,
mdiBookRemoveOutline,
mdiMagnify,
mdiMenu,
mdiLoginVariant,
mdiEmailAlertOutline,
mdiAccountOutline,
mdiLogoutVariant,
mdiSunglasses,
mdiGlasses,
mdiCogOutline,
mdiHomeOutline,
mdiRecycle,
mdiSync,
mdiLoading,
mdiExportVariant,
mdiArrowExpandDown,
mdiArrowTopRightThick,
mdiChevronLeft,
mdiChevronRight,
mdiPound,
mdiPinOutline,
mdiPin,
mdiWeatherNight,
mdiWeatherSunny,
mdiThemeLightDark,
mdiCheckCircle,
mdiLanguageMarkdownOutline,
mdiFilePdfOutline,
mdiLanguageHtml5,
mdiFormatTitle,
mdiAlertCircle,
mdiInformation,
mdiToggleSwitchOffOutline,
mdiToggleSwitchOutline,
mdiBackupRestore,
mdiCurrencyUsdCircleOutline,
mdiPencil,
mdiUndoVariant,
mdiRedoVariant,
mdiTune,
mdiChevronDown,
mdiChevronUp,
mdiSortAscending,
mdiSortDescending,
mdiEye,
mdiEyeOff,
mdiFullscreen,
mdiFullscreenExit,
mdiBullhorn,
mdiCloudUploadOutline,
mdiCloudCheckOutline,
mdiContentCopy,
mdiCheckboxMultipleMarkedCircleOutline,
mdiBookEditOutline,
mdiDeleteForeverOutline,
mdiTextBoxMultipleOutline,
mdiRocketLaunchOutline,
mdiShareVariantOutline,
mdiFormTextboxPassword,
mdiBomb,
mdiViewHeadline,
mdiViewSequentialOutline,
mdiEmailCheckOutline,
mdiDiscord,
mdiTwitter,
mdiReddit,
mdiFileOutline,
mdiImage,
mdiNoteOutline,
} from "@mdi/js";
import { useTheme } from "emotion-theming"; import { useTheme } from "emotion-theming";
import Animated from "../animated"; import { AnimatedFlex } from "../animated";
function Icon({ title, name, size = 24, color = "icon", stroke, rotate }) { function Icon({ title, name, size = 24, color = "icon", stroke, rotate }) {
const theme = useTheme(); const theme = useTheme();
@@ -25,7 +118,7 @@ function createIcon(name, rotate = false) {
return function (props) { return function (props) {
const [isHovering, setIsHovering] = useState(); const [isHovering, setIsHovering] = useState();
return ( return (
<Animated.Flex <AnimatedFlex
id={props.id} id={props.id}
title={props.title} title={props.title}
variant={props.variant} variant={props.variant}
@@ -49,138 +142,109 @@ function createIcon(name, rotate = false) {
} }
{...props} {...props}
/> />
</Animated.Flex> </AnimatedFlex>
); );
}; };
} }
export const Plus = createIcon(Icons.mdiPlus); export const Plus = createIcon(mdiPlus);
export const Note = createIcon(Icons.mdiHomeVariantOutline); export const Note = createIcon(mdiNoteOutline);
export const Minus = createIcon(Icons.mdiMinus); export const Minus = createIcon(mdiMinus);
export const Notebook = createIcon(Icons.mdiBookOutline); export const Notebook = createIcon(mdiBookOutline);
export const Notebook2 = createIcon(Icons.mdiNotebookOutline); export const Notebook2 = createIcon(mdiNotebookOutline);
export const ArrowLeft = createIcon(Icons.mdiArrowLeft); export const ArrowLeft = createIcon(mdiArrowLeft);
export const ArrowRight = createIcon(Icons.mdiArrowRight); export const ArrowRight = createIcon(mdiArrowRight);
export const ArrowDown = createIcon(Icons.mdiArrowDown); export const ArrowDown = createIcon(mdiArrowDown);
export const Move = createIcon(Icons.mdiBookPlusMultipleOutline); export const Move = createIcon(mdiBookPlusMultipleOutline);
export const Topic = createIcon(Icons.mdiBookmarkOutline); export const Topic = createIcon(mdiBookmarkOutline);
export const Alert = createIcon(Icons.mdiAlert); export const Alert = createIcon(mdiAlert);
export const Vault = createIcon(Icons.mdiShieldOutline); export const Vault = createIcon(mdiShieldOutline);
export const Unlock = createIcon(Icons.mdiLockOpenOutline); export const Unlock = createIcon(mdiLockOpenOutline);
export const Lock = createIcon(Icons.mdiLockOutline); export const Lock = createIcon(mdiLockOutline);
export const Star = createIcon(Icons.mdiStar); export const Star = createIcon(mdiStar);
export const StarOutline = createIcon(Icons.mdiStarOutline); export const StarOutline = createIcon(mdiStarOutline);
export const Circle = createIcon(Icons.mdiCircle); export const Circle = createIcon(mdiCircle);
export const CircleEmpty = createIcon(Icons.mdiCircleOutline); export const CircleEmpty = createIcon(mdiCircleOutline);
export const Update = createIcon(Icons.mdiUpdate); export const Update = createIcon(mdiUpdate);
export const Check = createIcon(Icons.mdiCheck); export const Check = createIcon(mdiCheck);
export const Cross = createIcon(Icons.mdiClose); export const Cross = createIcon(mdiClose);
export const MoreVertical = createIcon(Icons.mdiDotsVertical); export const MoreVertical = createIcon(mdiDotsVertical);
export const Trash = createIcon(Icons.mdiTrashCanOutline); export const Trash = createIcon(mdiTrashCanOutline);
export const TopicRemove = createIcon(Icons.mdiBookRemoveOutline); export const TopicRemove = createIcon(mdiBookRemoveOutline);
export const Search = createIcon(Icons.mdiMagnify); export const Search = createIcon(mdiMagnify);
export const Menu = createIcon(Icons.mdiMenu); export const Menu = createIcon(mdiMenu);
export const Login = createIcon(Icons.mdiLoginVariant); export const Login = createIcon(mdiLoginVariant);
export const Email = createIcon(Icons.mdiEmailAlertOutline); export const Email = createIcon(mdiEmailAlertOutline);
export const Signup = createIcon(Icons.mdiAccountOutline); export const Signup = createIcon(mdiAccountOutline);
export const Logout = createIcon(Icons.mdiLogoutVariant); export const Logout = createIcon(mdiLogoutVariant);
export const FocusMode = createIcon(Icons.mdiSunglasses); export const FocusMode = createIcon(mdiSunglasses);
export const NormalMode = createIcon(Icons.mdiGlasses); export const NormalMode = createIcon(mdiGlasses);
export const Settings = createIcon(Icons.mdiCogOutline); export const Settings = createIcon(mdiCogOutline);
export const Home = createIcon(Icons.mdiHomeOutline); export const Home = createIcon(mdiHomeOutline);
export const Restore = createIcon(Icons.mdiRecycle); export const Restore = createIcon(mdiRecycle);
export const Sync = createIcon(Icons.mdiSync); export const Sync = createIcon(mdiSync);
export const Loading = createIcon(Icons.mdiLoading, true); export const Loading = createIcon(mdiLoading, true);
export const Export = createIcon(Icons.mdiExportVariant); export const Export = createIcon(mdiExportVariant);
export const AddToNotebook = createIcon(Icons.mdiBookPlusMultipleOutline); export const AddToNotebook = createIcon(mdiBookPlusMultipleOutline);
export const Expand = createIcon(Icons.mdiArrowExpandDown); export const Expand = createIcon(mdiArrowExpandDown);
export const Shortcut = createIcon(Icons.mdiArrowTopRightThick); export const Shortcut = createIcon(mdiArrowTopRightThick);
export const ChevronLeft = createIcon(mdiChevronLeft);
/** Properties Icons */ export const ChevronRight = createIcon(mdiChevronRight);
export const ChevronLeft = createIcon(Icons.mdiChevronLeft); export const Close = createIcon(mdiClose);
export const ChevronRight = createIcon(Icons.mdiChevronRight); export const Tag = createIcon(mdiPound);
export const Close = createIcon(Icons.mdiClose); export const Tag2 = createIcon(mdiPound);
export const Tag = createIcon(Icons.mdiPound); export const Pin = createIcon(mdiPinOutline);
export const Tag2 = createIcon(Icons.mdiPound); export const PinFilled = createIcon(mdiPin);
export const Pin = createIcon(Icons.mdiPinOutline); export const User = createIcon(mdiAccountOutline);
export const PinFilled = createIcon(Icons.mdiPin); export const DarkMode = createIcon(mdiWeatherNight);
export const LightMode = createIcon(mdiWeatherSunny);
/** Settings Icons */ export const Theme = createIcon(mdiThemeLightDark);
export const User = createIcon(Icons.mdiAccountOutline); export const Checkmark = createIcon(mdiCheck);
export const DarkMode = createIcon(Icons.mdiWeatherNight); export const CheckCircle = createIcon(mdiCheckCircle);
export const LightMode = createIcon(Icons.mdiWeatherSunny); export const Properties = createIcon(mdiDotsVertical);
export const Theme = createIcon(Icons.mdiThemeLightDark); export const Markdown = createIcon(mdiLanguageMarkdownOutline);
export const Checkmark = createIcon(Icons.mdiCheck); export const PDF = createIcon(mdiFilePdfOutline);
export const CheckCircle = createIcon(Icons.mdiCheckCircle); export const HTML = createIcon(mdiLanguageHtml5);
export const Text = createIcon(mdiFormatTitle);
export const Properties = createIcon(Icons.mdiDotsVertical); export const Success = createIcon(mdiCheckCircle);
export const Error = createIcon(mdiAlertCircle);
// FORMATS export const Warn = createIcon(mdiAlert);
export const Info = createIcon(mdiInformation);
export const Markdown = createIcon(Icons.mdiLanguageMarkdownOutline); export const ToggleUnchecked = createIcon(mdiToggleSwitchOffOutline);
export const PDF = createIcon(Icons.mdiFilePdfOutline); export const ToggleChecked = createIcon(mdiToggleSwitchOutline);
export const HTML = createIcon(Icons.mdiLanguageHtml5); export const Backup = createIcon(mdiBackupRestore);
export const Text = createIcon(Icons.mdiFormatTitle); export const Buy = createIcon(mdiCurrencyUsdCircleOutline);
export const Edit = createIcon(mdiPencil);
// TOAST export const Undo = createIcon(mdiUndoVariant);
export const Success = createIcon(Icons.mdiCheckCircle); export const Redo = createIcon(mdiRedoVariant);
export const Error = createIcon(Icons.mdiAlertCircle); export const Filter = createIcon(mdiTune);
export const Warn = createIcon(Icons.mdiAlert); export const ChevronDown = createIcon(mdiChevronDown);
export const Info = createIcon(Icons.mdiInformation); export const ChevronUp = createIcon(mdiChevronUp);
export const SortAsc = createIcon(mdiSortAscending);
export const ToggleUnchecked = createIcon(Icons.mdiToggleSwitchOffOutline); export const SortDesc = createIcon(mdiSortDescending);
export const ToggleChecked = createIcon(Icons.mdiToggleSwitchOutline); export const PasswordInvisible = createIcon(mdiEye);
export const PasswordVisible = createIcon(mdiEyeOff);
export const Backup = createIcon(Icons.mdiBackupRestore); export const Fullscreen = createIcon(mdiFullscreen);
export const Buy = createIcon(Icons.mdiCurrencyUsdCircleOutline); export const ExitFullscreen = createIcon(mdiFullscreenExit);
export const Announcement = createIcon(mdiBullhorn);
export const Edit = createIcon(Icons.mdiPencil); export const Publish = createIcon(mdiCloudUploadOutline);
export const Published = createIcon(mdiCloudCheckOutline);
export const Undo = createIcon(Icons.mdiUndoVariant); export const Copy = createIcon(mdiContentCopy);
export const Redo = createIcon(Icons.mdiRedoVariant); export const Select = createIcon(mdiCheckboxMultipleMarkedCircleOutline);
export const NotebookEdit = createIcon(mdiBookEditOutline);
export const Filter = createIcon(Icons.mdiTune); export const DeleteForver = createIcon(mdiDeleteForeverOutline);
export const Monographs = createIcon(mdiTextBoxMultipleOutline);
export const ChevronDown = createIcon(Icons.mdiChevronDown); export const Rocket = createIcon(mdiRocketLaunchOutline);
export const ChevronUp = createIcon(Icons.mdiChevronUp); export const Share = createIcon(mdiShareVariantOutline);
export const Password = createIcon(mdiFormTextboxPassword);
export const SortAsc = createIcon(Icons.mdiSortAscending); export const Destruct = createIcon(mdiBomb);
export const SortDesc = createIcon(Icons.mdiSortDescending); export const CompactView = createIcon(mdiViewHeadline);
export const DetailedView = createIcon(mdiViewSequentialOutline);
export const PasswordInvisible = createIcon(Icons.mdiEye); export const MailCheck = createIcon(mdiEmailCheckOutline);
export const PasswordVisible = createIcon(Icons.mdiEyeOff); export const Discord = createIcon(mdiDiscord);
export const Twitter = createIcon(mdiTwitter);
export const Fullscreen = createIcon(Icons.mdiFullscreen); export const Reddit = createIcon(mdiReddit);
export const ExitFullscreen = createIcon(Icons.mdiFullscreenExit); export const Dismiss = createIcon(mdiClose);
export const File = createIcon(mdiFileOutline);
export const Announcement = createIcon(Icons.mdiBullhorn); export const Download = createIcon(mdiArrowDown);
export const Publish = createIcon(Icons.mdiCloudUploadOutline); export const ImageDownload = createIcon(mdiImage);
export const Published = createIcon(Icons.mdiCloudCheckOutline);
export const Copy = createIcon(Icons.mdiContentCopy);
export const Select = createIcon(Icons.mdiCheckboxMultipleMarkedCircleOutline);
export const NotebookEdit = createIcon(Icons.mdiBookEditOutline);
export const DeleteForver = createIcon(Icons.mdiDeleteForeverOutline);
export const Monographs = createIcon(Icons.mdiTextBoxMultipleOutline);
export const Rocket = createIcon(Icons.mdiRocketLaunchOutline);
export const Share = createIcon(Icons.mdiShareVariantOutline);
export const Password = createIcon(Icons.mdiFormTextboxPassword);
export const Destruct = createIcon(Icons.mdiBomb);
export const CompactView = createIcon(Icons.mdiViewHeadline);
export const DetailedView = createIcon(Icons.mdiViewSequentialOutline);
export const MailCheck = createIcon(Icons.mdiEmailCheckOutline);
export const Discord = createIcon(Icons.mdiDiscord);
export const Twitter = createIcon(Icons.mdiTwitter);
export const Reddit = createIcon(Icons.mdiReddit);
export const Dismiss = createIcon(Icons.mdiClose);
export const File = createIcon(Icons.mdiFileOutline);
export const Download = createIcon(Icons.mdiArrowDown);
export const ImageDownload = createIcon(Icons.mdiImage);

View File

@@ -0,0 +1,21 @@
import { Flex, Text } from "rebass";
import { Loading } from "../icons";
export default function Loader({ title, text }) {
return (
<Flex
flexDirection="column"
flex={1}
justifyContent="center"
alignItems="center"
>
<Loading rotate />
<Text variant="subtitle" mt={4}>
{title}
</Text>
<Text variant="body" mt={2}>
{text}
</Text>
</Flex>
);
}

View File

@@ -4,7 +4,7 @@ import React, { useEffect, useMemo } from "react";
import { Flex, Box, Text, Button } from "rebass"; import { Flex, Box, Text, Button } from "rebass";
import { useIsUserPremium } from "../../hooks/use-is-user-premium"; import { useIsUserPremium } from "../../hooks/use-is-user-premium";
import useMobile from "../../utils/use-mobile"; import useMobile from "../../utils/use-mobile";
import Animated from "../animated"; import { AnimatedFlex } from "../animated";
function Menu(props) { function Menu(props) {
const { menuItems, data, closeMenu, id, style, sx, state } = props; const { menuItems, data, closeMenu, id, style, sx, state } = props;
@@ -193,7 +193,7 @@ function MobileMenuContainer({ style, id, state, title, children }) {
overflow="hidden" overflow="hidden"
sx={{ position: "relative" }} sx={{ position: "relative" }}
> >
<Animated.Flex <AnimatedFlex
width="100%" width="100%"
bg="background" bg="background"
sx={{ sx={{
@@ -221,7 +221,7 @@ function MobileMenuContainer({ style, id, state, title, children }) {
</Text> </Text>
{children} {children}
</Flex> </Flex>
</Animated.Flex> </AnimatedFlex>
</Flex> </Flex>
); );
} }

View File

@@ -1,16 +1,32 @@
import React, { useCallback } from "react"; import { useCallback } from "react";
import { Box, Flex } from "rebass"; import { Box, Flex } from "rebass";
import { useStore as useAppStore } from "../../stores/app-store"; import {
import * as Icon from "../icons"; Note,
import { useStore as useUserStore } from "../../stores/user-store"; Notebook,
import { useStore as useThemeStore } from "../../stores/theme-store"; StarOutline,
import Animated from "../animated"; Monographs,
Tag,
Trash,
Settings,
Notebook2,
Tag2,
Topic,
DarkMode,
LightMode,
Sync,
Login,
Circle,
} from "../icons";
import { AnimatedFlex } from "../animated";
import NavigationItem from "./navigation-item"; import NavigationItem from "./navigation-item";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import { db } from "../../common/db"; import { db } from "../../common/db";
import useMobile from "../../utils/use-mobile"; import useMobile from "../../utils/use-mobile";
import { useLocation } from "wouter";
import { showRenameColorDialog } from "../../common/dialog-controller"; import { showRenameColorDialog } from "../../common/dialog-controller";
import { useStore as useAppStore } from "../../stores/app-store";
import { useStore as useUserStore } from "../../stores/user-store";
import { useStore as useThemeStore } from "../../stores/theme-store";
import useLocation from "../../hooks/use-location";
function shouldSelectNavItem(route, pin) { function shouldSelectNavItem(route, pin) {
if (pin.type === "notebook") { if (pin.type === "notebook") {
@@ -23,31 +39,31 @@ function shouldSelectNavItem(route, pin) {
} }
const routes = [ const routes = [
{ title: "Notes", path: "/", icon: Icon.Note }, { title: "Notes", path: "/notes", icon: Note },
{ {
title: "Notebooks", title: "Notebooks",
path: "/notebooks", path: "/notebooks",
icon: Icon.Notebook, icon: Notebook,
}, },
{ {
title: "Favorites", title: "Favorites",
path: "/favorites", path: "/favorites",
icon: Icon.StarOutline, icon: StarOutline,
}, },
{ title: "Tags", path: "/tags", icon: Icon.Tag }, { title: "Tags", path: "/tags", icon: Tag },
{ {
title: "Monographs", title: "Monographs",
path: "/monographs", path: "/monographs",
icon: Icon.Monographs, icon: Monographs,
}, },
{ title: "Trash", path: "/trash", icon: Icon.Trash }, { title: "Trash", path: "/trash", icon: Trash },
]; ];
const bottomRoutes = [ const bottomRoutes = [
{ {
title: "Settings", title: "Settings",
path: "/settings", path: "/settings",
icon: Icon.Settings, icon: Settings,
}, },
]; ];
@@ -77,7 +93,7 @@ function NavigationMenu(props) {
); );
return ( return (
<Animated.Flex <AnimatedFlex
id="navigationmenu" id="navigationmenu"
flexDirection="column" flexDirection="column"
justifyContent="space-between" justifyContent="space-between"
@@ -141,7 +157,7 @@ function NavigationMenu(props) {
<NavigationItem <NavigationItem
key={color.id} key={color.id}
title={db.colors.alias(color.id)} title={db.colors.alias(color.id)}
icon={Icon.Circle} icon={Circle}
selected={location === `/colors/${color.id}`} selected={location === `/colors/${color.id}`}
color={color.title.toLowerCase()} color={color.title.toLowerCase()}
onClick={() => { onClick={() => {
@@ -181,10 +197,10 @@ function NavigationMenu(props) {
}} }}
icon={ icon={
pin.type === "notebook" pin.type === "notebook"
? Icon.Notebook2 ? Notebook2
: pin.type === "tag" : pin.type === "tag"
? Icon.Tag2 ? Tag2
: Icon.Topic : Topic
} }
isShortcut isShortcut
selected={shouldSelectNavItem(location, pin)} selected={shouldSelectNavItem(location, pin)}
@@ -204,13 +220,13 @@ function NavigationMenu(props) {
{theme === "light" ? ( {theme === "light" ? (
<NavigationItem <NavigationItem
title="Dark mode" title="Dark mode"
icon={Icon.DarkMode} icon={DarkMode}
onClick={toggleNightMode} onClick={toggleNightMode}
/> />
) : ( ) : (
<NavigationItem <NavigationItem
title="Light mode" title="Light mode"
icon={Icon.LightMode} icon={LightMode}
onClick={toggleNightMode} onClick={toggleNightMode}
/> />
)} )}
@@ -219,14 +235,14 @@ function NavigationMenu(props) {
<NavigationItem <NavigationItem
title="Sync" title="Sync"
isLoading={isSyncing} isLoading={isSyncing}
icon={Icon.Sync} icon={Sync}
onClick={sync} onClick={sync}
/> />
</> </>
) : ( ) : (
<NavigationItem <NavigationItem
title="Login" title="Login"
icon={Icon.Login} icon={Login}
onClick={() => navigate("/login")} onClick={() => navigate("/login")}
/> />
)} )}
@@ -242,7 +258,7 @@ function NavigationMenu(props) {
/> />
))} ))}
</Flex> </Flex>
</Animated.Flex> </AnimatedFlex>
); );
} }
export default NavigationMenu; export default NavigationMenu;

View File

@@ -0,0 +1,190 @@
import { Flex } from "rebass";
import { useStore as useThemeStore } from "../../stores/theme-store";
import { Button, Text } from "rebass";
import useTablet from "../../utils/use-tablet";
import {
Note,
Notebook,
StarOutline,
Monographs,
Tag,
Trash,
Settings,
DarkMode,
LightMode,
Sync,
Login,
} from "../icons";
import useLocation from "../../hooks/use-location";
const routes = [
{ title: "Notes", path: "/", icon: Note },
{
title: "Notebooks",
path: "/notebooks",
icon: Notebook,
},
{
title: "Favorites",
path: "/favorites",
icon: StarOutline,
},
{ title: "Tags", path: "/tags", icon: Tag },
{
title: "Monographs",
path: "/monographs",
icon: Monographs,
},
{ title: "Trash", path: "/trash", icon: Trash },
];
const bottomRoutes = [
{
title: "Settings",
path: "/settings",
icon: Settings,
},
];
const NAVIGATION_MENU_WIDTH = "10em";
const NAVIGATION_MENU_TABLET_WIDTH = "4em";
function NavigationMenu() {
const isLoggedIn = false;
const theme = useThemeStore((store) => store.theme);
const toggleNightMode = useThemeStore((store) => store.toggleNightMode);
const [location] = useLocation();
return (
<Flex
id="navigationmenu"
flexDirection="column"
justifyContent="space-between"
flex={1}
sx={{
borderRight: "1px solid",
borderRightColor: "border",
minWidth: [
NAVIGATION_MENU_WIDTH,
NAVIGATION_MENU_TABLET_WIDTH,
NAVIGATION_MENU_WIDTH,
],
maxWidth: [
NAVIGATION_MENU_WIDTH,
NAVIGATION_MENU_TABLET_WIDTH,
NAVIGATION_MENU_WIDTH,
],
zIndex: 1,
height: "auto",
position: "relative",
}}
bg={"bgSecondary"}
px={0}
>
<Flex
flexDirection="column"
sx={{
overflow: "scroll",
scrollbarWidth: "none",
"::-webkit-scrollbar": { width: 0, height: 0 },
msOverflowStyle: "none",
}}
>
{routes.map((item) => (
<NavigationItem
key={item.path}
title={item.title}
icon={item.icon}
selected={
item.path === "/"
? location === item.path
: location.startsWith(item.path)
}
/>
))}
</Flex>
<Flex flexDirection="column">
{theme === "light" ? (
<NavigationItem
title="Dark mode"
icon={DarkMode}
onClick={toggleNightMode}
/>
) : (
<NavigationItem
title="Light mode"
icon={LightMode}
onClick={toggleNightMode}
/>
)}
{isLoggedIn ? (
<>
<NavigationItem title="Sync" icon={Sync} />
</>
) : (
<NavigationItem title="Login" icon={Login} />
)}
{bottomRoutes.map((item) => (
<NavigationItem
key={item.path}
title={item.title}
icon={item.icon}
selected={location.startsWith(item.path)}
/>
))}
</Flex>
</Flex>
);
}
export default NavigationMenu;
function NavigationItem(props) {
const { icon: Icon, color, title, isLoading } = props;
const isTablet = useTablet();
return (
<Button
data-test-id={`navitem-${title.toLowerCase()}`}
variant="icon"
bg={props.selected ? "border" : "transparent"}
p={2}
mx={2}
mt={[1, 2, 1]}
sx={{
borderRadius: "default",
position: "relative",
":first-of-type": { mt: 2 },
":last-of-type": { mb: 2 },
}}
label={title}
title={title}
onClick={() => {
props.onClick();
}}
display="flex"
justifyContent={["flex-start", "center", "flex-start"]}
alignItems="center"
>
<Icon
size={isTablet ? 18 : 15}
color={color || (props.selected ? "primary" : "icon")}
rotate={isLoading}
/>
<Text
display={["block", "none", "block"]}
variant="body"
fontSize="subtitle"
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
fontWeight: props.selected ? "bold" : "normal",
}}
ml={1}
>
{title}
</Text>
</Button>
);
}

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Fav } from "../../assets/fav.svg"; import Fav from "../../assets/fav.svg";
import Placeholder from "./index"; import Placeholder from "./index";
function FavoritesPlaceholder() { function FavoritesPlaceholder() {

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Button, Flex, Text } from "rebass"; import { Button, Flex, Image, Text } from "rebass";
import { useStore } from "../../stores/theme-store"; import { useStore } from "../../stores/theme-store";
import { changeSvgTheme } from "../../utils/css"; import { changeSvgTheme } from "../../utils/css";
@@ -8,7 +8,7 @@ function Placeholder(props) {
useEffect(() => { useEffect(() => {
changeSvgTheme(accent); changeSvgTheme(accent);
}, [accent]); }, [accent]);
const { image: Image, text, callToAction } = props; const { image: PlaceholderImage, text, callToAction } = props;
return ( return (
<> <>
@@ -17,7 +17,7 @@ function Placeholder(props) {
alignSelf="stretch" alignSelf="stretch"
sx={{ position: "relative" }} sx={{ position: "relative" }}
> >
<Image width={"100%"} height={"100px"} /> <Image src={PlaceholderImage} width={"100%"} height={"100px"} />
<Text <Text
variant="body" variant="body"
mt={2} mt={2}

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Monographs } from "../../assets/monographs.svg"; import Monographs from "../../assets/monographs.svg";
import Placeholder from "./index"; import Placeholder from "./index";
function MonographsPlaceholder() { function MonographsPlaceholder() {

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Notebook } from "../../assets/notebook.svg"; import Notebook from "../../assets/notebook.svg";
import Placeholder from "./index"; import Placeholder from "./index";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { hashNavigate } from "../../navigation"; import { hashNavigate } from "../../navigation";

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Note } from "../../assets/note.svg"; import Note from "../../assets/note.svg";
import Placeholder from "./index"; import Placeholder from "./index";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { hashNavigate } from "../../navigation"; import { hashNavigate } from "../../navigation";

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Search } from "../../assets/search.svg"; import Search from "../../assets/search.svg";
import Placeholder from "./index"; import Placeholder from "./index";
function SearchPlaceholder({ text }) { function SearchPlaceholder({ text }) {

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Tag } from "../../assets/tag.svg"; import Tag from "../../assets/tag.svg";
import Placeholder from "./index"; import Placeholder from "./index";
function TagsPlaceholder() { function TagsPlaceholder() {

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Notebook } from "../../assets/notebook.svg"; import Notebook from "../../assets/notebook.svg";
import Placeholder from "./index"; import Placeholder from "./index";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { hashNavigate } from "../../navigation"; import { hashNavigate } from "../../navigation";

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { ReactComponent as Trash } from "../../assets/trash.svg"; import Trash from "../../assets/trash.svg";
import Placeholder from "./index"; import Placeholder from "./index";
function TrashPlaceholder() { function TrashPlaceholder() {

View File

@@ -1,7 +1,7 @@
import { useAnimation } from "framer-motion"; import { useAnimation } from "framer-motion";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Flex } from "rebass"; import { Flex } from "rebass";
import Animated from "../animated"; import { AnimatedBox } from "../animated";
function ProgressBar(props) { function ProgressBar(props) {
const { width, progress, duration = 1, onLoadingEnd, sx } = props; const { width, progress, duration = 1, onLoadingEnd, sx } = props;
@@ -12,7 +12,7 @@ function ProgressBar(props) {
return ( return (
<Flex overflow="hidden" width={width} sx={sx}> <Flex overflow="hidden" width={width} sx={sx}>
<Animated.Box <AnimatedBox
height={5} height={5}
initial={{ width: "0%" }} initial={{ width: "0%" }}
animate={animation} animate={animation}

View File

@@ -5,7 +5,7 @@ import { useStore } from "../../stores/editor-store";
import { AppEventManager, AppEvents, COLORS } from "../../common"; import { AppEventManager, AppEvents, COLORS } from "../../common";
import { db } from "../../common/db"; import { db } from "../../common/db";
import { useStore as useAppStore } from "../../stores/app-store"; import { useStore as useAppStore } from "../../stores/app-store";
import Animated from "../animated"; import { AnimatedFlex } from "../animated";
import Toggle from "./toggle"; import Toggle from "./toggle";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import IconTag from "../icon-tag"; import IconTag from "../icon-tag";
@@ -77,7 +77,7 @@ function Properties({ noteId }) {
if (isFocusMode || !sessionId) return null; if (isFocusMode || !sessionId) return null;
return ( return (
<> <>
<Animated.Flex <AnimatedFlex
animate={{ animate={{
x: arePropertiesVisible ? 0 : 800, x: arePropertiesVisible ? 0 : 800,
}} }}
@@ -325,7 +325,7 @@ function Properties({ noteId }) {
})} })}
</Card> </Card>
)} )}
</Animated.Flex> </AnimatedFlex>
</> </>
); );
} }

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { Box, Button, Flex, Text } from "rebass"; import { Box, Button, Flex, Text } from "rebass";
import EditorFooter from "../editor/footer"; import EditorFooter from "../editor/footer";
import * as Icon from "../icons"; import { Circle, Sync, Loading, Update } from "../icons";
import { useStore as useUserStore } from "../../stores/user-store"; import { useStore as useUserStore } from "../../stores/user-store";
import { useStore as useAppStore } from "../../stores/app-store"; import { useStore as useAppStore } from "../../stores/app-store";
import TimeAgo from "timeago-react"; import TimeAgo from "timeago-react";
@@ -43,7 +43,7 @@ function StatusBar() {
display="flex" display="flex"
sx={{ alignItems: "center", justifyContent: "center" }} sx={{ alignItems: "center", justifyContent: "center" }}
> >
<Icon.Circle <Circle
size={7} size={7}
color={user.isEmailConfirmed ? "success" : "warn"} color={user.isEmailConfirmed ? "success" : "warn"}
/> />
@@ -58,7 +58,7 @@ function StatusBar() {
onClick={sync} onClick={sync}
sx={{ alignItems: "center", justifyContent: "center" }} sx={{ alignItems: "center", justifyContent: "center" }}
> >
<Icon.Sync size={10} rotate={isSyncing} /> <Sync size={10} rotate={isSyncing} />
<Text variant="subBody" ml={1}> <Text variant="subBody" ml={1}>
{"Synced "} {"Synced "}
{lastSynced ? ( {lastSynced ? (
@@ -76,7 +76,7 @@ function StatusBar() {
onClick={() => navigate("/login")} onClick={() => navigate("/login")}
sx={{ alignItems: "center", justifyContent: "center" }} sx={{ alignItems: "center", justifyContent: "center" }}
> >
<Icon.Circle size={7} color="error" /> <Circle size={7} color="error" />
<Text variant="subBody" color="bgSecondaryText" ml={1}> <Text variant="subBody" color="bgSecondaryText" ml={1}>
Not logged in Not logged in
</Text> </Text>
@@ -84,7 +84,7 @@ function StatusBar() {
)} )}
{processingStatuses?.map(({ key, status, progress }) => ( {processingStatuses?.map(({ key, status, progress }) => (
<Flex key={key} ml={1} alignItems="center" justifyContent="center"> <Flex key={key} ml={1} alignItems="center" justifyContent="center">
<Icon.Loading size={12} /> <Loading size={12} />
<Text variant="subBody" color="bgSecondaryText" ml={1}> <Text variant="subBody" color="bgSecondaryText" ml={1}>
{progress ? `${progress}% ${status}` : status} {progress ? `${progress}% ${status}` : status}
</Text> </Text>
@@ -106,7 +106,7 @@ function StatusBar() {
}} }}
sx={{ alignItems: "center", justifyContent: "center" }} sx={{ alignItems: "center", justifyContent: "center" }}
> >
<Icon.Update <Update
rotate={ rotate={
updateStatus.type !== "updated" && updateStatus.type !== "updated" &&
updateStatus.type !== "completed" && updateStatus.type !== "completed" &&

View File

@@ -0,0 +1,27 @@
import { Box, Button, Text } from "rebass";
import { Circle } from "../icons";
function StatusBar() {
return (
<Box
bg="bgSecondary"
display={["none", "flex"]}
sx={{ borderTop: "1px solid", borderTopColor: "border" }}
justifyContent="space-between"
px={2}
>
<Button
variant="statusitem"
display="flex"
sx={{ alignItems: "center", justifyContent: "center" }}
>
<Circle size={7} color="error" />
<Text variant="subBody" color="bgSecondaryText" ml={1}>
Not logged in
</Text>
</Button>
</Box>
);
}
export default StatusBar;

View File

@@ -0,0 +1,16 @@
import { useEffect, useState } from "react";
import { initializeDatabase } from "../common/db";
export default function useDatabase() {
const [isAppLoaded, setIsAppLoaded] = useState(false);
useEffect(() => {
(async () => {
await import("../app.css");
await initializeDatabase();
setIsAppLoaded(true);
})();
}, []);
return [isAppLoaded];
}

View File

@@ -0,0 +1,18 @@
const { useState, useEffect } = require("react");
const { getCurrentPath, NavigationEvents } = require("../navigation");
export default function useLocation() {
const [location, setLocation] = useState(getCurrentPath());
useEffect(() => {
const navigateEvent = NavigationEvents.subscribe(
"onNavigate",
(_, location) => {
setLocation(location);
}
);
return () => {
navigateEvent.unsubscribe();
};
}, []);
return [location];
}

View File

@@ -1,7 +1,10 @@
import { useCallback, useEffect, useMemo, useRef } from "react"; import { useCallback, useEffect, useMemo, useRef } from "react";
export default function useSlider({ initialIndex, onSliding, onChange }) { export default function useSlider(
const ref = useRef(); sliderId,
{ initialIndex, onSliding, onChange }
) {
const ref = useRef(document.getElementById(sliderId));
const slides = useMemo(() => [], []); const slides = useMemo(() => [], []);
useEffect(() => { useEffect(() => {
@@ -49,5 +52,5 @@ export default function useSlider({ initialIndex, onSliding, onChange }) {
[ref, slides] [ref, slides]
); );
return [ref, slideToIndex]; return [slideToIndex];
} }

View File

@@ -1,65 +0,0 @@
html,
body {
font-size: 16px;
}
@media only screen and (max-width: 480px) {
html,
body {
font-size: 18px;
background-color: var(--background);
}
}
:root,
body,
#root {
margin: 0 !important;
height: 100%;
overflow: hidden;
}
* {
font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
@keyframes fadeUp {
0% {
transform: translateY(500px);
opacity: 0;
}
80% {
transform: translateY(0px);
opacity: 0.7;
}
100% {
opacity: 1;
}
}
/* svg {
width: 1.5rem;
} */
.ReactModal__Overlay {
opacity: 0;
transition: opacity 200ms ease-in-out;
}
.ReactModal__Overlay--after-open {
opacity: 1;
}
.ReactModal__Overlay--before-close {
opacity: 0;
}
.slide {
background-color: transparent !important;
}
.carousel.carousel-slider {
display: flex;
flex-direction: row;
}

View File

@@ -1,46 +1,46 @@
import "./commands"; import { render } from "react-dom";
import React from "react"; import { getCurrentPath } from "./navigation";
import { initializeDatabase } from "./common/db";
import "./index.css";
import * as serviceWorker from "./serviceWorkerRegistration"; import * as serviceWorker from "./serviceWorkerRegistration";
import { loadTrackerScript } from "./utils/analytics"; if (process.env.REACT_APP_PLATFORM === "desktop") require("./commands");
import Config from "./utils/config";
import { isTesting } from "./utils/platform";
if (process.env.NODE_ENV === "production") { const ROUTES = {
loadTrackerScript(); "/account/recovery": {
console.log = () => {}; component: () => import("./views/recovery"),
props: {},
},
"/account/verified": {
component: () => import("./views/email-confirmed"),
props: {},
},
"/signup": {
component: () => import("./views/auth"),
props: { type: "signup" },
},
"/login": {
component: () => import("./views/auth"),
props: { type: "login" },
},
"/recover": {
component: () => import("./views/auth"),
props: { type: "recover" },
},
default: { component: () => import("./app"), props: {} },
};
function getRoute() {
const path = getCurrentPath();
return ROUTES[path] || ROUTES.default;
} }
const HOMEPAGE_ROUTE = { 1: "/notebooks", 2: "/favorites", 3: "/tags" }; const route = getRoute();
route.component().then(({ default: Component }) => {
async function checkRedirects(db) { render(
const isLoggedIn = !!(await db.user.getUser()); <Component {...route.props} />,
if (window.location.pathname === "/") { document.getElementById("root"),
const skipInitiation = Config.get("skipInitiation", false); () => {
const homepage = Config.get("homepage", 0); document.getElementById("splash").remove();
if (!isTesting() && !isLoggedIn && !skipInitiation)
window.location.replace("/signup");
else if (homepage) {
const route = HOMEPAGE_ROUTE[homepage];
window.location.replace(route);
} }
} );
}
initializeDatabase().then(async (db) => {
await checkRedirects(db);
import("react-dom").then(({ render }) => {
import("./App").then(({ default: App }) => {
render(<App />, document.getElementById("root"), async () => {
document.getElementById("splash").remove();
import("react-modal").then(({ default: Modal }) => {
Modal.setAppElement("#root");
});
});
});
});
}); });
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change

View File

@@ -122,7 +122,6 @@ async function hashStream(reader) {
} }
async function readEncrypted(filename, key, cipherData) { async function readEncrypted(filename, key, cipherData) {
console.log("Reading encrypted file", filename);
const fileHandle = await streamablefs.readFile(filename); const fileHandle = await streamablefs.readFile(filename);
if (!fileHandle) { if (!fileHandle) {
console.error(`File not found. Filename: ${filename}`); console.error(`File not found. Filename: ${filename}`);
@@ -159,8 +158,6 @@ async function readEncrypted(filename, key, cipherData) {
} }
async function uploadFile(filename, requestOptions) { async function uploadFile(filename, requestOptions) {
console.log("Request to upload file", filename, requestOptions);
const fileHandle = await streamablefs.readFile(filename); const fileHandle = await streamablefs.readFile(filename);
if (!fileHandle) if (!fileHandle)
throw new Error(`File stream not found. Filename: ${filename}`); throw new Error(`File stream not found. Filename: ${filename}`);
@@ -261,7 +258,6 @@ function reportProgress(ev, { type, hash }) {
async function downloadFile(filename, requestOptions) { async function downloadFile(filename, requestOptions) {
const { url, headers, cancellationToken } = requestOptions; const { url, headers, cancellationToken } = requestOptions;
console.log("Request to download file", filename, url, headers);
if (await streamablefs.exists(filename)) return true; if (await streamablefs.exists(filename)) return true;
try { try {
@@ -273,7 +269,6 @@ async function downloadFile(filename, requestOptions) {
reportProgress(ev, { type: "download", hash: filename }), reportProgress(ev, { type: "download", hash: filename }),
}); });
console.log("File downloaded", filename, url, response);
if (!isSuccessStatusCode(response.status)) return false; if (!isSuccessStatusCode(response.status)) return false;
const distributor = new ChunkDistributor(ENCRYPTED_CHUNK_SIZE); const distributor = new ChunkDistributor(ENCRYPTED_CHUNK_SIZE);
distributor.fill(new Uint8Array(response.data)); distributor.fill(new Uint8Array(response.data));
@@ -352,7 +347,6 @@ function cancellable(operation) {
return { return {
execute: () => operation(filename, requestOptions), execute: () => operation(filename, requestOptions),
cancel: (message) => { cancel: (message) => {
console.log("Canceled", message);
source.cancel(message); source.cancel(message);
}, },
}; };

View File

@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import EventManager from "notes-core/utils/eventmanager"; import EventManager from "notes-core/utils/eventmanager";
// import { useCallback } from "react"; import Config from "../utils/config";
export function navigate(url, replaceOrQuery, replace) { export function navigate(url, replaceOrQuery, replace) {
if (typeof url !== "string") { if (typeof url !== "string") {
@@ -81,3 +81,13 @@ export function getCurrentHash() {
} }
export const NavigationEvents = new EventManager(); export const NavigationEvents = new EventManager();
const HOMEPAGE_ROUTE = {
0: "/notes",
1: "/notebooks",
2: "/favorites",
3: "/tags",
};
export function getHomeRoute() {
return HOMEPAGE_ROUTE[Config.get("homepage", 0)];
}

View File

@@ -1,14 +0,0 @@
import React from "react";
import AccountRecovery from "../views/recovery";
import EmailConfirmed from "../views/emailconfirmed";
import Auth from "../views/auth";
const rootroutes = {
"/account/recovery": () => ({ component: <AccountRecovery /> }),
"/account/verified": () => ({ component: <EmailConfirmed /> }),
"/signup": () => ({ component: <Auth type="signup" /> }),
"/login": () => ({ component: <Auth type="login" /> }),
"/recover": () => ({ component: <Auth type="recover" /> }),
};
export default rootroutes;

View File

@@ -15,7 +15,7 @@ import Monographs from "../views/monographs";
import { showToast } from "../utils/toast"; import { showToast } from "../utils/toast";
const routes = { const routes = {
"/": () => ({ "/notes": () => ({
key: "home", key: "home",
type: "notes", type: "notes",
title: "Notes", title: "Notes",

View File

@@ -67,7 +67,6 @@ class UserStore extends BaseStore {
}); });
EV.subscribe(EVENTS.databaseSyncRequested, async () => { EV.subscribe(EVENTS.databaseSyncRequested, async () => {
console.log("Sync requested.");
await appStore.sync(false); await appStore.sync(false);
}); });

View File

@@ -1,27 +1,27 @@
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import makeMatcher from "wouter/matcher"; import makeMatcher from "wouter/matcher";
import { navigate } from "../navigation"; import { navigate, getHomeRoute } from "../navigation";
import { store as selectionStore } from "../stores/selection-store";
export default function useRoutes(routes, options) { export default function useRoutes(routes, options) {
const [location] = useLocation(); const [location] = useLocation();
const matcher = makeMatcher(); const matcher = makeMatcher();
// TODO move this to an extension function if (location === "/") navigate(getHomeRoute());
if (selectionStore.get().isSelectionMode)
selectionStore.toggleSelectionMode(false); options?.hooks?.beforeNavigate(location);
for (var key in routes) { for (var key in routes) {
const [match, params] = matcher(key, location); const [match, params] = matcher(key, location);
if (match) { if (match) {
const result = routes[key](params); const result = routes[key](params);
if (!result) break; if (!result) break;
return result; return [result, location];
} }
} }
if (!options) return; if (!options) return [];
const { fallbackRoute } = options; const { fallbackRoute } = options;
if (fallbackRoute) { if (fallbackRoute) {
navigate(fallbackRoute); navigate(fallbackRoute);
} }
return [];
} }

View File

@@ -1,12 +1,14 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Box, Button, Flex, Text } from "rebass"; import { Box, Button, Flex, Text } from "rebass";
import ThemeProvider from "../components/theme-provider"; import ThemeProvider from "../components/theme-provider";
import * as Icon from "../components/icons"; import { CheckCircle, Loading, ArrowRight, Error } from "../components/icons";
import Field from "../components/field"; import Field from "../components/field";
import { getQueryParams, navigate, useQueryParams } from "../navigation"; import { getQueryParams, navigate, useQueryParams } from "../navigation";
import { store as userstore } from "../stores/user-store"; import { store as userstore } from "../stores/user-store";
import { db } from "../common/db"; import { db } from "../common/db";
import Config from "../utils/config"; import Config from "../utils/config";
import useDatabase from "../hooks/use-database";
import Loader from "../components/loader";
const features = [ const features = [
{ {
@@ -90,7 +92,6 @@ const authTypes = {
</> </>
), ),
onSubmit: async (form, onError) => { onSubmit: async (form, onError) => {
console.log(form);
if (form.password !== form.confirmPassword) { if (form.password !== form.confirmPassword) {
onError("Passwords do not match."); onError("Passwords do not match.");
return; return;
@@ -162,6 +163,7 @@ function Auth(props) {
const [error, setError] = useState(); const [error, setError] = useState();
const [success, setSuccess] = useState(); const [success, setSuccess] = useState();
const [featureIndex, setFeatureIndex] = useState(0); const [featureIndex, setFeatureIndex] = useState(0);
const [isAppLoaded] = useDatabase();
const data = authTypes[type]; const data = authTypes[type];
const feature = features[featureIndex]; const feature = features[featureIndex];
@@ -188,7 +190,7 @@ function Auth(props) {
setSuccess(); setSuccess();
if (authTypes[type].resetOnNavigate) { if (authTypes[type].resetOnNavigate) {
const form = document.getElementById("authForm"); const form = document.getElementById("authForm");
form.reset(); form?.reset();
} }
}, [type]); }, [type]);
@@ -211,7 +213,7 @@ function Auth(props) {
navigate("/"); navigate("/");
}} }}
> >
<Icon.ArrowRight size={16} /> <ArrowRight size={16} />
<Text variant="body" ml={1}> <Text variant="body" ml={1}>
Go to app Go to app
</Text> </Text>
@@ -262,7 +264,7 @@ function Auth(props) {
mt={4} mt={4}
alignItems="center" alignItems="center"
> >
<Icon.ArrowRight size={18} color="static" /> <ArrowRight size={18} color="static" />
<Text variant="title" ml={1} color="static"> <Text variant="title" ml={1} color="static">
{feature.linkText} {feature.linkText}
</Text> </Text>
@@ -285,151 +287,171 @@ function Auth(props) {
</Flex> </Flex>
</Box> </Box>
<Flex justifyContent="center" flex={1} flexShrink={0}> <Flex justifyContent="center" flex={1} flexShrink={0}>
<Flex {isAppLoaded ? (
as="form" <Flex
id="authForm" as="form"
flexDirection="column" id="authForm"
justifyContent="center" flexDirection="column"
alignItems="stretch"
width={["95%", "55%", "35%"]}
onSubmit={async (e) => {
e.preventDefault();
setIsSubmitting(true);
const form = new FormData(e.target);
const obj = Object.fromEntries(form.entries());
obj.redirect = redirect;
await data.onSubmit(
obj,
(error) => {
setIsSubmitting(false);
setError(error);
},
setSuccess
);
setIsSubmitting(false);
}}
>
<Text variant="heading" textAlign="center" fontWeight="heading">
{data.title}
</Text>
<Text variant="body" mt={1} textAlign="center" color="fontTertiary">
{data.subtitle.text}{" "}
{data.subtitle.action && (
<Text
sx={{ ":hover": { color: "dimPrimary" }, cursor: "pointer" }}
as="b"
color="primary"
onClick={data.subtitle.action.onClick}
>
{data.subtitle.action.text}
</Text>
)}
</Text>
<Field
styles={{
container: { mt: 50 },
}}
data-test-id="email"
id="email"
required
name="email"
autoComplete="email"
label={data.labels.email}
helpText={data.helpTexts?.email}
/>
{data.labels.password && (
<Field
styles={{
container: { mt: 2 },
}}
data-test-id="password"
id="password"
required
name="password"
autoComplete={data.autoComplete?.password}
label={data.labels.password}
type="password"
/>
)}
{data.confirmPassword && (
<Field
styles={{
container: { mt: 2 },
}}
data-test-id="confirm-password"
id="confirmPassword"
required
name="confirmPassword"
autoComplete="confirm-password"
label="Confirm your password"
type="password"
/>
)}
{data.supportsPasswordRecovery && (
<Button
type="button"
alignSelf="start"
mt={2}
variant="anchor"
onClick={() => navigate("/recover", getQueryParams())}
>
Forgot password?
</Button>
)}
<Button
data-test-id="submitButton"
display="flex"
type="submit"
mt={4}
variant="primary"
disabled={isSubmitting}
sx={{ borderRadius: "default" }}
justifyContent="center" justifyContent="center"
alignItems="center" alignItems="stretch"
width={["95%", "55%", "35%"]}
onSubmit={async (e) => {
e.preventDefault();
setIsSubmitting(true);
const form = new FormData(e.target);
const obj = Object.fromEntries(form.entries());
obj.redirect = redirect;
await data.onSubmit(
obj,
(error) => {
setIsSubmitting(false);
setError(error);
},
setSuccess
);
setIsSubmitting(false);
}}
> >
{isSubmitting ? ( <Text variant="heading" textAlign="center" fontWeight="heading">
<> {data.title}
<Icon.Loading color="static" size={16} sx={{ mr: 1 }} />{" "}
{data.primaryAction.loadingText}
</>
) : (
data.primaryAction.text
)}
</Button>
{data.secondaryAction && (
<Button
type="button"
variant="secondary"
mt={1}
onClick={data.secondaryAction.onClick}
>
{data.secondaryAction.text}
</Button>
)}
{error && (
<Flex bg="errorBg" p={1} mt={2} sx={{ borderRadius: "default" }}>
<Icon.Error size={15} color="error" />
<Text variant="error" ml={1}>
{error}
</Text>
</Flex>
)}
{success && (
<Flex bg="shade" p={1} mt={2} sx={{ borderRadius: "default" }}>
<Icon.CheckCircle size={15} color="primary" />
<Text variant="error" color="primary" ml={1}>
{success}
</Text>
</Flex>
)}
{data.agreementText && (
<Text mt={2} variant="subBody">
{data.agreementText}
</Text> </Text>
)} <Text
</Flex> variant="body"
mt={1}
textAlign="center"
color="fontTertiary"
>
{data.subtitle.text}{" "}
{data.subtitle.action && (
<Text
sx={{
":hover": { color: "dimPrimary" },
cursor: "pointer",
}}
as="b"
color="primary"
onClick={data.subtitle.action.onClick}
>
{data.subtitle.action.text}
</Text>
)}
</Text>
<Field
styles={{
container: { mt: 50 },
}}
data-test-id="email"
id="email"
required
name="email"
autoComplete="email"
label={data.labels.email}
helpText={data.helpTexts?.email}
/>
{data.labels.password && (
<Field
styles={{
container: { mt: 2 },
}}
data-test-id="password"
id="password"
required
name="password"
autoComplete={data.autoComplete?.password}
label={data.labels.password}
type="password"
/>
)}
{data.confirmPassword && (
<Field
styles={{
container: { mt: 2 },
}}
data-test-id="confirm-password"
id="confirmPassword"
required
name="confirmPassword"
autoComplete="confirm-password"
label="Confirm your password"
type="password"
/>
)}
{data.supportsPasswordRecovery && (
<Button
type="button"
alignSelf="start"
mt={2}
variant="anchor"
onClick={() => navigate("/recover", getQueryParams())}
>
Forgot password?
</Button>
)}
<Button
data-test-id="submitButton"
display="flex"
type="submit"
mt={4}
variant="primary"
disabled={isSubmitting}
sx={{ borderRadius: "default" }}
justifyContent="center"
alignItems="center"
>
{isSubmitting ? (
<>
<Loading color="static" size={16} sx={{ mr: 1 }} />{" "}
{data.primaryAction.loadingText}
</>
) : (
data.primaryAction.text
)}
</Button>
{data.secondaryAction && (
<Button
type="button"
variant="secondary"
mt={1}
onClick={data.secondaryAction.onClick}
>
{data.secondaryAction.text}
</Button>
)}
{error && (
<Flex
bg="errorBg"
p={1}
mt={2}
sx={{ borderRadius: "default" }}
>
<Error size={15} color="error" />
<Text variant="error" ml={1}>
{error}
</Text>
</Flex>
)}
{success && (
<Flex bg="shade" p={1} mt={2} sx={{ borderRadius: "default" }}>
<CheckCircle size={15} color="primary" />
<Text variant="error" color="primary" ml={1}>
{success}
</Text>
</Flex>
)}
{data.agreementText && (
<Text mt={2} variant="subBody">
{data.agreementText}
</Text>
)}
</Flex>
) : (
<Loader
title="Did you know?"
text="Your password never leaves your device. Ever."
/>
)}
</Flex> </Flex>
</Flex> </Flex>
</ThemeProvider> </ThemeProvider>

View File

@@ -1,3 +1,4 @@
import "../app.css";
import { useEffect } from "react"; import { useEffect } from "react";
import { Flex, Text } from "rebass"; import { Flex, Text } from "rebass";
import ThemeProvider from "../components/theme-provider"; import ThemeProvider from "../components/theme-provider";

View File

@@ -10,6 +10,7 @@ import { useCallback } from "react";
import { showRecoveryKeyDialog } from "../common/dialog-controller"; import { showRecoveryKeyDialog } from "../common/dialog-controller";
import { createBackup } from "../common"; import { createBackup } from "../common";
import Logo from "../assets/logo.svg"; import Logo from "../assets/logo.svg";
import useDatabase from "../hooks/use-database";
function navigate(path) { function navigate(path) {
window.location.href = path; window.location.href = path;
@@ -42,7 +43,9 @@ function useRecovery() {
} }
function useAuthenticateUser({ code, userId, performAction }) { function useAuthenticateUser({ code, userId, performAction }) {
const [isAppLoaded] = useDatabase();
useEffect(() => { useEffect(() => {
if (!isAppLoaded) return;
performAction({ performAction({
message: "Authenticating. Please wait...", message: "Authenticating. Please wait...",
error: "Failed to authenticate. Please try again.", error: "Failed to authenticate. Please try again.",
@@ -63,7 +66,7 @@ function useAuthenticateUser({ code, userId, performAction }) {
} }
await db.user.fetchUser(true); await db.user.fetchUser(true);
} }
}, [code, userId, performAction]); }, [code, userId, performAction, isAppLoaded]);
} }
const steps = [RecoveryKeyStep, BackupDataStep, NewPasswordStep, FinalStep]; const steps = [RecoveryKeyStep, BackupDataStep, NewPasswordStep, FinalStep];