From ec78cf8bf7b947ec29ddcccf73bf1a68859b4d5b Mon Sep 17 00:00:00 2001 From: reya Date: Thu, 8 Feb 2024 21:24:08 +0700 Subject: [PATCH] feat: migrate frontend to new backend --- apps/desktop/src/app.tsx | 281 +---------------- apps/desktop/src/router.tsx | 290 ++++++++++++++++++ apps/desktop/src/routes/auth/create-keys.tsx | 7 +- apps/desktop/src/routes/auth/login-key.tsx | 18 +- apps/desktop/src/routes/auth/onboarding.tsx | 2 +- apps/desktop/src/routes/home/index.tsx | 194 +----------- packages/ark/src/ark.ts | 88 +++++- .../ark/src/components/note/buttons/pin.tsx | 11 - packages/ark/src/components/note/content.tsx | 4 +- .../ark/src/components/note/mentions/user.tsx | 17 +- packages/ark/src/components/note/menu.tsx | 9 - .../components/note/primitives/childReply.tsx | 6 +- .../src/components/note/primitives/repost.tsx | 10 +- .../src/components/note/primitives/text.tsx | 4 +- .../src/components/note/primitives/thread.tsx | 2 +- packages/ark/src/components/note/provider.tsx | 6 +- packages/ark/src/components/note/thread.tsx | 8 - packages/ark/src/components/note/user.tsx | 5 +- packages/ark/src/components/user/about.tsx | 2 +- packages/ark/src/components/user/avatar.tsx | 2 +- packages/ark/src/components/user/name.tsx | 2 +- packages/ark/src/components/user/nip05.tsx | 2 +- packages/ark/src/provider.tsx | 2 +- packages/lume-column-timeline/src/home.tsx | 22 +- packages/lume-column-timeline/src/index.tsx | 33 +- packages/types/index.d.ts | 7 +- packages/ui/src/account/active.tsx | 2 +- packages/ui/src/editor/form.tsx | 25 +- packages/ui/src/search/dialog.tsx | 3 +- src-tauri/src/commands.rs | 1 - src-tauri/src/commands/secret.rs | 25 -- src-tauri/src/main.rs | 10 +- src-tauri/src/nostr/event.rs | 8 +- src-tauri/src/nostr/keys.rs | 20 +- 34 files changed, 478 insertions(+), 650 deletions(-) create mode 100644 apps/desktop/src/router.tsx delete mode 100644 src-tauri/src/commands/secret.rs diff --git a/apps/desktop/src/app.tsx b/apps/desktop/src/app.tsx index bd61b78f..abe5aaa4 100644 --- a/apps/desktop/src/app.tsx +++ b/apps/desktop/src/app.tsx @@ -1,18 +1,10 @@ -import { LoaderIcon } from "@lume/icons"; +import { ArkProvider } from "@lume/ark"; import { StorageProvider } from "@lume/storage"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { invoke } from "@tauri-apps/api/core"; -import { fetch } from "@tauri-apps/plugin-http"; import { I18nextProvider } from "react-i18next"; -import { - RouterProvider, - createBrowserRouter, - defer, - redirect, -} from "react-router-dom"; import { Toaster } from "sonner"; import i18n from "./i18n"; -import { ErrorScreen } from "./routes/error"; +import Router from "./router"; const queryClient = new QueryClient({ defaultOptions: { @@ -22,278 +14,15 @@ const queryClient = new QueryClient({ }, }); -const router = createBrowserRouter([ - { - async lazy() { - const { AppLayout } = await import("@lume/ui"); - return { Component: AppLayout }; - }, - children: [ - { - path: "/", - errorElement: , - async lazy() { - const { HomeLayout } = await import("@lume/ui"); - return { Component: HomeLayout }; - }, - loader: async () => { - const signer = await invoke("verify_signer"); - if (!signer) return redirect("auth"); - return null; - }, - children: [ - { - index: true, - async lazy() { - const { HomeScreen } = await import("./routes/home"); - return { Component: HomeScreen }; - }, - }, - ], - }, - { - path: "settings", - async lazy() { - const { SettingsLayout } = await import("@lume/ui"); - return { Component: SettingsLayout }; - }, - children: [ - { - index: true, - async lazy() { - const { GeneralSettingScreen } = await import( - "./routes/settings/general" - ); - return { Component: GeneralSettingScreen }; - }, - }, - { - path: "profile", - async lazy() { - const { ProfileSettingScreen } = await import( - "./routes/settings/profile" - ); - return { Component: ProfileSettingScreen }; - }, - }, - { - path: "backup", - async lazy() { - const { BackupSettingScreen } = await import( - "./routes/settings/backup" - ); - return { Component: BackupSettingScreen }; - }, - }, - { - path: "advanced", - async lazy() { - const { AdvancedSettingScreen } = await import( - "./routes/settings/advanced" - ); - return { Component: AdvancedSettingScreen }; - }, - }, - { - path: "nwc", - async lazy() { - const { NWCScreen } = await import("./routes/settings/nwc"); - return { Component: NWCScreen }; - }, - }, - { - path: "about", - async lazy() { - const { AboutScreen } = await import("./routes/settings/about"); - return { Component: AboutScreen }; - }, - }, - ], - }, - { - path: "activity", - async lazy() { - const { ActivityScreen } = await import("./routes/activty"); - return { Component: ActivityScreen }; - }, - children: [ - { - path: ":id", - async lazy() { - const { ActivityIdScreen } = await import("./routes/activty/id"); - return { Component: ActivityIdScreen }; - }, - }, - ], - }, - { - path: "relays", - async lazy() { - const { RelaysScreen } = await import("./routes/relays"); - return { Component: RelaysScreen }; - }, - children: [ - { - index: true, - async lazy() { - const { RelayGlobalScreen } = await import( - "./routes/relays/global" - ); - return { Component: RelayGlobalScreen }; - }, - }, - { - path: "follows", - async lazy() { - const { RelayFollowsScreen } = await import( - "./routes/relays/follows" - ); - return { Component: RelayFollowsScreen }; - }, - }, - { - path: ":url", - loader: async ({ request, params }) => { - return defer({ - relay: fetch(`https://${params.url}`, { - method: "GET", - headers: { - Accept: "application/nostr+json", - }, - signal: request.signal, - }).then((res) => res.json()), - }); - }, - async lazy() { - const { RelayUrlScreen } = await import("./routes/relays/url"); - return { Component: RelayUrlScreen }; - }, - }, - ], - }, - { - path: "depot", - children: [ - { - index: true, - async lazy() { - const { DepotScreen } = await import("./routes/depot"); - return { Component: DepotScreen }; - }, - }, - { - path: "onboarding", - async lazy() { - const { DepotOnboardingScreen } = await import( - "./routes/depot/onboarding" - ); - return { Component: DepotOnboardingScreen }; - }, - }, - ], - }, - ], - }, - { - path: "auth", - errorElement: , - async lazy() { - const { AuthLayout } = await import("@lume/ui"); - return { Component: AuthLayout }; - }, - children: [ - { - index: true, - async lazy() { - const { WelcomeScreen } = await import("./routes/auth/welcome"); - return { Component: WelcomeScreen }; - }, - }, - { - path: "create", - async lazy() { - const { CreateAccountScreen } = await import("./routes/auth/create"); - return { Component: CreateAccountScreen }; - }, - }, - { - path: "create-keys", - async lazy() { - const { CreateAccountKeys } = await import( - "./routes/auth/create-keys" - ); - return { Component: CreateAccountKeys }; - }, - }, - { - path: "create-address", - loader: async () => { - // return await ark.getOAuthServices(); - return null; - }, - async lazy() { - const { CreateAccountAddress } = await import( - "./routes/auth/create-address" - ); - return { Component: CreateAccountAddress }; - }, - }, - { - path: "login", - async lazy() { - const { LoginScreen } = await import("./routes/auth/login"); - return { Component: LoginScreen }; - }, - }, - { - path: "login-key", - async lazy() { - const { LoginWithKey } = await import("./routes/auth/login-key"); - return { Component: LoginWithKey }; - }, - }, - { - path: "login-nsecbunker", - async lazy() { - const { LoginWithNsecbunker } = await import( - "./routes/auth/login-nsecbunker" - ); - return { Component: LoginWithNsecbunker }; - }, - }, - { - path: "login-oauth", - async lazy() { - const { LoginWithOAuth } = await import("./routes/auth/login-oauth"); - return { Component: LoginWithOAuth }; - }, - }, - { - path: "onboarding", - async lazy() { - const { OnboardingScreen } = await import("./routes/auth/onboarding"); - return { Component: OnboardingScreen }; - }, - }, - ], - }, -]); - export default function App() { return ( - - - - } - future={{ v7_startTransition: true }} - /> + + + diff --git a/apps/desktop/src/router.tsx b/apps/desktop/src/router.tsx new file mode 100644 index 00000000..bdd9940e --- /dev/null +++ b/apps/desktop/src/router.tsx @@ -0,0 +1,290 @@ +import { useArk } from "@lume/ark"; +import { LoaderIcon } from "@lume/icons"; +import { + RouterProvider, + createBrowserRouter, + defer, + redirect, +} from "react-router-dom"; +import { ErrorScreen } from "./routes/error"; + +export default function Router() { + const ark = useArk(); + + const router = createBrowserRouter([ + { + async lazy() { + const { AppLayout } = await import("@lume/ui"); + return { Component: AppLayout }; + }, + children: [ + { + path: "/", + errorElement: , + async lazy() { + const { HomeLayout } = await import("@lume/ui"); + return { Component: HomeLayout }; + }, + loader: async () => { + const signer = await ark.verify_signer(); + if (!signer) return redirect("auth"); + return null; + }, + children: [ + { + index: true, + async lazy() { + const { HomeScreen } = await import("./routes/home"); + return { Component: HomeScreen }; + }, + }, + ], + }, + { + path: "settings", + async lazy() { + const { SettingsLayout } = await import("@lume/ui"); + return { Component: SettingsLayout }; + }, + children: [ + { + index: true, + async lazy() { + const { GeneralSettingScreen } = await import( + "./routes/settings/general" + ); + return { Component: GeneralSettingScreen }; + }, + }, + { + path: "profile", + async lazy() { + const { ProfileSettingScreen } = await import( + "./routes/settings/profile" + ); + return { Component: ProfileSettingScreen }; + }, + }, + { + path: "backup", + async lazy() { + const { BackupSettingScreen } = await import( + "./routes/settings/backup" + ); + return { Component: BackupSettingScreen }; + }, + }, + { + path: "advanced", + async lazy() { + const { AdvancedSettingScreen } = await import( + "./routes/settings/advanced" + ); + return { Component: AdvancedSettingScreen }; + }, + }, + { + path: "nwc", + async lazy() { + const { NWCScreen } = await import("./routes/settings/nwc"); + return { Component: NWCScreen }; + }, + }, + { + path: "about", + async lazy() { + const { AboutScreen } = await import("./routes/settings/about"); + return { Component: AboutScreen }; + }, + }, + ], + }, + { + path: "activity", + async lazy() { + const { ActivityScreen } = await import("./routes/activty"); + return { Component: ActivityScreen }; + }, + children: [ + { + path: ":id", + async lazy() { + const { ActivityIdScreen } = await import( + "./routes/activty/id" + ); + return { Component: ActivityIdScreen }; + }, + }, + ], + }, + { + path: "relays", + async lazy() { + const { RelaysScreen } = await import("./routes/relays"); + return { Component: RelaysScreen }; + }, + children: [ + { + index: true, + async lazy() { + const { RelayGlobalScreen } = await import( + "./routes/relays/global" + ); + return { Component: RelayGlobalScreen }; + }, + }, + { + path: "follows", + async lazy() { + const { RelayFollowsScreen } = await import( + "./routes/relays/follows" + ); + return { Component: RelayFollowsScreen }; + }, + }, + { + path: ":url", + loader: async ({ request, params }) => { + return defer({ + relay: fetch(`https://${params.url}`, { + method: "GET", + headers: { + Accept: "application/nostr+json", + }, + signal: request.signal, + }).then((res) => res.json()), + }); + }, + async lazy() { + const { RelayUrlScreen } = await import("./routes/relays/url"); + return { Component: RelayUrlScreen }; + }, + }, + ], + }, + { + path: "depot", + children: [ + { + index: true, + async lazy() { + const { DepotScreen } = await import("./routes/depot"); + return { Component: DepotScreen }; + }, + }, + { + path: "onboarding", + async lazy() { + const { DepotOnboardingScreen } = await import( + "./routes/depot/onboarding" + ); + return { Component: DepotOnboardingScreen }; + }, + }, + ], + }, + ], + }, + { + path: "auth", + errorElement: , + async lazy() { + const { AuthLayout } = await import("@lume/ui"); + return { Component: AuthLayout }; + }, + children: [ + { + index: true, + async lazy() { + const { WelcomeScreen } = await import("./routes/auth/welcome"); + return { Component: WelcomeScreen }; + }, + }, + { + path: "create", + async lazy() { + const { CreateAccountScreen } = await import( + "./routes/auth/create" + ); + return { Component: CreateAccountScreen }; + }, + }, + { + path: "create-keys", + async lazy() { + const { CreateAccountKeys } = await import( + "./routes/auth/create-keys" + ); + return { Component: CreateAccountKeys }; + }, + }, + { + path: "create-address", + loader: async () => { + // return await ark.getOAuthServices(); + return null; + }, + async lazy() { + const { CreateAccountAddress } = await import( + "./routes/auth/create-address" + ); + return { Component: CreateAccountAddress }; + }, + }, + { + path: "login", + async lazy() { + const { LoginScreen } = await import("./routes/auth/login"); + return { Component: LoginScreen }; + }, + }, + { + path: "login-key", + async lazy() { + const { LoginWithKey } = await import("./routes/auth/login-key"); + return { Component: LoginWithKey }; + }, + }, + { + path: "login-nsecbunker", + async lazy() { + const { LoginWithNsecbunker } = await import( + "./routes/auth/login-nsecbunker" + ); + return { Component: LoginWithNsecbunker }; + }, + }, + { + path: "login-oauth", + async lazy() { + const { LoginWithOAuth } = await import( + "./routes/auth/login-oauth" + ); + return { Component: LoginWithOAuth }; + }, + }, + { + path: "onboarding", + async lazy() { + const { OnboardingScreen } = await import( + "./routes/auth/onboarding" + ); + return { Component: OnboardingScreen }; + }, + }, + ], + }, + ]); + + return ( + + + + } + future={{ v7_startTransition: true }} + /> + ); +} diff --git a/apps/desktop/src/routes/auth/create-keys.tsx b/apps/desktop/src/routes/auth/create-keys.tsx index 487681d5..84cbdd6c 100644 --- a/apps/desktop/src/routes/auth/create-keys.tsx +++ b/apps/desktop/src/routes/auth/create-keys.tsx @@ -28,7 +28,12 @@ export function CreateAccountKeys() { setLoading(true); // trigger save key - await invoke("save_key", { nsec: key }); + const save = await invoke("save_key", { nsec: key }); + + if (!save) { + setLoading(false); + toast.error("Save account keys failed, please try again later."); + } // update state setLoading(false); diff --git a/apps/desktop/src/routes/auth/login-key.tsx b/apps/desktop/src/routes/auth/login-key.tsx index 7de52444..2f72380e 100644 --- a/apps/desktop/src/routes/auth/login-key.tsx +++ b/apps/desktop/src/routes/auth/login-key.tsx @@ -1,7 +1,6 @@ -import { useArk } from "@lume/ark"; import { EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons"; import { useStorage } from "@lume/storage"; -import { getPublicKey, nip19 } from "nostr-tools"; +import { invoke } from "@tauri-apps/api/core"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { Trans, useTranslation } from "react-i18next"; @@ -9,7 +8,6 @@ import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; export function LoginWithKey() { - const ark = useArk(); const storage = useStorage(); const navigate = useNavigate(); @@ -31,15 +29,15 @@ export function LoginWithKey() { setLoading(true); - const privkey = nip19.decode(data.nsec).data as string; - const pubkey = getPublicKey(privkey); + // trigger save key + const save = await invoke("save_key", { nsec: data.nsec }); - const account = await storage.createAccount({ - pubkey: pubkey, - privkey: privkey, - }); - ark.account = account; + if (!save) { + setLoading(false); + toast.error("Save account keys failed, please try again later."); + } + // redirect to next step return navigate("/auth/onboarding", { replace: true }); } catch (e) { setLoading(false); diff --git a/apps/desktop/src/routes/auth/onboarding.tsx b/apps/desktop/src/routes/auth/onboarding.tsx index d6e8ca00..31765b2e 100644 --- a/apps/desktop/src/routes/auth/onboarding.tsx +++ b/apps/desktop/src/routes/auth/onboarding.tsx @@ -69,7 +69,7 @@ export function OnboardingScreen() { setSettings((prev) => ({ ...prev, notification: permissionGranted })); // get other settings - const data = await storage.getAllSettings(); + const data = await storage.settings(); for (const item of data) { if (item.key === "lowPower") setSettings((prev) => ({ diff --git a/apps/desktop/src/routes/home/index.tsx b/apps/desktop/src/routes/home/index.tsx index 17725662..1e7f6edd 100644 --- a/apps/desktop/src/routes/home/index.tsx +++ b/apps/desktop/src/routes/home/index.tsx @@ -1,201 +1,9 @@ -import { Antenas } from "@columns/antenas"; -import { Default } from "@columns/default"; -import { ForYou } from "@columns/foryou"; -import { Global } from "@columns/global"; -import { Group } from "@columns/group"; -import { Hashtag } from "@columns/hashtag"; -import { Thread } from "@columns/thread"; import { Timeline } from "@columns/timeline"; -import { TrendingNotes } from "@columns/trending-notes"; -import { User } from "@columns/user"; -import { Waifu } from "@columns/waifu"; -import { useColumnContext } from "@lume/ark"; -import { - ArrowLeftIcon, - ArrowRightIcon, - PlusIcon, - PlusSquareIcon, -} from "@lume/icons"; -import { IColumn } from "@lume/types"; -import { TutorialModal } from "@lume/ui/src/tutorial/modal"; -import { COL_TYPES } from "@lume/utils"; -import * as Tooltip from "@radix-ui/react-tooltip"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { VList } from "virtua"; export function HomeScreen() { - const { t } = useTranslation(); - const { columns, vlistRef, addColumn } = useColumnContext(); - - const [selectedIndex, setSelectedIndex] = useState(-1); - - const renderItem = (column: IColumn) => { - switch (column.kind) { - case COL_TYPES.default: - return ; - case COL_TYPES.newsfeed: - return ; - case COL_TYPES.foryou: - return ; - case COL_TYPES.thread: - return ; - case COL_TYPES.user: - return ; - case COL_TYPES.hashtag: - return ; - case COL_TYPES.group: - return ; - case COL_TYPES.antenas: - return ; - case COL_TYPES.global: - return ; - case COL_TYPES.trendingNotes: - return ; - case COL_TYPES.waifu: - return ; - default: - return ; - } - }; - return (
- { - if (!vlistRef.current) return; - switch (e.code) { - case "ArrowUp": - case "ArrowLeft": { - e.preventDefault(); - const prevIndex = Math.max(selectedIndex - 1, 0); - setSelectedIndex(prevIndex); - vlistRef.current.scrollToIndex(prevIndex, { - align: "center", - smooth: true, - }); - break; - } - case "ArrowDown": - case "ArrowRight": { - e.preventDefault(); - const nextIndex = Math.min(selectedIndex + 1, columns.length - 1); - setSelectedIndex(nextIndex); - vlistRef.current.scrollToIndex(nextIndex, { - align: "center", - smooth: true, - }); - break; - } - default: - break; - } - }} - > - {columns.map((column) => renderItem(column))} -
- -
-
- -
-
- - - - - - - {t("global.moveLeft")} - - - - - - - - - - - {t("global.moveRight")} - - - - - - - - - - - {t("global.newColumn")} - - - - -
- -
-
- +
); } diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index 0837aca1..9154f9ba 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -5,7 +5,18 @@ export class Ark { public account: CurrentAccount; constructor() { - this.account = null; + this.account = { pubkey: "" }; + } + + public async verify_signer() { + try { + const cmd: string = await invoke("verify_signer"); + if (!cmd) return false; + this.account.pubkey = cmd; + return true; + } catch (e) { + console.error(String(e)); + } } public async event_to_bech32(id: string, relays: string[]) { @@ -15,8 +26,8 @@ export class Ark { relays, }); return cmd; - } catch { - console.error("get nevent id failed"); + } catch (e) { + console.error(String(e)); } } @@ -26,7 +37,70 @@ export class Ark { const event = JSON.parse(cmd) as Event; return event; } catch (e) { - console.error("failed to get event", id); + console.error(String(e)); + } + } + + public async get_text_events(limit: number, until?: number) { + try { + const cmd: Event[] = await invoke("get_text_events", { limit, until }); + return cmd; + } catch (e) { + console.error(String(e)); + } + } + + public async publish(content: string) { + try { + const cmd: string = await invoke("publish", { content }); + return cmd; + } catch (e) { + console.error(String(e)); + } + } + + public async reply_to(content: string, tags: string[]) { + try { + const cmd: string = await invoke("reply_to", { content, tags }); + return cmd; + } catch (e) { + console.error(String(e)); + } + } + + public async repost(id: string, pubkey: string) { + try { + const cmd: string = await invoke("repost", { id, pubkey }); + return cmd; + } catch (e) { + console.error(String(e)); + } + } + + public async upvote(id: string, pubkey: string) { + try { + const cmd: string = await invoke("upvote", { id, pubkey }); + return cmd; + } catch (e) { + console.error(String(e)); + } + } + + public async downvote(id: string, pubkey: string) { + try { + const cmd: string = await invoke("downvote", { id, pubkey }); + return cmd; + } catch (e) { + console.error(String(e)); + } + } + + public async get_event_thread(id: string) { + try { + const cmd: Event[] = await invoke("get_event_thread", { id }); + return cmd; + } catch (e) { + console.error(String(e)); } } @@ -72,7 +146,7 @@ export class Ark { const cmd: Metadata = await invoke("get_profile", { id }); return cmd; } catch (e) { - console.error("failed to get profile", id); + console.error(String(e)); } } @@ -83,8 +157,8 @@ export class Ark { relays, }); return cmd; - } catch { - console.error("get nprofile id failed"); + } catch (e) { + console.error(String(e)); } } } diff --git a/packages/ark/src/components/note/buttons/pin.tsx b/packages/ark/src/components/note/buttons/pin.tsx index 6125f4e3..cc1bc9a7 100644 --- a/packages/ark/src/components/note/buttons/pin.tsx +++ b/packages/ark/src/components/note/buttons/pin.tsx @@ -1,15 +1,11 @@ import { PinIcon } from "@lume/icons"; -import { COL_TYPES } from "@lume/utils"; import * as Tooltip from "@radix-ui/react-tooltip"; import { useTranslation } from "react-i18next"; -import { useColumnContext } from "../../column/provider"; import { useNoteContext } from "../provider"; export function NotePin() { const event = useNoteContext(); - const { t } = useTranslation(); - const { addColumn } = useColumnContext(); return ( @@ -17,13 +13,6 @@ export function NotePin() {
diff --git a/packages/ark/src/components/note/provider.tsx b/packages/ark/src/components/note/provider.tsx index c2cf4d45..c5c5b201 100644 --- a/packages/ark/src/components/note/provider.tsx +++ b/packages/ark/src/components/note/provider.tsx @@ -1,12 +1,12 @@ -import { NDKEvent } from "@nostr-dev-kit/ndk"; +import { Event } from "@lume/types"; import { ReactNode, createContext, useContext } from "react"; -const EventContext = createContext(null); +const EventContext = createContext(null); export function NoteProvider({ event, children, -}: { event: NDKEvent; children: ReactNode }) { +}: { event: Event; children: ReactNode }) { return ( {children} ); diff --git a/packages/ark/src/components/note/thread.tsx b/packages/ark/src/components/note/thread.tsx index 61de4b3c..45679409 100644 --- a/packages/ark/src/components/note/thread.tsx +++ b/packages/ark/src/components/note/thread.tsx @@ -20,7 +20,6 @@ export function NoteThread({ }); const { t } = useTranslation(); - const { addColumn } = useColumnContext(); if (!thread) return null; @@ -42,13 +41,6 @@ export function NoteThread({