From 0fec21b9cec0f611ec0800944fe46698f2afe5c2 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: Sun, 30 Jun 2024 14:26:02 +0700 Subject: [PATCH] Some improments and Negentropy (#219) * feat: adjust default window size * feat: save window state * feat: add window state plugin * feat: add search * feat: use negentropy for newsfeed * feat: live feeds * feat: add search user --- apps/desktop2/src/app.tsx | 3 +- .../src/components/note/mentions/note.tsx | 8 +- .../src/components/note/preview/images.tsx | 2 +- .../src/components/note/preview/video.tsx | 3 +- .../src/components/note/preview/videos.tsx | 6 +- apps/desktop2/src/components/note/user.tsx | 2 +- apps/desktop2/src/components/repost.tsx | 2 +- apps/desktop2/src/components/text.tsx | 2 - apps/desktop2/src/components/user/avatar.tsx | 35 +- apps/desktop2/src/routes/$account.home.tsx | 50 +- apps/desktop2/src/routes/$account.tsx | 280 +- apps/desktop2/src/routes/create-group.tsx | 4 +- .../src/routes/create-newsfeed.users.tsx | 2 +- apps/desktop2/src/routes/editor/index.tsx | 14 +- apps/desktop2/src/routes/index.tsx | 2 +- apps/desktop2/src/routes/newsfeed.tsx | 101 +- apps/desktop2/src/routes/nwc.lazy.tsx | 52 - apps/desktop2/src/routes/panel.tsx | 72 +- apps/desktop2/src/routes/search.notes.tsx | 96 + apps/desktop2/src/routes/search.tsx | 168 +- apps/desktop2/src/routes/search.users.tsx | 101 + apps/desktop2/src/routes/settings/backup.tsx | 2 +- apps/desktop2/src/routes/trending.users.tsx | 4 +- package.json | 4 +- packages/system/src/commands.ts | 26 +- packages/system/src/event.ts | 15 +- packages/system/src/query.ts | 23 + packages/system/src/window.ts | 12 +- packages/tailwindcss/package.json | 3 +- packages/tailwindcss/tailwind.config.js | 2 +- packages/utils/index.ts | 1 - packages/utils/src/delay.ts | 1 - pnpm-lock.yaml | 7465 +++++++++-------- src-tauri/Cargo.lock | 35 +- src-tauri/Cargo.toml | 11 +- src-tauri/capabilities/column.json | 51 + src-tauri/capabilities/main.json | 177 +- src-tauri/gen/schemas/acl-manifests.json | 2 +- src-tauri/gen/schemas/capabilities.json | 2 +- src-tauri/gen/schemas/desktop-schema.json | 250 +- src-tauri/gen/schemas/macOS-schema.json | 250 +- src-tauri/src/main.rs | 12 +- src-tauri/src/nostr/event.rs | 68 +- src-tauri/src/nostr/keys.rs | 68 +- src-tauri/src/nostr/metadata.rs | 12 +- src-tauri/tauri.macos.conf.json | 70 +- 46 files changed, 5633 insertions(+), 3938 deletions(-) delete mode 100644 apps/desktop2/src/routes/nwc.lazy.tsx create mode 100644 apps/desktop2/src/routes/search.notes.tsx create mode 100644 apps/desktop2/src/routes/search.users.tsx delete mode 100644 packages/utils/src/delay.ts create mode 100644 src-tauri/capabilities/column.json diff --git a/apps/desktop2/src/app.tsx b/apps/desktop2/src/app.tsx index bdd3cbe0..f65c8ff6 100644 --- a/apps/desktop2/src/app.tsx +++ b/apps/desktop2/src/app.tsx @@ -1,12 +1,11 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider, createRouter } from "@tanstack/react-router"; -import { StrictMode } from "react"; import { type } from "@tauri-apps/plugin-os"; +import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import { routeTree } from "./router.gen"; // auto generated file import "./app.css"; -// Set up a Router instance const queryClient = new QueryClient(); const platform = type(); const router = createRouter({ diff --git a/apps/desktop2/src/components/note/mentions/note.tsx b/apps/desktop2/src/components/note/mentions/note.tsx index c81f2dfe..7b7bf1cf 100644 --- a/apps/desktop2/src/components/note/mentions/note.tsx +++ b/apps/desktop2/src/components/note/mentions/note.tsx @@ -1,8 +1,8 @@ -import { LumeWindow, useEvent } from "@lume/system"; -import { LinkIcon } from "@lume/icons"; -import { cn } from "@lume/utils"; import { User } from "@/components/user"; +import { LinkIcon } from "@lume/icons"; +import { LumeWindow, useEvent } from "@lume/system"; import { Spinner } from "@lume/ui"; +import { cn } from "@lume/utils"; export function MentionNote({ eventId, @@ -40,7 +40,7 @@ export function MentionNote({
- +
ยท diff --git a/apps/desktop2/src/components/note/preview/images.tsx b/apps/desktop2/src/components/note/preview/images.tsx index dac869f0..0be699a1 100644 --- a/apps/desktop2/src/components/note/preview/images.tsx +++ b/apps/desktop2/src/components/note/preview/images.tsx @@ -96,7 +96,7 @@ export function Images({ urls }: { urls: string[] }) { } return ( -
+
{imageUrls.map((url, index) => ( diff --git a/apps/desktop2/src/components/note/preview/video.tsx b/apps/desktop2/src/components/note/preview/video.tsx index 0fb21b17..04d7db36 100644 --- a/apps/desktop2/src/components/note/preview/video.tsx +++ b/apps/desktop2/src/components/note/preview/video.tsx @@ -20,10 +20,11 @@ export function VideoPreview({ url }: { url: string }) {
diff --git a/apps/desktop2/src/components/note/preview/videos.tsx b/apps/desktop2/src/components/note/preview/videos.tsx index a7d5f5bd..4642c7d7 100644 --- a/apps/desktop2/src/components/note/preview/videos.tsx +++ b/apps/desktop2/src/components/note/preview/videos.tsx @@ -5,12 +5,12 @@ export function Videos({ urls }: { urls: string[] }) { return (
@@ -28,7 +28,7 @@ export function Videos({ urls }: { urls: string[] }) { controls={false} muted > - + Your browser does not support the video tag. diff --git a/apps/desktop2/src/components/note/user.tsx b/apps/desktop2/src/components/note/user.tsx index e545a184..24674d78 100644 --- a/apps/desktop2/src/components/note/user.tsx +++ b/apps/desktop2/src/components/note/user.tsx @@ -42,7 +42,7 @@ export function NoteUser({ className }: { className?: string }) { onClick={(e) => showContextMenu(e)} className="shrink-0" > - +
diff --git a/apps/desktop2/src/components/repost.tsx b/apps/desktop2/src/components/repost.tsx index 4b9868a4..20dc81bf 100644 --- a/apps/desktop2/src/components/repost.tsx +++ b/apps/desktop2/src/components/repost.tsx @@ -69,7 +69,7 @@ export const RepostNote = memo(function RepostNote({
Reposted by
- +
diff --git a/apps/desktop2/src/components/text.tsx b/apps/desktop2/src/components/text.tsx index f35d4c1d..9c7dd5fd 100644 --- a/apps/desktop2/src/components/text.tsx +++ b/apps/desktop2/src/components/text.tsx @@ -10,8 +10,6 @@ export const TextNote = memo(function TextNote({ event: LumeEvent; className?: string; }) { - console.log("Rendered at: ", event.id, new Date().toLocaleTimeString()); - return ( `data:image/svg+xml;utf8,${encodeURIComponent( - minidenticon(user.pubkey || nanoid(), 90, 50), + minidenticon(user.pubkey, 60, 50), )}`, [user.pubkey], ); if (settings && !settings.display_avatar) { return ( - + {user.pubkey} @@ -45,19 +51,24 @@ export function UserAvatar({ className }: { className?: string }) { } return ( - + - + {user.pubkey} diff --git a/apps/desktop2/src/routes/$account.home.tsx b/apps/desktop2/src/routes/$account.home.tsx index 742ab2dc..193259ef 100644 --- a/apps/desktop2/src/routes/$account.home.tsx +++ b/apps/desktop2/src/routes/$account.home.tsx @@ -1,6 +1,6 @@ import { Column } from "@/components/column"; import { Toolbar } from "@/components/toolbar"; -import { ArrowLeftIcon, ArrowRightIcon, PlusSquareIcon } from "@lume/icons"; +import { ArrowLeftIcon, ArrowRightIcon } from "@lume/icons"; import { NostrQuery } from "@lume/system"; import type { ColumnEvent, LumeColumn } from "@lume/types"; import { createFileRoute } from "@tanstack/react-router"; @@ -45,17 +45,6 @@ function Screen() { getCurrent().emit("child-webview", { resize: true, direction: "x" }); }, []); - const openLumeStore = useDebouncedCallback(async () => { - await getCurrent().emit("columns", { - type: "add", - column: { - label: "store", - name: "Store", - content: "/store/official", - }, - }); - }, 150); - const add = useDebouncedCallback((column: LumeColumn) => { column.label = `${column.label}-${nanoid()}`; // update col label setColumns((prev) => [column, ...prev]); @@ -158,29 +147,20 @@ function Screen() {
-
- - - -
+ +
); diff --git a/apps/desktop2/src/routes/$account.tsx b/apps/desktop2/src/routes/$account.tsx index e09378f1..cdca5643 100644 --- a/apps/desktop2/src/routes/$account.tsx +++ b/apps/desktop2/src/routes/$account.tsx @@ -1,49 +1,83 @@ import { User } from "@/components/user"; -import { ComposeFilledIcon, HorizontalDotsIcon, PlusIcon } from "@lume/icons"; +import { + ChevronDownIcon, + ComposeFilledIcon, + PlusIcon, + SearchIcon, +} from "@lume/icons"; import { LumeWindow, NostrAccount } from "@lume/system"; import { cn } from "@lume/utils"; -import * as Popover from "@radix-ui/react-popover"; import { Outlet, createFileRoute } from "@tanstack/react-router"; -import { Link } from "@tanstack/react-router"; -import { Menu, MenuItem } from "@tauri-apps/api/menu"; +import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { getCurrent } from "@tauri-apps/api/window"; import { message } from "@tauri-apps/plugin-dialog"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { memo, useCallback, useState } from "react"; export const Route = createFileRoute("/$account")({ - beforeLoad: async () => { + beforeLoad: async ({ params }) => { const accounts = await NostrAccount.getAccounts(); - return { accounts }; + const otherAccounts = accounts.filter( + (account) => account !== params.account, + ); + + return { otherAccounts }; }, component: Screen, }); function Screen() { + const { platform } = Route.useRouteContext(); + + const openLumeStore = async () => { + await getCurrent().emit("columns", { + type: "add", + column: { + label: "store", + name: "Store", + content: "/store/official", + }, + }); + }; + return (
-
- - + +
-
+
+ +
+
-
+
@@ -53,34 +87,25 @@ function Screen() { ); } -function Accounts() { - const navigate = Route.useNavigate(); - const { accounts } = Route.useRouteContext(); +const Accounts = memo(function Accounts() { + const { otherAccounts } = Route.useRouteContext(); const { account } = Route.useParams(); - const [windowWidth, setWindowWidth] = useState(null); - - const sortedList = useMemo(() => { - const list = accounts; - - for (const [i, item] of list.entries()) { - if (item === account) { - list.splice(i, 1); - list.unshift(item); - } - } - - return list; - }, [accounts]); + const navigate = Route.useNavigate(); const showContextMenu = useCallback( - async (e: React.MouseEvent, npub: string) => { + async (e: React.MouseEvent) => { e.preventDefault(); const menuItems = await Promise.all([ + MenuItem.new({ + text: "New Post", + action: () => LumeWindow.openEditor(), + }), + PredefinedMenuItem.new({ item: "Separator" }), MenuItem.new({ text: "View Profile", - action: () => LumeWindow.openProfile(npub), + action: () => LumeWindow.openProfile(account), }), MenuItem.new({ text: "Open Settings", @@ -94,112 +119,107 @@ function Accounts() { await menu.popup().catch((e) => console.error(e)); }, - [], + [account], ); - const changeAccount = async (e: React.MouseEvent, npub: string) => { - if (npub === account) { - return showContextMenu(e, npub); - } + const changeAccount = useCallback( + async (npub: string) => { + // Change current account and update signer + const select = await NostrAccount.loadAccount(npub); - // Change current account and update signer - const select = await NostrAccount.loadAccount(npub); + if (select) { + // Reset current columns + await getCurrent().emit("columns", { type: "reset" }); - if (select) { - // Reset current columns - await getCurrent().emit("columns", { type: "reset" }); + // Redirect to new account + return navigate({ + to: "/$account/home", + params: { account: npub }, + resetScroll: true, + replace: true, + }); + } else { + await message("Something wrong.", { title: "Accounts", kind: "error" }); + } + }, + [otherAccounts], + ); - // Redirect to new account - return navigate({ - to: "/$account/home", - params: { account: npub }, - resetScroll: true, - replace: true, - }); - } else { - await message("Something wrong.", { title: "Accounts", kind: "error" }); - } - }; + return ( +
+ {otherAccounts.map((npub) => ( + + ))} + +
+ ); +}); - const getWindowDimensions = () => { - const { innerWidth: width, innerHeight: height } = window; - return { - width, - height, - }; - }; +const Search = memo(function Search() { + const [searchType, setSearchType] = useState<"notes" | "users">("notes"); + const [query, setQuery] = useState(""); - useEffect(() => { - function handleResize() { - setWindowWidth(getWindowDimensions().width); - } + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); - if (!windowWidth) { - setWindowWidth(getWindowDimensions().width); - } + const menuItems = await Promise.all([ + MenuItem.new({ + text: "Notes", + action: () => setSearchType("notes"), + }), + MenuItem.new({ + text: "Users", + action: () => setSearchType("users"), + }), + ]); - window.addEventListener("resize", handleResize); + const menu = await Menu.new({ + items: menuItems, + }); - return () => { - window.removeEventListener("resize", handleResize); - }; + await menu.popup().catch((e) => console.error(e)); }, []); return ( -
- {sortedList - .slice(0, windowWidth > 500 ? account.length : 2) - .map((user) => ( - - ))} - {accounts.length >= 3 && windowWidth <= 700 ? ( - - - - - - - {sortedList.slice(2).map((user) => ( - - ))} - - - - - ) : null} +
+ + setQuery(e.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + LumeWindow.openSearch(searchType, query); + } + }} + className="h-full w-full px-3 text-sm rounded-full border-none ring-0 focus:ring-0 focus:outline-none bg-transparent placeholder:text-black/50 dark:placeholder:text-black/50" + /> +
); -} +}); diff --git a/apps/desktop2/src/routes/create-group.tsx b/apps/desktop2/src/routes/create-group.tsx index a552b7ae..f773ca59 100644 --- a/apps/desktop2/src/routes/create-group.tsx +++ b/apps/desktop2/src/routes/create-group.tsx @@ -126,7 +126,7 @@ function Screen() { > - +
@@ -157,7 +157,7 @@ function Screen() { > - +
diff --git a/apps/desktop2/src/routes/create-newsfeed.users.tsx b/apps/desktop2/src/routes/create-newsfeed.users.tsx index e8827906..4a1440d2 100644 --- a/apps/desktop2/src/routes/create-newsfeed.users.tsx +++ b/apps/desktop2/src/routes/create-newsfeed.users.tsx @@ -95,7 +95,7 @@ function Screen() {
- +
+
+ ); +} diff --git a/apps/desktop2/src/routes/nwc.lazy.tsx b/apps/desktop2/src/routes/nwc.lazy.tsx deleted file mode 100644 index 1454eebb..00000000 --- a/apps/desktop2/src/routes/nwc.lazy.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { ZapIcon } from "@lume/icons"; -import { NostrAccount } from "@lume/system"; -import { Container } from "@lume/ui"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { useState } from "react"; - -export const Route = createLazyFileRoute("/nwc")({ - component: Screen, -}); - -function Screen() { - const [uri, setUri] = useState(""); - const [isDone, setIsDone] = useState(false); - - const save = async () => { - const nwc = await NostrAccount.setWallet(uri); - setIsDone(nwc); - }; - - return ( - -
-
-
-

- Connect bitcoin wallet to - start zapping to your favorite content and creator. -

-
-
-
-
- -