diff --git a/apps/desktop2/src/app.css b/apps/desktop2/src/app.css index 0d1730e6..a9df0c78 100644 --- a/apps/desktop2/src/app.css +++ b/apps/desktop2/src/app.css @@ -32,7 +32,7 @@ } .shadow-primary { - filter: drop-shadow(0px 0px 4px rgba(66, 65, 73, 0.14)); + box-shadow: 0px 0px 4px rgba(66, 65, 73, 0.14); } } diff --git a/apps/desktop2/src/routes/$account.home.tsx b/apps/desktop2/src/routes/$account.home.tsx index 97042ef5..a5038588 100644 --- a/apps/desktop2/src/routes/$account.home.tsx +++ b/apps/desktop2/src/routes/$account.home.tsx @@ -54,43 +54,17 @@ function Screen() { }; const add = (column: LumeColumn) => { - const existed = columns.find((item) => item.label === column.label); - if (!existed) { - const lastColIndex = columns.findIndex((item) => item.label === "open"); - const newColumns = [ - ...columns.slice(0, lastColIndex), - column, - ...columns.slice(lastColIndex), - ]; - - // update state - setColumns(newColumns); - setSelectedIndex(newColumns.length - 1); - - // save state - ark.set_columns(newColumns); - } - - // scroll to new column - vlistRef.current.scrollToIndex(columns.length - 1, { - align: "center", - }); + setColumns((state) => [...state, column]); }; const remove = (label: string) => { - const newColumns = columns.filter((t) => t.label !== label); - - // update state - setColumns(newColumns); - setSelectedIndex(newColumns.length - 1); - vlistRef.current.scrollToIndex(newColumns.length - 1, { - align: "center", - }); - - // save state - ark.set_columns(newColumns); + setColumns((state) => state.filter((t) => t.label !== label)); }; + useEffect(() => { + ark.set_columns(columns); + }, [columns]); + useEffect(() => { let unlisten: UnlistenFn = undefined; diff --git a/apps/desktop2/src/routes/antenas.lazy.tsx b/apps/desktop2/src/routes/antenas.tsx similarity index 67% rename from apps/desktop2/src/routes/antenas.lazy.tsx rename to apps/desktop2/src/routes/antenas.tsx index f5d9eff7..365b6cfd 100644 --- a/apps/desktop2/src/routes/antenas.lazy.tsx +++ b/apps/desktop2/src/routes/antenas.tsx @@ -1,30 +1,44 @@ import { RepostNote } from "@/components/repost"; import { Suggest } from "@/components/suggest"; import { TextNote } from "@/components/text"; -import { useEvents } from "@lume/ark"; import { LoaderIcon, ArrowRightCircleIcon, InfoIcon } from "@lume/icons"; -import { Event, Kind } from "@lume/types"; +import { ColumnRouteSearch, Event, Kind } from "@lume/types"; import { Column } from "@lume/ui"; -import { createLazyFileRoute } from "@tanstack/react-router"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { createFileRoute } from "@tanstack/react-router"; import { useTranslation } from "react-i18next"; import { Virtualizer } from "virtua"; -export const Route = createLazyFileRoute("/antenas")({ +export const Route = createFileRoute("/antenas")({ + validateSearch: (search: Record): ColumnRouteSearch => { + return { + account: search.account, + label: search.label, + name: search.name, + }; + }, component: Screen, }); export function Screen() { - // @ts-ignore, just work!!! - const { id, name, account } = Route.useSearch(); + const { label, name, account } = Route.useSearch(); + const { ark } = Route.useRouteContext(); const { t } = useTranslation(); - const { - data, - hasNextPage, - isLoading, - isRefetching, - isFetchingNextPage, - fetchNextPage, - } = useEvents("local", account); + const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = + useInfiniteQuery({ + queryKey: [name, account], + initialPageParam: 0, + queryFn: async ({ pageParam }: { pageParam: number }) => { + const events = await ark.get_events(20, pageParam); + return events; + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage?.at(-1); + return lastEvent ? lastEvent.created_at - 1 : null; + }, + select: (data) => data?.pages.flatMap((page) => page), + refetchOnWindowFocus: false, + }); const renderItem = (event: Event) => { if (!event) return; @@ -38,9 +52,9 @@ export function Screen() { return ( - + - {isLoading || isRefetching ? ( + {isLoading ? (
diff --git a/apps/desktop2/src/routes/create-group.tsx b/apps/desktop2/src/routes/create-group.tsx new file mode 100644 index 00000000..c3fe8048 --- /dev/null +++ b/apps/desktop2/src/routes/create-group.tsx @@ -0,0 +1,116 @@ +import { CheckCircleIcon } from "@lume/icons"; +import { ColumnRouteSearch } from "@lume/types"; +import { Column, User } from "@lume/ui"; +import { createFileRoute } from "@tanstack/react-router"; +import { useState } from "react"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/create-group")({ + validateSearch: (search: Record): ColumnRouteSearch => { + return { + account: search.account, + label: search.label, + name: search.name, + }; + }, + loader: async ({ context }) => { + const ark = context.ark; + const contacts = await ark.get_contact_list(); + return contacts; + }, + component: Screen, +}); + +function Screen() { + const contacts = Route.useLoaderData(); + + const { ark } = Route.useRouteContext(); + const { label, name } = Route.useSearch(); + + const [title, setTitle] = useState("Just a new group"); + const [users, setUsers] = useState>([]); + const [isDone, setIsDone] = useState(false); + + const toggleUser = (pubkey: string) => { + const arr = users.includes(pubkey) + ? users.filter((i) => i !== pubkey) + : [...users, pubkey]; + setUsers(arr); + }; + + const submit = async () => { + try { + if (isDone) return history.back(); + + const groups = await ark.set_nstore( + `lume_group_${label}`, + JSON.stringify(users), + ); + + if (groups) setIsDone(true); + } catch (e) { + toast.error(e); + } + }; + + return ( + + + +
+
+ + setTitle(e.target.value)} + placeholder="Nostrichs..." + className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" + /> +
+
+
+ Pick user + {`${users.length} / ∞`} +
+
+ {contacts.map((item: string) => ( + + ))} +
+
+
+
+ +
+
+
+ ); +} diff --git a/apps/desktop2/src/routes/group.create.tsx b/apps/desktop2/src/routes/group.create.tsx deleted file mode 100644 index ca36dbd2..00000000 --- a/apps/desktop2/src/routes/group.create.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/group/create')({ - component: () =>
Hello /group/create!
-}) \ No newline at end of file diff --git a/apps/desktop2/src/routes/group.tsx b/apps/desktop2/src/routes/group.tsx index 1bbf4dd8..df59948e 100644 --- a/apps/desktop2/src/routes/group.tsx +++ b/apps/desktop2/src/routes/group.tsx @@ -10,7 +10,6 @@ import { useTranslation } from "react-i18next"; import { Virtualizer } from "virtua"; export const Route = createFileRoute("/group")({ - component: Screen, validateSearch: (search: Record): ColumnRouteSearch => { return { account: search.account, @@ -18,14 +17,23 @@ export const Route = createFileRoute("/group")({ name: search.name, }; }, - beforeLoad: async ({ context }) => { + beforeLoad: async ({ search, context }) => { const ark = context.ark; - if (!ark) { + const groups = await ark.get_nstore(`lume_group_${search.label}`); + + if (!groups) { throw redirect({ - to: "/group/create", + to: "/create-group", + replace: false, + search, }); } + + return { + groups, + }; }, + component: Screen, }); export function Screen() { diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index 592f517f..58db323c 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -19,7 +19,6 @@ enum NSTORE_KEYS { settings = "lume_user_settings", interests = "lume_user_interests", columns = "lume_user_columns", - group = "lume_group_", } export class Ark { @@ -654,11 +653,36 @@ export class Ark { content: JSON.stringify(interests), }); return cmd; + } catch (e) { + throw new Error(String(e)); + } + } + + public async get_nstore(key: string) { + try { + const cmd: string = await invoke("get_nstore", { + key, + }); + const parse: string | string[] = cmd ? JSON.parse(cmd) : null; + if (!parse.length) return null; + return parse; } catch { return null; } } + public async set_nstore(key: string, content: string) { + try { + const cmd: string = await invoke("set_nstore", { + key, + content, + }); + return cmd; + } catch (e) { + throw new Error(String(e)); + } + } + public open_thread(id: string) { try { const window = new WebviewWindow(`event-${id}`, { diff --git a/packages/icons/src/expand.tsx b/packages/icons/src/expand.tsx index 84ae261b..ef7d8e60 100644 --- a/packages/icons/src/expand.tsx +++ b/packages/icons/src/expand.tsx @@ -1,22 +1,17 @@ -import { SVGProps } from 'react'; +import { SVGProps } from "react"; -export function ExpandIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function ExpandIcon( + props: JSX.IntrinsicAttributes & SVGProps, +) { return ( - + + d="M5.75 12.75v3.5a2 2 0 0 0 2 2h3.5m1.5-12.5h3.5a2 2 0 0 1 2 2v3.5" + /> ); } diff --git a/packages/ui/src/column/header.tsx b/packages/ui/src/column/header.tsx index 3977e51e..f062771e 100644 --- a/packages/ui/src/column/header.tsx +++ b/packages/ui/src/column/header.tsx @@ -1,4 +1,4 @@ -import { CancelIcon, RefreshIcon } from "@lume/icons"; +import { CancelIcon, ExpandIcon, RefreshIcon } from "@lume/icons"; import { cn } from "@lume/utils"; import { getCurrent } from "@tauri-apps/api/window"; import { ReactNode } from "react"; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 22a0a918..45f51aec 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -56,15 +56,11 @@ fn main() { client .add_relay("wss://relay.nostr.band") .await - .unwrap_or_default(); - client - .add_relay("wss://relay.damus.io") - .await - .unwrap_or_default(); + .expect("Cannot connect to relay.nostr.band, please try again later."); client .add_relay("wss://purplepag.es") .await - .unwrap_or_default(); + .expect("Cannot connect to purplepag.es, please try again later."); // Connect client.connect().await;