feat: group metadata query
This commit is contained in:
26
src/app.tsx
26
src/app.tsx
@@ -1,8 +1,12 @@
|
||||
import { broadcastQueryClient } from "@tanstack/query-broadcast-client-experimental";
|
||||
import { 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 { Store } from "@tauri-apps/plugin-store";
|
||||
import { StrictMode } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { newQueryStorage } from "./commons";
|
||||
import type { LumeEvent } from "./system";
|
||||
|
||||
import { routeTree } from "./routes.gen"; // auto generated file
|
||||
@@ -18,7 +22,27 @@ declare module "@tanstack/react-router" {
|
||||
}
|
||||
|
||||
const platform = type();
|
||||
const queryClient = new QueryClient();
|
||||
// @ts-ignore, won't fix
|
||||
const store = await Store.load(".data", { autoSave: 300 });
|
||||
const storage = newQueryStorage(store);
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 20, // 20 seconds
|
||||
persister: experimental_createPersister({
|
||||
storage: storage,
|
||||
maxAge: 1000 * 60 * 60 * 6, // 6 hours
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Make sure all webviews use same query client
|
||||
broadcastQueryClient({
|
||||
queryClient,
|
||||
broadcastChannel: "lume",
|
||||
});
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: { queryClient, platform },
|
||||
|
||||
@@ -128,9 +128,9 @@ async setSigner(id: string) : Promise<Result<null, string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getProfile(id: string, cacheOnly: boolean) : Promise<Result<string, string>> {
|
||||
async getProfile(id: string) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_profile", { id, cacheOnly }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_profile", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
MaybePromise,
|
||||
PersistedQuery,
|
||||
} from "@tanstack/query-persist-client-core";
|
||||
import { Store } from "@tanstack/store";
|
||||
import { Store } from "@tanstack/react-store";
|
||||
import { ask, message, open } from "@tauri-apps/plugin-dialog";
|
||||
import { readFile } from "@tauri-apps/plugin-fs";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
|
||||
@@ -45,7 +45,7 @@ export function NoteRepost({
|
||||
const list: Promise<MenuItem>[] = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const res = await commands.getProfile(account, true);
|
||||
const res = await commands.getProfile(account);
|
||||
let name = "unknown";
|
||||
|
||||
if (res.status === "ok") {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { memo } from "react";
|
||||
export const MentionUser = memo(function MentionUser({
|
||||
pubkey,
|
||||
}: { pubkey: string }) {
|
||||
const { isLoading, isError, profile } = useProfile(pubkey);
|
||||
const { isLoading, profile } = useProfile(pubkey);
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -14,10 +14,8 @@ export const MentionUser = memo(function MentionUser({
|
||||
className="break-words text-start text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{isLoading
|
||||
? "@anon"
|
||||
: isError
|
||||
? displayNpub(pubkey, 16)
|
||||
: `@${profile?.name || profile?.display_name || displayNpub(pubkey, 16)}`}
|
||||
? displayNpub(pubkey, 16)
|
||||
: `@${profile?.name || profile?.display_name || displayNpub(pubkey, 16)}`}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,26 +2,27 @@ import { useProfile } from "@/system";
|
||||
import type { Metadata } from "@/types";
|
||||
import { type ReactNode, createContext, useContext } from "react";
|
||||
|
||||
const UserContext = createContext<{
|
||||
interface UserContext {
|
||||
pubkey: string;
|
||||
profile: Metadata;
|
||||
isError: boolean;
|
||||
profile: Metadata | undefined;
|
||||
isLoading: boolean;
|
||||
}>(null);
|
||||
}
|
||||
|
||||
const UserContext = createContext<UserContext | null>(null);
|
||||
|
||||
export function UserProvider({
|
||||
pubkey,
|
||||
children,
|
||||
embedProfile,
|
||||
data,
|
||||
}: {
|
||||
pubkey: string;
|
||||
children: ReactNode;
|
||||
embedProfile?: string;
|
||||
data?: string;
|
||||
}) {
|
||||
const { isLoading, isError, profile } = useProfile(pubkey, embedProfile);
|
||||
const { isLoading, profile } = useProfile(pubkey, data);
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ pubkey, profile, isError, isLoading }}>
|
||||
<UserContext.Provider value={{ pubkey, isLoading, profile }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { RichEvent } from "@/commands.gen";
|
||||
import { Spinner } from "@/components";
|
||||
import type { Metadata, NostrEvent } from "@/types";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import type { OsType } from "@tauri-apps/plugin-os";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface RouterContext {
|
||||
@@ -22,16 +21,16 @@ function Screen() {
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen<RichEvent>("event", async (data) => {
|
||||
const event = JSON.parse(data.payload.raw);
|
||||
const unlisten = getCurrentWindow().listen<string>(
|
||||
"metadata",
|
||||
async (data) => {
|
||||
const payload = data.payload;
|
||||
const event: NostrEvent = JSON.parse(payload);
|
||||
const metadata: Metadata = JSON.parse(event.content);
|
||||
|
||||
if (event.kind === 0) {
|
||||
const npub = nip19.npubEncode(event.pubkey);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["profile", npub, event.pubkey],
|
||||
});
|
||||
}
|
||||
});
|
||||
queryClient.setQueryData(["profile", event.pubkey], () => metadata);
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
|
||||
@@ -199,7 +199,7 @@ function OpenLaunchpad() {
|
||||
const list: Promise<MenuItem>[] = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const res = await commands.getProfile(account, true);
|
||||
const res = await commands.getProfile(account);
|
||||
let name = "unknown";
|
||||
|
||||
if (res.status === "ok") {
|
||||
|
||||
@@ -104,7 +104,7 @@ function Screen() {
|
||||
const list: Promise<MenuItem>[] = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const res = await commands.getProfile(account, true);
|
||||
const res = await commands.getProfile(account);
|
||||
let name = "unknown";
|
||||
|
||||
if (res.status === "ok") {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/settings/$id/profile")({
|
||||
beforeLoad: async ({ params }) => {
|
||||
const res = await commands.getProfile(params.id, true);
|
||||
const res = await commands.getProfile(params.id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const profile: Profile = JSON.parse(res.data);
|
||||
|
||||
@@ -31,7 +31,7 @@ function Screen() {
|
||||
const list: Promise<MenuItem>[] = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const res = await commands.getProfile(account, true);
|
||||
const res = await commands.getProfile(account);
|
||||
let name = "unknown";
|
||||
|
||||
if (res.status === "ok") {
|
||||
|
||||
@@ -1,43 +1,54 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { Metadata } from "@/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function useProfile(pubkey: string, embed?: string) {
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
data: profile,
|
||||
} = useQuery({
|
||||
queryKey: ["metadata", "profile", pubkey],
|
||||
export function useProfile(pubkey: string, data?: string) {
|
||||
const hex = useMemo(() => {
|
||||
try {
|
||||
const normalized = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
const decoded = nip19.decode(normalized);
|
||||
|
||||
switch (decoded.type) {
|
||||
case "npub":
|
||||
return decoded.data;
|
||||
case "nprofile":
|
||||
return decoded.data.pubkey;
|
||||
case "naddr":
|
||||
return decoded.data.pubkey;
|
||||
default:
|
||||
return pubkey;
|
||||
}
|
||||
} catch {
|
||||
return pubkey;
|
||||
}
|
||||
}, [pubkey]);
|
||||
|
||||
const { isLoading, data: profile } = useQuery({
|
||||
queryKey: ["profile", hex],
|
||||
queryFn: async () => {
|
||||
if (embed) {
|
||||
const metadata: Metadata = JSON.parse(embed);
|
||||
if (data) {
|
||||
const metadata: Metadata = JSON.parse(data);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
let normalizedId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (normalizedId.startsWith("nprofile")) {
|
||||
const decoded = nip19.decode(normalizedId);
|
||||
|
||||
if (decoded.type === "nprofile") {
|
||||
normalizedId = decoded.data.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
const query = await commands.getProfile(normalizedId, false);
|
||||
const query = await commands.getProfile(hex);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return JSON.parse(query.data) as Metadata;
|
||||
const metadata: Metadata = JSON.parse(query.data);
|
||||
return metadata;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
await getCurrentWindow().emit("request_metadata", { id: hex });
|
||||
return {};
|
||||
}
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: !!hex,
|
||||
});
|
||||
|
||||
return { isLoading, isError, profile };
|
||||
return { isLoading, profile };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user