mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-25 04:32:31 +01:00
web: load tag & color title lazily
This commit is contained in:
@@ -23,6 +23,7 @@ import { ArrowLeft, Menu, Search, Plus } from "../icons";
|
||||
import { useStore } from "../../stores/app-store";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
import { navigate } from "../../navigation";
|
||||
import usePromise from "../../hooks/use-promise";
|
||||
|
||||
export type RouteContainerButtons = {
|
||||
search?: {
|
||||
@@ -40,7 +41,7 @@ export type RouteContainerButtons = {
|
||||
|
||||
export type RouteContainerProps = {
|
||||
type: string;
|
||||
title?: string;
|
||||
title?: string | (() => Promise<string | undefined>);
|
||||
buttons?: RouteContainerButtons;
|
||||
};
|
||||
function RouteContainer(props: PropsWithChildren<RouteContainerProps>) {
|
||||
@@ -56,7 +57,11 @@ function RouteContainer(props: PropsWithChildren<RouteContainerProps>) {
|
||||
export default RouteContainer;
|
||||
|
||||
function Header(props: RouteContainerProps) {
|
||||
const { title, buttons, type } = props;
|
||||
const { buttons, type } = props;
|
||||
const titlePromise = usePromise<string | undefined>(
|
||||
() => (typeof props.title === "string" ? props.title : props.title?.()),
|
||||
[props.title]
|
||||
);
|
||||
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
|
||||
const isMobile = useMobile();
|
||||
|
||||
@@ -84,9 +89,9 @@ function Header(props: RouteContainerProps) {
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
{title && (
|
||||
{titlePromise.status === "fulfilled" && titlePromise.value && (
|
||||
<Text variant="heading" data-test-id="routeHeader" color="heading">
|
||||
{title}
|
||||
{titlePromise.value}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -41,7 +41,7 @@ function Tag(props: TagProps) {
|
||||
item={item}
|
||||
isCompact
|
||||
title={
|
||||
<Text as="span">
|
||||
<Text as="span" variant="body">
|
||||
<Text as="span" sx={{ color: "accent" }}>
|
||||
{"#"}
|
||||
</Text>
|
||||
|
||||
84
apps/web/src/hooks/use-promise.ts
Normal file
84
apps/web/src/hooks/use-promise.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
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 { DependencyList, useEffect, useState } from "react";
|
||||
|
||||
export type PromiseResult<T> = PromisePendingResult | PromiseSettledResult<T>;
|
||||
|
||||
export interface PromisePendingResult {
|
||||
status: "pending";
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that creates a promise, takes a signal to abort fetch requests.
|
||||
*/
|
||||
export type PromiseFactoryFn<T> = (signal: AbortSignal) => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* Takes a function that creates a Promise and returns its pending, fulfilled, or rejected result.
|
||||
*
|
||||
* ```ts
|
||||
* const result = usePromise(() => fetch('/api/products'))
|
||||
* ```
|
||||
*
|
||||
* Also takes a list of dependencies, when the dependencies change the promise is recreated.
|
||||
*
|
||||
* ```ts
|
||||
* const result = usePromise(() => fetch(`/api/products/${id}`), [id])
|
||||
* ```
|
||||
*
|
||||
* Can abort a fetch request, a [signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) is provided from the factory function to do so.
|
||||
*
|
||||
* ```ts
|
||||
* const result = usePromise(signal => fetch(`/api/products/${id}`, { signal }), [id])
|
||||
* ```
|
||||
*
|
||||
* @param factory Function that creates the promise.
|
||||
* @param deps If present, promise will be recreated if the values in the list change.
|
||||
*/
|
||||
export default function usePromise<T>(
|
||||
factory: PromiseFactoryFn<T>,
|
||||
deps: DependencyList = []
|
||||
): PromiseResult<T> {
|
||||
const [result, setResult] = useState<PromiseResult<T>>({ status: "pending" });
|
||||
|
||||
useEffect(() => {
|
||||
if (result.status !== "pending") {
|
||||
setResult({ status: "pending" });
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
async function handlePromise() {
|
||||
const [promiseResult] = await Promise.allSettled([factory(signal)]);
|
||||
|
||||
if (!signal.aborted) {
|
||||
setResult(promiseResult);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
handlePromise();
|
||||
|
||||
return () => controller.abort();
|
||||
}, deps);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import { CREATE_BUTTON_MAP } from "../common";
|
||||
type RouteResult = {
|
||||
key: string;
|
||||
type: "notes" | "notebooks" | "reminders" | "trash" | "tags" | "search";
|
||||
title?: string;
|
||||
title?: string | (() => Promise<string | undefined>);
|
||||
component: React.ReactNode;
|
||||
props?: any;
|
||||
buttons?: RouteContainerButtons;
|
||||
@@ -171,14 +171,15 @@ const routes = defineRoutes({
|
||||
}
|
||||
}),
|
||||
"/tags/:tagId": ({ tagId }) => {
|
||||
const tag = db.tags.tag(tagId);
|
||||
if (!tag) return false;
|
||||
const { id, title } = tag;
|
||||
notestore.setContext({ type: "tag", value: id });
|
||||
notestore.setContext({ type: "tag", id: tagId });
|
||||
return defineRoute({
|
||||
key: "notes",
|
||||
type: "notes",
|
||||
title: `#${title}`,
|
||||
title: async () => {
|
||||
const tag = await db.tags.tag(tagId);
|
||||
if (!tag) return;
|
||||
return `#${tag.title}`;
|
||||
},
|
||||
component: Notes,
|
||||
buttons: {
|
||||
create: CREATE_BUTTON_MAP.notes,
|
||||
@@ -187,28 +188,26 @@ const routes = defineRoutes({
|
||||
onClick: () => navigate("/tags")
|
||||
},
|
||||
search: {
|
||||
title: `Search #${title} notes`
|
||||
title: `Search notes`
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
"/colors/:colorId": ({ colorId }) => {
|
||||
const color = db.colors.color(colorId);
|
||||
if (!color) {
|
||||
navigate("/");
|
||||
return false;
|
||||
}
|
||||
const { id, title } = color;
|
||||
notestore.setContext({ type: "color", value: id });
|
||||
notestore.setContext({ type: "color", id: colorId });
|
||||
return defineRoute({
|
||||
key: "notes",
|
||||
type: "notes",
|
||||
title: title,
|
||||
title: async () => {
|
||||
const color = await db.colors.color(colorId);
|
||||
if (!color) return;
|
||||
return `${color.title}`;
|
||||
},
|
||||
component: Notes,
|
||||
buttons: {
|
||||
create: CREATE_BUTTON_MAP.notes,
|
||||
search: {
|
||||
title: `Search ${title} colored notes`
|
||||
title: `Search notes`
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user