From 8eb01c8bbf9c6759d5ef822869f622e262fd5d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E5=AE=AE=E8=93=AE?= <123083837+reyamir@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:51:50 +0700 Subject: [PATCH] Improve column management (#221) * wip: redesign store * feat: update trending column * feat: add more functions --- apps/desktop2/src/components/column.tsx | 78 ++++++++++-- apps/desktop2/src/routes/$account.home.tsx | 23 +++- apps/desktop2/src/routes/$account.tsx | 4 +- apps/desktop2/src/routes/global.tsx | 4 +- apps/desktop2/src/routes/group.tsx | 4 +- apps/desktop2/src/routes/newsfeed.tsx | 2 +- apps/desktop2/src/routes/store.community.tsx | 21 ---- apps/desktop2/src/routes/store.official.tsx | 69 ---------- apps/desktop2/src/routes/store.tsx | 126 +++++++++++++------ apps/desktop2/src/routes/topic.tsx | 4 +- apps/desktop2/src/routes/trending.notes.tsx | 34 +++-- apps/desktop2/src/routes/trending.tsx | 14 +-- packages/system/src/commands.ts | 8 ++ packages/types/index.d.ts | 3 +- src-tauri/resources/official_columns.json | 85 ++++++------- src-tauri/src/commands/window.rs | 15 +++ src-tauri/src/main.rs | 1 + 17 files changed, 281 insertions(+), 214 deletions(-) delete mode 100644 apps/desktop2/src/routes/store.community.tsx delete mode 100644 apps/desktop2/src/routes/store.official.tsx diff --git a/apps/desktop2/src/components/column.tsx b/apps/desktop2/src/components/column.tsx index 17c63de0..3af62a1d 100644 --- a/apps/desktop2/src/components/column.tsx +++ b/apps/desktop2/src/components/column.tsx @@ -1,8 +1,8 @@ -import { CancelIcon, CheckIcon } from "@lume/icons"; +import { CheckIcon, HorizontalDotsIcon } from "@lume/icons"; import type { LumeColumn } from "@lume/types"; -import { cn } from "@lume/utils"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; +import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { getCurrent } from "@tauri-apps/api/webviewWindow"; import { memo, useCallback, useEffect, useRef, useState } from "react"; @@ -86,14 +86,22 @@ export const Column = memo(function Column({ return (
-
+
); }); -function Header({ label, name }: { label: string; name: string }) { +function Header({ + label, + webview, + name, +}: { label: string; webview: string; name: string }) { const [title, setTitle] = useState(name); const [isChanged, setIsChanged] = useState(false); @@ -109,10 +117,56 @@ function Header({ label, name }: { label: string; name: string }) { setIsChanged(false); }; - const close = async () => { - const mainWindow = getCurrent(); - await mainWindow.emit("columns", { type: "remove", label }); - }; + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "Reload", + action: async () => { + await invoke("reload_column", { label: webview }); + }, + }), + MenuItem.new({ + text: "Open in new window", + action: () => console.log("not implemented."), + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "Move left", + action: async () => { + await getCurrent().emit("columns", { + type: "move", + label, + direction: "left", + }); + }, + }), + MenuItem.new({ + text: "Move right", + action: async () => { + await getCurrent().emit("columns", { + type: "move", + label, + direction: "right", + }); + }, + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "Close", + action: async () => { + await getCurrent().emit("columns", { type: "remove", label }); + }, + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, []); useEffect(() => { if (title.length !== name.length) setIsChanged(true); @@ -121,7 +175,7 @@ function Header({ label, name }: { label: string; name: string }) { return (
-
+
); diff --git a/apps/desktop2/src/routes/$account.home.tsx b/apps/desktop2/src/routes/$account.home.tsx index 0cb8a17b..e5dd516c 100644 --- a/apps/desktop2/src/routes/$account.home.tsx +++ b/apps/desktop2/src/routes/$account.home.tsx @@ -50,8 +50,8 @@ function Screen() { type: "add", column: { label: "store", - name: "Store", - content: "/store/official", + name: "Column Gallery", + content: "/store", }, }); }, []); @@ -65,6 +65,23 @@ function Screen() { setColumns((prev) => prev.filter((t) => t.label !== label)); }, 150); + const move = useDebouncedCallback( + (label: string, direction: "left" | "right") => { + const newCols = [...columns]; + + const col = newCols.find((el) => el.label === label); + const colIndex = newCols.findIndex((el) => el.label === label); + + newCols.splice(colIndex, 1); + + if (direction === "left") newCols.splice(colIndex - 1, 0, col); + if (direction === "right") newCols.splice(colIndex + 1, 0, col); + + setColumns(newCols); + }, + 150, + ); + const updateName = useDebouncedCallback((label: string, title: string) => { const currentColIndex = columns.findIndex((col) => col.label === label); @@ -135,6 +152,8 @@ function Screen() { if (data.payload.type === "reset") reset(); if (data.payload.type === "add") add(data.payload.column); if (data.payload.type === "remove") remove(data.payload.label); + if (data.payload.type === "move") + move(data.payload.label, data.payload.direction); if (data.payload.type === "set_title") updateName(data.payload.label, data.payload.title); }); diff --git a/apps/desktop2/src/routes/$account.tsx b/apps/desktop2/src/routes/$account.tsx index 57e266e9..084b19fa 100644 --- a/apps/desktop2/src/routes/$account.tsx +++ b/apps/desktop2/src/routes/$account.tsx @@ -34,8 +34,8 @@ function Screen() { type: "add", column: { label: "store", - name: "Store", - content: "/store/official", + name: "Column Gallery", + content: "/store", }, }); }; diff --git a/apps/desktop2/src/routes/global.tsx b/apps/desktop2/src/routes/global.tsx index 8882da17..8496d9e4 100644 --- a/apps/desktop2/src/routes/global.tsx +++ b/apps/desktop2/src/routes/global.tsx @@ -81,11 +81,11 @@ export function Screen() { {isFetching && !isLoading && !isFetchingNextPage ? ( -
+
- Fetching new notes... + Getting new notes...
diff --git a/apps/desktop2/src/routes/group.tsx b/apps/desktop2/src/routes/group.tsx index 7334ea3d..40f95f67 100644 --- a/apps/desktop2/src/routes/group.tsx +++ b/apps/desktop2/src/routes/group.tsx @@ -95,11 +95,11 @@ export function Screen() { {isFetching && !isLoading && !isFetchingNextPage ? ( -
+
- Fetching new notes... + Getting new notes...
diff --git a/apps/desktop2/src/routes/newsfeed.tsx b/apps/desktop2/src/routes/newsfeed.tsx index 65d15aae..0676077d 100644 --- a/apps/desktop2/src/routes/newsfeed.tsx +++ b/apps/desktop2/src/routes/newsfeed.tsx @@ -111,7 +111,7 @@ export function Screen() { {isFetching && !isLoading && !isFetchingNextPage ? ( -
+
diff --git a/apps/desktop2/src/routes/store.community.tsx b/apps/desktop2/src/routes/store.community.tsx deleted file mode 100644 index 50cc6fa1..00000000 --- a/apps/desktop2/src/routes/store.community.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/store/community")({ - component: Screen, -}); - -function Screen() { - return ( -
-
-
-
-
-

Coming Soon

-

- Enhance your experience
by adding column shared by community. -

-
-
- ); -} diff --git a/apps/desktop2/src/routes/store.official.tsx b/apps/desktop2/src/routes/store.official.tsx deleted file mode 100644 index 647b7325..00000000 --- a/apps/desktop2/src/routes/store.official.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { LumeColumn } from "@lume/types"; -import { createFileRoute } from "@tanstack/react-router"; -import { resolveResource } from "@tauri-apps/api/path"; -import { getCurrent } from "@tauri-apps/api/window"; -import { readTextFile } from "@tauri-apps/plugin-fs"; - -export const Route = createFileRoute("/store/official")({ - beforeLoad: async () => { - const resourcePath = await resolveResource( - "resources/official_columns.json", - ); - const officialColumns: LumeColumn[] = JSON.parse( - await readTextFile(resourcePath), - ); - - return { - officialColumns, - }; - }, - component: Screen, -}); - -function Screen() { - const { officialColumns } = Route.useRouteContext(); - - const install = async (column: LumeColumn) => { - const mainWindow = getCurrent(); - await mainWindow.emit("columns", { type: "add", column }); - }; - - return ( -
- {officialColumns.map((column) => ( -
- {column.cover ? ( - {column.name} - ) : null} -
-
-
-

{column.name}

-

- {column.description} -

-
- -
-
-
- ))} -
- ); -} diff --git a/apps/desktop2/src/routes/store.tsx b/apps/desktop2/src/routes/store.tsx index 8c42716e..aab6235a 100644 --- a/apps/desktop2/src/routes/store.tsx +++ b/apps/desktop2/src/routes/store.tsx @@ -1,48 +1,104 @@ -import { GlobalIcon, LaurelIcon } from "@lume/icons"; -import { cn } from "@lume/utils"; -import { Link } from "@tanstack/react-router"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { CommunityIcon, LaurelIcon } from "@lume/icons"; +import type { LumeColumn } from "@lume/types"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { createFileRoute } from "@tanstack/react-router"; +import { resolveResource } from "@tauri-apps/api/path"; +import { getCurrent } from "@tauri-apps/api/window"; +import { readTextFile } from "@tauri-apps/plugin-fs"; export const Route = createFileRoute("/store")({ + beforeLoad: async () => { + const path = "resources/official_columns.json"; + const resourcePath = await resolveResource(path); + const fileContent = await readTextFile(resourcePath); + const officialColumns: LumeColumn[] = JSON.parse(fileContent); + + return { + officialColumns, + }; + }, component: Screen, }); function Screen() { + const { officialColumns } = Route.useRouteContext(); + + const install = async (column: LumeColumn) => { + const mainWindow = getCurrent(); + await mainWindow.emit("columns", { type: "add", column }); + }; + return ( -
-
-
- - {({ isActive }) => ( -
+
+ + +
+
+
- Official
- )} - - - {({ isActive }) => ( -
- - Community + Official +
+
+ {officialColumns.map((column) => ( +
+
+ +
+
+ {column.cover ? ( + {column.name} + ) : null} +
+
+

+ {column.name} +

+
+
+ ))} +
+
+
+
+
+
- )} - -
-
-
- -
+ Community +
+
+ Coming Soon. +
+
+ + + + + +
); } diff --git a/apps/desktop2/src/routes/topic.tsx b/apps/desktop2/src/routes/topic.tsx index 1eaeb044..fb7efaa4 100644 --- a/apps/desktop2/src/routes/topic.tsx +++ b/apps/desktop2/src/routes/topic.tsx @@ -105,11 +105,11 @@ export function Screen() { {isFetching && !isLoading && !isFetchingNextPage ? ( -
+
- Fetching new notes... + Getting new notes...
diff --git a/apps/desktop2/src/routes/trending.notes.tsx b/apps/desktop2/src/routes/trending.notes.tsx index e340937d..955faf06 100644 --- a/apps/desktop2/src/routes/trending.notes.tsx +++ b/apps/desktop2/src/routes/trending.notes.tsx @@ -1,11 +1,14 @@ +import { Conversation } from "@/components/conversation"; +import { Quote } from "@/components/quote"; +import { RepostNote } from "@/components/repost"; import { TextNote } from "@/components/text"; import { LumeEvent } from "@lume/system"; -import type { NostrEvent } from "@lume/types"; +import { Kind, type NostrEvent } from "@lume/types"; import { Spinner } from "@lume/ui"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { Await, createFileRoute } from "@tanstack/react-router"; import { defer } from "@tanstack/react-router"; -import { Suspense, useRef } from "react"; +import { Suspense, useCallback, useRef } from "react"; import { Virtualizer } from "virtua"; export const Route = createFileRoute("/trending/notes")({ @@ -21,7 +24,9 @@ export const Route = createFileRoute("/trending/notes")({ const events: NostrEvent[] = res.notes.map( (item: { event: NostrEvent }) => item.event, ); - const lumeEvents = events.map((ev) => new LumeEvent(ev)); + const lumeEvents = Promise.all( + events.map(async (ev) => await LumeEvent.build(ev)), + ); return lumeEvents; }), ), @@ -35,7 +40,24 @@ export const Route = createFileRoute("/trending/notes")({ export function Screen() { const { data } = Route.useLoaderData(); + const ref = useRef(null); + const renderItem = useCallback((event: LumeEvent) => { + if (!event) return; + switch (event.kind) { + case Kind.Repost: + return ; + default: { + if (event.isConversation) { + return ; + } + if (event.isQuote) { + return ; + } + return ; + } + } + }, []); return ( - {(notes) => - notes.map((event) => ( - - )) - } + {(notes) => notes.map((event) => renderItem(event))} diff --git a/apps/desktop2/src/routes/trending.tsx b/apps/desktop2/src/routes/trending.tsx index 1631d8c1..0dcf738e 100644 --- a/apps/desktop2/src/routes/trending.tsx +++ b/apps/desktop2/src/routes/trending.tsx @@ -25,14 +25,14 @@ function Screen() { return (
-
-
+
+
{({ isActive }) => (
@@ -44,8 +44,8 @@ function Screen() { {({ isActive }) => (
@@ -55,7 +55,7 @@ function Screen() {
-
+
diff --git a/packages/system/src/commands.ts b/packages/system/src/commands.ts index 86cc0faa..cf522b9f 100644 --- a/packages/system/src/commands.ts +++ b/packages/system/src/commands.ts @@ -428,6 +428,14 @@ try { else return { status: "error", error: e as any }; } }, +async reloadColumn(label: string) : Promise> { +try { + return { status: "ok", data: await TAURI_INVOKE("reload_column", { label }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async openWindow(window: Window) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("open_window", { window }) }; diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index 2d4da9e7..46987f62 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -78,10 +78,11 @@ export interface LumeColumn { } export interface ColumnEvent { - type: "reset" | "add" | "remove" | "update" | "left" | "right" | "set_title"; + type: "reset" | "add" | "remove" | "update" | "move" | "set_title"; label?: string; title?: string; column?: LumeColumn; + direction?: "left" | "right"; } export interface Relays { diff --git a/src-tauri/resources/official_columns.json b/src-tauri/resources/official_columns.json index 9ed0b5f9..316c4f2a 100644 --- a/src-tauri/resources/official_columns.json +++ b/src-tauri/resources/official_columns.json @@ -1,52 +1,37 @@ [ - { - "label": "lZfXLFgPPR4NNrgjlWDxn", - "name": "Newsfeed", - "content": "/newsfeed", - "logo": "", - "cover": "/newsfeed.png", - "coverRetina": "/newsfeed@2x.png", - "author": "Lume", - "description": "Keep up to date with the people you're following." - }, - { - "label": "rRtguZwIpd5G8Wt54OTb7", - "name": "Topic", - "content": "/topic", - "logo": "", - "cover": "/foryou.png", - "coverRetina": "/foryou@2x.png", - "author": "Lume", - "description": "Keep up to date with content based on your interests." - }, - { - "label": "fve9fk2fVyFWORPBkjd79", - "name": "Group", - "content": "/group", - "logo": "", - "cover": "/group.png", - "coverRetina": "/group@2x.png", - "author": "Lume", - "description": "Focus feeds for people you like." - }, - { - "label": "gxtcIbgD8YNPbeI5o92I8", - "name": "Trending", - "content": "/trending/notes", - "logo": "", - "cover": "/trending.png", - "coverRetina": "/trending@2x.png", - "author": "Lume", - "description": "What is trending on Nostr?." - }, - { - "label": "GLFm44za8rhJDP04LMr3M", - "name": "Global", - "content": "/global", - "logo": "", - "cover": "/global.png", - "coverRetina": "/global@2x.png", - "author": "Lume", - "description": "All events from connected relays." - } + { + "label": "lZfXLFgPPR4NNrgjlWDxn", + "name": "Local Feeds", + "content": "/newsfeed", + "cover": "/newsfeed.png", + "coverRetina": "/newsfeed@2x.png" + }, + { + "label": "GLFm44za8rhJDP04LMr3M", + "name": "Global Feeds", + "content": "/global", + "cover": "/global.png", + "coverRetina": "/global@2x.png" + }, + { + "label": "fve9fk2fVyFWORPBkjd79", + "name": "Group Feeds", + "content": "/group", + "cover": "/group.png", + "coverRetina": "/group@2x.png" + }, + { + "label": "rRtguZwIpd5G8Wt54OTb7", + "name": "Topic", + "content": "/topic", + "cover": "/foryou.png", + "coverRetina": "/foryou@2x.png" + }, + { + "label": "gxtcIbgD8YNPbeI5o92I8", + "name": "Trending", + "content": "/trending/notes", + "cover": "/trending.png", + "coverRetina": "/trending@2x.png" + } ] diff --git a/src-tauri/src/commands/window.rs b/src-tauri/src/commands/window.rs index f41a85bf..bad1fc44 100644 --- a/src-tauri/src/commands/window.rs +++ b/src-tauri/src/commands/window.rs @@ -138,6 +138,21 @@ pub fn resize_column( } } +#[tauri::command] +#[specta::specta] +pub fn reload_column(label: &str, app_handle: tauri::AppHandle) -> Result<(), String> { + match app_handle.get_webview(label) { + Some(webview) => { + if webview.eval("window.location.reload()").is_ok() { + Ok(()) + } else { + Err("Reload column failed".into()) + } + } + None => Err("Webview not found".into()), + } +} + #[tauri::command] #[specta::specta] pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), String> { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a5010ca4..c2348f01 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -124,6 +124,7 @@ fn main() { commands::window::close_column, commands::window::reposition_column, commands::window::resize_column, + commands::window::reload_column, commands::window::open_window, commands::window::open_main_window, commands::window::set_badge