feat: improve performance (#234)
* feat: use negentropy as much as possible * update * update
This commit is contained in:
15
src/app.tsx
15
src/app.tsx
@@ -1,27 +1,24 @@
|
||||
import {
|
||||
type PersistedQuery,
|
||||
experimental_createPersister,
|
||||
} from "@tanstack/query-persist-client-core";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import { type } from "@tauri-apps/plugin-os";
|
||||
import { StrictMode } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { newQueryStorage } from "./commons";
|
||||
import { routeTree } from "./routes.gen"; // auto generated file
|
||||
import type { LumeEvent } from "./system";
|
||||
import "./app.css";
|
||||
import { experimental_createPersister } from "@tanstack/query-persist-client-core";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
import { newQueryStorage } from "./commons";
|
||||
|
||||
const platform = type();
|
||||
const tauriStore = new Store(".lume.dat");
|
||||
const store = new Store(".cache");
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 30,
|
||||
persister: experimental_createPersister<PersistedQuery>({
|
||||
storage: newQueryStorage(tauriStore),
|
||||
maxAge: 1000 * 60 * 60 * 24, // 24 hours,
|
||||
persister: experimental_createPersister({
|
||||
storage: newQueryStorage(store),
|
||||
maxAge: 1000 * 60 * 60 * 12, // 12 hours
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -96,6 +96,9 @@ async resetPassword(key: string, password: string) : Promise<Result<null, string
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async isAccountSync(id: string) : Promise<boolean> {
|
||||
return await TAURI_INVOKE("is_account_sync", { id });
|
||||
},
|
||||
async login(account: string, password: string) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("login", { account, password }) };
|
||||
@@ -136,14 +139,6 @@ async setContactList(publicKeys: string[]) : Promise<Result<boolean, string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async isContactListEmpty() : Promise<Result<boolean, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("is_contact_list_empty") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async checkContact(hex: string) : Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("check_contact", { hex }) };
|
||||
@@ -320,9 +315,9 @@ async getEventsBy(publicKey: string, limit: number) : Promise<Result<RichEvent[]
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEventsFromContacts(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async getLocalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_events_from_contacts", { until }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_local_events", { until }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -475,7 +470,7 @@ subscription: "subscription"
|
||||
|
||||
export type Column = { label: string; url: string; x: number; y: number; width: number; height: number }
|
||||
export type Mention = { pubkey: string; avatar: string; display_name: string; name: string }
|
||||
export type Meta = { content: string; images: string[]; videos: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
||||
export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
||||
export type NewSettings = Settings
|
||||
export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null }
|
||||
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LumeWindow } from "@/system";
|
||||
import { FrameCorners } from "@phosphor-icons/react";
|
||||
import { ListPlus } from "@phosphor-icons/react";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { useNoteContext } from "../provider";
|
||||
|
||||
@@ -15,7 +15,7 @@ export function NoteOpenThread() {
|
||||
onClick={() => LumeWindow.openEvent(event)}
|
||||
className="group inline-flex h-7 w-14 bg-neutral-100 dark:bg-white/10 rounded-full items-center justify-center text-sm font-medium text-neutral-800 dark:text-neutral-200 hover:text-blue-500 hover:bg-neutral-200 dark:hover:bg-white/20"
|
||||
>
|
||||
<FrameCorners className="shrink-0 size-4" />
|
||||
<ListPlus className="shrink-0 size-4" />
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Hashtag } from "./mentions/hashtag";
|
||||
import { MentionNote } from "./mentions/note";
|
||||
import { MentionUser } from "./mentions/user";
|
||||
import { Images } from "./preview/images";
|
||||
import { Videos } from "./preview/videos";
|
||||
import { useNoteContext } from "./provider";
|
||||
|
||||
export function NoteContent({
|
||||
@@ -102,14 +101,9 @@ export function NoteContent({
|
||||
{content}
|
||||
</div>
|
||||
{visible ? (
|
||||
<>
|
||||
{event.meta?.images.length ? (
|
||||
<Images urls={event.meta.images} />
|
||||
) : null}
|
||||
{event.meta?.videos.length ? (
|
||||
<Videos urls={event.meta.videos} />
|
||||
) : null}
|
||||
</>
|
||||
event.meta?.images.length ? (
|
||||
<Images urls={event.meta.images} />
|
||||
) : null
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Hashtag } from "./mentions/hashtag";
|
||||
import { MentionNote } from "./mentions/note";
|
||||
import { MentionUser } from "./mentions/user";
|
||||
import { ImagePreview } from "./preview/image";
|
||||
import { VideoPreview } from "./preview/video";
|
||||
import { useNoteContext } from "./provider";
|
||||
|
||||
export function NoteContentLarge({
|
||||
@@ -18,7 +17,7 @@ export function NoteContentLarge({
|
||||
const content = useMemo(() => {
|
||||
try {
|
||||
// Get parsed meta
|
||||
const { images, videos, hashtags, events, mentions } = event.meta;
|
||||
const { images, hashtags, events, mentions } = event.meta;
|
||||
|
||||
// Define rich content
|
||||
let richContent: ReactNode[] | string = event.content;
|
||||
@@ -48,12 +47,6 @@ export function NoteContentLarge({
|
||||
));
|
||||
}
|
||||
|
||||
for (const video of videos) {
|
||||
richContent = reactStringReplace(richContent, video, (match, i) => (
|
||||
<VideoPreview key={match + i} url={match} />
|
||||
));
|
||||
}
|
||||
|
||||
richContent = reactStringReplace(
|
||||
richContent,
|
||||
/(https?:\/\/\S+)/gi,
|
||||
@@ -75,8 +68,7 @@ export function NoteContentLarge({
|
||||
));
|
||||
|
||||
return richContent;
|
||||
} catch (e) {
|
||||
console.log("[parser]: ", e);
|
||||
} catch {
|
||||
return event.content;
|
||||
}
|
||||
}, [event.content]);
|
||||
|
||||
@@ -15,8 +15,7 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
|
||||
const picture = useMemo(() => {
|
||||
if (service?.length && user.profile?.picture?.length) {
|
||||
const url = `${service}?url=${user.profile?.picture}&w=100&h=100&default=1&n=-1`;
|
||||
return url;
|
||||
return `${service}?url=${user.profile?.picture}&w=100&h=100&n=-1&default=${user.profile?.picture}`;
|
||||
} else {
|
||||
return user.profile?.picture;
|
||||
}
|
||||
|
||||
@@ -59,10 +59,10 @@ export function UserFollowButton({ className }: { className?: string }) {
|
||||
type="button"
|
||||
disabled={isPending}
|
||||
onClick={() => toggleFollow()}
|
||||
className={cn("w-max", className)}
|
||||
className={cn("w-max gap-1", className)}
|
||||
>
|
||||
{isError ? "Error" : null}
|
||||
{isPending || isLoading ? <Spinner className="size-4" /> : null}
|
||||
{isPending || isLoading ? <Spinner className="size-3" /> : null}
|
||||
{isFollow ? "Unfollow" : "Follow"}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { createFileRoute } from '@tanstack/react-router'
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as LoadingImport } from './routes/loading'
|
||||
import { Route as BootstrapRelaysImport } from './routes/bootstrap-relays'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as EditorIndexImport } from './routes/editor/index'
|
||||
@@ -95,6 +96,11 @@ const NewLazyRoute = NewLazyImport.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route))
|
||||
|
||||
const LoadingRoute = LoadingImport.update({
|
||||
path: '/loading',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const BootstrapRelaysRoute = BootstrapRelaysImport.update({
|
||||
path: '/bootstrap-relays',
|
||||
getParentRoute: () => rootRoute,
|
||||
@@ -338,6 +344,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof BootstrapRelaysImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/loading': {
|
||||
id: '/loading'
|
||||
path: '/loading'
|
||||
fullPath: '/loading'
|
||||
preLoaderRoute: typeof LoadingImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/new': {
|
||||
id: '/new'
|
||||
path: '/new'
|
||||
@@ -713,6 +726,7 @@ const ColumnsRouteWithChildren =
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||
'/loading': typeof LoadingRoute
|
||||
'/new': typeof NewLazyRoute
|
||||
'/reset': typeof ResetLazyRoute
|
||||
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
||||
@@ -751,6 +765,7 @@ export interface FileRoutesByFullPath {
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||
'/loading': typeof LoadingRoute
|
||||
'/new': typeof NewLazyRoute
|
||||
'/reset': typeof ResetLazyRoute
|
||||
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
||||
@@ -790,6 +805,7 @@ export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||
'/loading': typeof LoadingRoute
|
||||
'/new': typeof NewLazyRoute
|
||||
'/reset': typeof ResetLazyRoute
|
||||
'/$account': typeof AccountRouteWithChildren
|
||||
@@ -833,6 +849,7 @@ export interface FileRouteTypes {
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/bootstrap-relays'
|
||||
| '/loading'
|
||||
| '/new'
|
||||
| '/reset'
|
||||
| '/$account'
|
||||
@@ -870,6 +887,7 @@ export interface FileRouteTypes {
|
||||
to:
|
||||
| '/'
|
||||
| '/bootstrap-relays'
|
||||
| '/loading'
|
||||
| '/new'
|
||||
| '/reset'
|
||||
| '/$account'
|
||||
@@ -907,6 +925,7 @@ export interface FileRouteTypes {
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/bootstrap-relays'
|
||||
| '/loading'
|
||||
| '/new'
|
||||
| '/reset'
|
||||
| '/$account'
|
||||
@@ -949,6 +968,7 @@ export interface FileRouteTypes {
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
BootstrapRelaysRoute: typeof BootstrapRelaysRoute
|
||||
LoadingRoute: typeof LoadingRoute
|
||||
NewLazyRoute: typeof NewLazyRoute
|
||||
ResetLazyRoute: typeof ResetLazyRoute
|
||||
AccountRoute: typeof AccountRouteWithChildren
|
||||
@@ -963,6 +983,7 @@ export interface RootRouteChildren {
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
BootstrapRelaysRoute: BootstrapRelaysRoute,
|
||||
LoadingRoute: LoadingRoute,
|
||||
NewLazyRoute: NewLazyRoute,
|
||||
ResetLazyRoute: ResetLazyRoute,
|
||||
AccountRoute: AccountRouteWithChildren,
|
||||
@@ -988,6 +1009,7 @@ export const routeTree = rootRoute
|
||||
"children": [
|
||||
"/",
|
||||
"/bootstrap-relays",
|
||||
"/loading",
|
||||
"/new",
|
||||
"/reset",
|
||||
"/$account",
|
||||
@@ -1005,6 +1027,9 @@ export const routeTree = rootRoute
|
||||
"/bootstrap-relays": {
|
||||
"filePath": "bootstrap-relays.tsx"
|
||||
},
|
||||
"/loading": {
|
||||
"filePath": "loading.tsx"
|
||||
},
|
||||
"/new": {
|
||||
"filePath": "new.lazy.tsx"
|
||||
},
|
||||
|
||||
@@ -224,7 +224,7 @@ function ReplyList() {
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center w-full mb-3 h-12 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Spinner className="size-5" />
|
||||
<Spinner className="size-4" />
|
||||
<span className="text-sm font-medium">Getting replies...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function Screen() {
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||
const res = await commands.getEventsFromContacts(until);
|
||||
const res = await commands.getLocalEvents(until);
|
||||
|
||||
if (res.status === "error") {
|
||||
throw new Error(res.error);
|
||||
|
||||
@@ -11,7 +11,7 @@ export const Route = createLazyFileRoute("/columns/_layout/trending")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
export function Screen() {
|
||||
function Screen() {
|
||||
const { isLoading, isError, data } = useQuery({
|
||||
queryKey: ["trending-notes"],
|
||||
queryFn: async ({ signal }) => {
|
||||
|
||||
@@ -64,11 +64,21 @@ function Screen() {
|
||||
appSettings.setState(() => settings.data);
|
||||
}
|
||||
|
||||
navigate({
|
||||
to: "/$account/home",
|
||||
params: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
const status = await commands.isAccountSync(res.data);
|
||||
|
||||
if (status) {
|
||||
navigate({
|
||||
to: "/$account/home",
|
||||
params: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
} else {
|
||||
navigate({
|
||||
to: "/loading",
|
||||
search: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await message(res.error, { title: "Login", kind: "error" });
|
||||
return;
|
||||
|
||||
47
src/routes/loading.tsx
Normal file
47
src/routes/loading.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Spinner } from "@/components";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type RouteSearch = {
|
||||
account: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/loading")({
|
||||
validateSearch: (search: Record<string, string>): RouteSearch => {
|
||||
return {
|
||||
account: search.account,
|
||||
};
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const navigate = Route.useNavigate();
|
||||
const search = Route.useSearch();
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen("synchronized", () => {
|
||||
navigate({
|
||||
to: "/$account/home",
|
||||
params: { account: search.account },
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<div className="flex flex-col gap-2 items-center justify-center text-center">
|
||||
<Spinner />
|
||||
<p className="text-sm">
|
||||
Fetching necessary data for the first time login...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export function useEvent(id: string) {
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: Number.POSITIVE_INFINITY,
|
||||
retry: false,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
return { isLoading, isError, error, data };
|
||||
|
||||
@@ -38,7 +38,7 @@ export function useProfile(pubkey: string, embed?: string) {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: Number.POSITIVE_INFINITY,
|
||||
retry: false,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
return { isLoading, isError, profile };
|
||||
|
||||
@@ -58,7 +58,7 @@ export const LumeWindow = {
|
||||
eTags.find((el) => el[3] === "reply")?.[1] ?? eTags[1]?.[1];
|
||||
|
||||
const url = `/columns/events/${root ?? reply ?? event.id}`;
|
||||
const label = `event-${root ?? reply ?? event.id}`;
|
||||
const label = `event-${root?.substring(0, 6) ?? reply?.substring(0, 6) ?? event.id.substring(0, 6)}`;
|
||||
|
||||
LumeWindow.openColumn({ label, url, name: "Thread" });
|
||||
},
|
||||
|
||||
@@ -13,7 +13,6 @@ export enum Kind {
|
||||
export interface Meta {
|
||||
content: string;
|
||||
images: string[];
|
||||
videos: string[];
|
||||
events: string[];
|
||||
mentions: string[];
|
||||
hashtags: string[];
|
||||
|
||||
Reference in New Issue
Block a user