diff --git a/src-tauri/src/commands/window.rs b/src-tauri/src/commands/window.rs index ca075eab..41259f35 100644 --- a/src-tauri/src/commands/window.rs +++ b/src-tauri/src/commands/window.rs @@ -48,18 +48,17 @@ pub fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result Ok(webview.label().into()), + Err(e) => Err(e.to_string()), } } }, - None => Err("Main window not found".into()), + None => Err("Window not found".into()), } } @@ -68,35 +67,33 @@ pub fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result Result { match app_handle.get_webview(&label) { Some(webview) => Ok(webview.close().is_ok()), - None => Err("Not found.".into()), + None => Err("Cannot close, column not found.".into()), } } #[tauri::command(async)] #[specta::specta] -pub fn reposition_column( - label: String, - x: f32, - y: f32, - app_handle: tauri::AppHandle, -) -> Result { - match app_handle.get_webview(&label) { - Some(webview) => Ok(webview.set_position(LogicalPosition::new(x, y)).is_ok()), - None => Err("Not found".into()), - } -} - -#[tauri::command(async)] -#[specta::specta] -pub fn resize_column( +pub fn update_column( label: String, width: f32, height: f32, + x: f32, + y: f32, app_handle: tauri::AppHandle, -) -> Result { +) -> Result<(), String> { match app_handle.get_webview(&label) { - Some(webview) => Ok(webview.set_size(LogicalSize::new(width, height)).is_ok()), - None => Err("Not found".into()), + Some(webview) => { + if let Err(e) = webview.set_size(LogicalSize::new(width, height)) { + return Err(e.to_string()); + } + + if let Err(e) = webview.set_position(LogicalPosition::new(x, y)) { + return Err(e.to_string()); + } + + Ok(()) + } + None => Err("Cannot update, column not found.".into()), } } @@ -105,7 +102,7 @@ pub fn resize_column( pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result { match app_handle.get_webview(&label) { Some(webview) => Ok(webview.eval("window.location.reload()").is_ok()), - None => Err("Not found".into()), + None => Err("Cannot reload, column not found.".into()), } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 603f09de..d25b852a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -151,10 +151,9 @@ fn main() { event_to_bech32, user_to_bech32, create_column, - close_column, - reposition_column, - resize_column, + update_column, reload_column, + close_column, open_window, reopen_lume, quit diff --git a/src/commands.gen.ts b/src/commands.gen.ts index 93e42409..532c4c67 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -454,25 +454,9 @@ async createColumn(column: Column) : Promise> { else return { status: "error", error: e as any }; } }, -async closeColumn(label: string) : Promise> { +async updateColumn(label: string, width: number, height: number, x: number, y: number) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("close_column", { label }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async repositionColumn(label: string, x: number, y: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("reposition_column", { label, x, y }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async resizeColumn(label: string, width: number, height: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("resize_column", { label, width, height }) }; + return { status: "ok", data: await TAURI_INVOKE("update_column", { label, width, height, x, y }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -486,6 +470,14 @@ async reloadColumn(label: string) : Promise> { else return { status: "error", error: e as any }; } }, +async closeColumn(label: string) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("close_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/src/components/column.tsx b/src/components/column.tsx index a51598f7..06d29ff3 100644 --- a/src/components/column.tsx +++ b/src/components/column.tsx @@ -1,107 +1,89 @@ import { commands } from "@/commands.gen"; import { appColumns } from "@/commons"; +import { useRect } from "@/system"; import type { LumeColumn } from "@/types"; import { CaretDown, Check } from "@phosphor-icons/react"; import { useParams } from "@tanstack/react-router"; import { useStore } from "@tanstack/react-store"; -import { invoke } from "@tauri-apps/api/core"; -import { listen } from "@tauri-apps/api/event"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { getCurrentWindow } from "@tauri-apps/api/window"; -import { memo, useCallback, useEffect, useRef, useState } from "react"; -import { Spinner } from "./spinner"; +import { useCallback, useEffect, useMemo, useState } from "react"; -type WindowEvent = { - scroll: boolean; - resize: boolean; -}; - -export const Column = memo(function Column({ column }: { column: LumeColumn }) { +export function Column({ column }: { column: LumeColumn }) { const params = useParams({ strict: false }); - const container = useRef(null); - const webviewLabel = `column-${params.account}_${column.label}`; + const webviewLabel = useMemo( + () => `column-${params.account}_${column.label}`, + [params.account, column.label], + ); - const [isCreated, setIsCreated] = useState(false); - - const repositionWebview = useCallback(async () => { - if (!container.current) return; - - const newRect = container.current.getBoundingClientRect(); - await invoke("reposition_column", { - label: webviewLabel, - x: newRect.x, - y: newRect.y, - }); - }, []); - - const resizeWebview = useCallback(async () => { - if (!container.current) return; - - const newRect = container.current.getBoundingClientRect(); - await invoke("resize_column", { - label: webviewLabel, - width: newRect.width, - height: newRect.height, - }); - }, []); + const [rect, ref] = useRect(); + const [error, setError] = useState(null); useEffect(() => { - if (!isCreated) return; + (async () => { + if (rect) { + const res = await commands.updateColumn( + webviewLabel, + rect.width, + rect.height, + rect.x, + rect.y, + ); - const unlisten = listen("child_webview", (data) => { - if (data.payload.scroll) repositionWebview(); - if (data.payload.resize) repositionWebview().then(() => resizeWebview()); - }); - - return () => { - unlisten.then((f) => f()); - }; - }, [isCreated]); + if (res.status === "ok") { + console.log("webview is updated: ", webviewLabel); + } else { + console.log("webview error: ", res.error); + } + } + })(); + }, [rect]); useEffect(() => { - if (!container.current) return; + const isCreated = window.sessionStorage.getItem(webviewLabel); - const rect = container.current.getBoundingClientRect(); - const url = `${column.url}?account=${params.account}&label=${column.label}&name=${column.name}`; + if (!isCreated) { + const initialRect = ref.current.getBoundingClientRect(); - const prop = { - label: webviewLabel, - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - url, - }; + commands + .createColumn({ + label: webviewLabel, + x: initialRect.x, + y: initialRect.y, + width: initialRect.width, + height: initialRect.height, + url: `${column.url}?account=${params.account}&label=${column.label}&name=${column.name}`, + }) + .then((res) => { + if (res.status === "ok") { + console.log("webview is created: ", webviewLabel); + window.sessionStorage.setItem(webviewLabel, ""); + } else { + setError(res.error); + } + }); - // create new webview - invoke("create_column", { column: prop }).then(() => { - console.log("created: ", webviewLabel); - setIsCreated(true); - }); - - // close webview when unmounted - return () => { - invoke("close_column", { label: webviewLabel }).then(() => { - console.log("closed: ", webviewLabel); - }); - }; + return () => { + commands.closeColumn(webviewLabel).then((res) => { + if (res.status === "ok") { + console.log("webview is closed: ", webviewLabel); + } else { + console.log("webview error: ", res.error); + } + }); + }; + } }, [params.account]); return (
-
- {!isCreated ? ( -
- -
- ) : null} -
+
); -}); +} function Header({ label }: { label: string }) { const [title, setTitle] = useState(""); diff --git a/src/routes.gen.ts b/src/routes.gen.ts index 792be33e..3f47bf65 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -32,7 +32,6 @@ import { Route as AccountSettingsRelayImport } from './routes/$account/_settings import { Route as AccountSettingsProfileImport } from './routes/$account/_settings/profile' import { Route as AccountSettingsGeneralImport } from './routes/$account/_settings/general' import { Route as AccountSettingsBitcoinConnectImport } from './routes/$account/_settings/bitcoin-connect' -import { Route as AccountAppHomeImport } from './routes/$account/_app/home' import { Route as ColumnsLayoutInterestsIdImport } from './routes/columns/_layout/interests.$id' import { Route as ColumnsLayoutGroupsIdImport } from './routes/columns/_layout/groups.$id' import { Route as ColumnsLayoutCreateNewsfeedUsersImport } from './routes/columns/_layout/create-newsfeed.users' @@ -63,6 +62,7 @@ const ColumnsLayoutNotificationLazyImport = createFileRoute( const ColumnsLayoutLaunchpadLazyImport = createFileRoute( '/columns/_layout/launchpad', )() +const AccountAppHomeLazyImport = createFileRoute('/$account/_app/home')() const ColumnsLayoutUsersIdLazyImport = createFileRoute( '/columns/_layout/users/$id', )() @@ -208,6 +208,13 @@ const ColumnsLayoutLaunchpadLazyRoute = ColumnsLayoutLaunchpadLazyImport.update( import('./routes/columns/_layout/launchpad.lazy').then((d) => d.Route), ) +const AccountAppHomeLazyRoute = AccountAppHomeLazyImport.update({ + path: '/home', + getParentRoute: () => AccountAppRoute, +} as any).lazy(() => + import('./routes/$account/_app.home.lazy').then((d) => d.Route), +) + const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({ path: '/stories', getParentRoute: () => ColumnsLayoutRoute, @@ -271,13 +278,6 @@ const AccountSettingsBitcoinConnectRoute = ), ) -const AccountAppHomeRoute = AccountAppHomeImport.update({ - path: '/home', - getParentRoute: () => AccountAppRoute, -} as any).lazy(() => - import('./routes/$account/_app/home.lazy').then((d) => d.Route), -) - const ColumnsLayoutUsersIdLazyRoute = ColumnsLayoutUsersIdLazyImport.update({ path: '/users/$id', getParentRoute: () => ColumnsLayoutRoute, @@ -457,13 +457,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof EditorIndexImport parentRoute: typeof rootRoute } - '/$account/_app/home': { - id: '/$account/_app/home' - path: '/home' - fullPath: '/$account/home' - preLoaderRoute: typeof AccountAppHomeImport - parentRoute: typeof AccountAppImport - } '/$account/_settings/bitcoin-connect': { id: '/$account/_settings/bitcoin-connect' path: '/bitcoin-connect' @@ -527,6 +520,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutStoriesImport parentRoute: typeof ColumnsLayoutImport } + '/$account/_app/home': { + id: '/$account/_app/home' + path: '/home' + fullPath: '/$account/home' + preLoaderRoute: typeof AccountAppHomeLazyImport + parentRoute: typeof AccountAppImport + } '/columns/_layout/launchpad': { id: '/columns/_layout/launchpad' path: '/launchpad' @@ -617,11 +617,11 @@ declare module '@tanstack/react-router' { // Create and export the route tree interface AccountAppRouteChildren { - AccountAppHomeRoute: typeof AccountAppHomeRoute + AccountAppHomeLazyRoute: typeof AccountAppHomeLazyRoute } const AccountAppRouteChildren: AccountAppRouteChildren = { - AccountAppHomeRoute: AccountAppHomeRoute, + AccountAppHomeLazyRoute: AccountAppHomeLazyRoute, } const AccountAppRouteWithChildren = AccountAppRoute._addFileChildren( @@ -745,7 +745,6 @@ export interface FileRoutesByFullPath { '/auth/import': typeof AuthImportLazyRoute '/auth/new': typeof AuthNewLazyRoute '/editor': typeof EditorIndexRoute - '/$account/home': typeof AccountAppHomeRoute '/$account/bitcoin-connect': typeof AccountSettingsBitcoinConnectRoute '/$account/general': typeof AccountSettingsGeneralRoute '/$account/profile': typeof AccountSettingsProfileRoute @@ -755,6 +754,7 @@ export interface FileRoutesByFullPath { '/columns/global': typeof ColumnsLayoutGlobalRoute '/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute '/columns/stories': typeof ColumnsLayoutStoriesRoute + '/$account/home': typeof AccountAppHomeLazyRoute '/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute '/columns/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute @@ -785,7 +785,6 @@ export interface FileRoutesByTo { '/auth/import': typeof AuthImportLazyRoute '/auth/new': typeof AuthNewLazyRoute '/editor': typeof EditorIndexRoute - '/$account/home': typeof AccountAppHomeRoute '/$account/bitcoin-connect': typeof AccountSettingsBitcoinConnectRoute '/$account/general': typeof AccountSettingsGeneralRoute '/$account/profile': typeof AccountSettingsProfileRoute @@ -795,6 +794,7 @@ export interface FileRoutesByTo { '/columns/global': typeof ColumnsLayoutGlobalRoute '/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute '/columns/stories': typeof ColumnsLayoutStoriesRoute + '/$account/home': typeof AccountAppHomeLazyRoute '/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute '/columns/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute @@ -829,7 +829,6 @@ export interface FileRoutesById { '/auth/import': typeof AuthImportLazyRoute '/auth/new': typeof AuthNewLazyRoute '/editor/': typeof EditorIndexRoute - '/$account/_app/home': typeof AccountAppHomeRoute '/$account/_settings/bitcoin-connect': typeof AccountSettingsBitcoinConnectRoute '/$account/_settings/general': typeof AccountSettingsGeneralRoute '/$account/_settings/profile': typeof AccountSettingsProfileRoute @@ -839,6 +838,7 @@ export interface FileRoutesById { '/columns/_layout/global': typeof ColumnsLayoutGlobalRoute '/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedRoute '/columns/_layout/stories': typeof ColumnsLayoutStoriesRoute + '/$account/_app/home': typeof AccountAppHomeLazyRoute '/columns/_layout/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute '/columns/_layout/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute @@ -871,7 +871,6 @@ export interface FileRouteTypes { | '/auth/import' | '/auth/new' | '/editor' - | '/$account/home' | '/$account/bitcoin-connect' | '/$account/general' | '/$account/profile' @@ -881,6 +880,7 @@ export interface FileRouteTypes { | '/columns/global' | '/columns/newsfeed' | '/columns/stories' + | '/$account/home' | '/columns/launchpad' | '/columns/notification' | '/columns/onboarding' @@ -910,7 +910,6 @@ export interface FileRouteTypes { | '/auth/import' | '/auth/new' | '/editor' - | '/$account/home' | '/$account/bitcoin-connect' | '/$account/general' | '/$account/profile' @@ -920,6 +919,7 @@ export interface FileRouteTypes { | '/columns/global' | '/columns/newsfeed' | '/columns/stories' + | '/$account/home' | '/columns/launchpad' | '/columns/notification' | '/columns/onboarding' @@ -952,7 +952,6 @@ export interface FileRouteTypes { | '/auth/import' | '/auth/new' | '/editor/' - | '/$account/_app/home' | '/$account/_settings/bitcoin-connect' | '/$account/_settings/general' | '/$account/_settings/profile' @@ -962,6 +961,7 @@ export interface FileRouteTypes { | '/columns/_layout/global' | '/columns/_layout/newsfeed' | '/columns/_layout/stories' + | '/$account/_app/home' | '/columns/_layout/launchpad' | '/columns/_layout/notification' | '/columns/_layout/onboarding' @@ -1131,10 +1131,6 @@ export const routeTree = rootRoute "/editor/": { "filePath": "editor/index.tsx" }, - "/$account/_app/home": { - "filePath": "$account/_app/home.tsx", - "parent": "/$account/_app" - }, "/$account/_settings/bitcoin-connect": { "filePath": "$account/_settings/bitcoin-connect.tsx", "parent": "/$account/_settings" @@ -1175,6 +1171,10 @@ export const routeTree = rootRoute "filePath": "columns/_layout/stories.tsx", "parent": "/columns/_layout" }, + "/$account/_app/home": { + "filePath": "$account/_app.home.lazy.tsx", + "parent": "/$account/_app" + }, "/columns/_layout/launchpad": { "filePath": "columns/_layout/launchpad.lazy.tsx", "parent": "/columns/_layout" diff --git a/src/routes/$account/_app/home.lazy.tsx b/src/routes/$account/_app.home.lazy.tsx similarity index 95% rename from src/routes/$account/_app/home.lazy.tsx rename to src/routes/$account/_app.home.lazy.tsx index 66b0d31e..c565718a 100644 --- a/src/routes/$account/_app/home.lazy.tsx +++ b/src/routes/$account/_app.home.lazy.tsx @@ -45,16 +45,16 @@ function Screen() { }, [emblaApi]); const emitScrollEvent = useCallback(() => { - getCurrentWindow().emit("child_webview", { scroll: true }); - }, []); - - const emitResizeEvent = useCallback(() => { - getCurrentWindow().emit("child_webview", { resize: true, direction: "x" }); + getCurrentWindow().emit("column_scroll", {}); }, []); const add = useDebouncedCallback((column: LumeColumn) => { column.label = `${column.label}-${nanoid()}`; // update col label appColumns.setState((prev) => [column, ...prev]); + + if (emblaApi) { + emblaApi.scrollTo(0, true); + } }, 150); const remove = useDebouncedCallback((label: string) => { @@ -112,16 +112,14 @@ function Screen() { useEffect(() => { if (emblaApi) { emblaApi.on("scroll", emitScrollEvent); - emblaApi.on("resize", emitResizeEvent); emblaApi.on("slidesChanged", emitScrollEvent); } return () => { emblaApi?.off("scroll", emitScrollEvent); - emblaApi?.off("resize", emitResizeEvent); emblaApi?.off("slidesChanged", emitScrollEvent); }; - }, [emblaApi, emitScrollEvent, emitResizeEvent]); + }, [emblaApi, emitScrollEvent]); // Listen for keyboard event useEffect(() => { diff --git a/src/routes/$account/_app/home.tsx b/src/routes/$account/_app/home.tsx deleted file mode 100644 index bb6f89e9..00000000 --- a/src/routes/$account/_app/home.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/_app/home")(); diff --git a/src/system/index.ts b/src/system/index.ts index f1ffc835..6ed6050c 100644 --- a/src/system/index.ts +++ b/src/system/index.ts @@ -1,4 +1,5 @@ export * from "./event"; export * from "./window"; -export * from "./hooks/useEvent"; -export * from "./hooks/useProfile"; +export * from "./useEvent"; +export * from "./useProfile"; +export * from "./useRect"; diff --git a/src/system/hooks/useEvent.ts b/src/system/useEvent.ts similarity index 97% rename from src/system/hooks/useEvent.ts rename to src/system/useEvent.ts index 5dc8da65..252b675d 100644 --- a/src/system/hooks/useEvent.ts +++ b/src/system/useEvent.ts @@ -2,7 +2,7 @@ import { type Result, type RichEvent, commands } from "@/commands.gen"; import type { NostrEvent } from "@/types"; import { useQuery } from "@tanstack/react-query"; import { nip19 } from "nostr-tools"; -import { LumeEvent } from "../event"; +import { LumeEvent } from "./event"; export function useEvent(id: string, repost?: string) { const { isLoading, isError, error, data } = useQuery({ diff --git a/src/system/hooks/useProfile.ts b/src/system/useProfile.ts similarity index 100% rename from src/system/hooks/useProfile.ts rename to src/system/useProfile.ts diff --git a/src/system/useRect.ts b/src/system/useRect.ts new file mode 100644 index 00000000..977b79ef --- /dev/null +++ b/src/system/useRect.ts @@ -0,0 +1,53 @@ +import { listen } from "@tauri-apps/api/event"; +import { useEffect, useRef, useState } from "react"; + +type MutableRefObject = { + current: T; +}; + +const useEffectInEvent = ( + event: K, + set: () => void, + useCapture?: boolean, +) => { + useEffect(() => { + if (set) { + set(); + window.addEventListener(event, set, useCapture); + + return () => window.removeEventListener(event, set, useCapture); + } + }, []); +}; + +const useTauriInEvent = (set: () => void) => { + useEffect(() => { + if (set) { + const unlisten = listen("column_scroll", () => { + set(); + }); + + return () => { + unlisten.then((f) => f()); + }; + } + }, []); +}; + +export const useRect = (): [ + DOMRect | undefined, + MutableRefObject, +] => { + const ref = useRef(null); + const [rect, setRect] = useState(); + + const set = (): void => { + setRect(ref.current?.getBoundingClientRect()); + }; + + useTauriInEvent(set); + useEffectInEvent("resize", set); + useEffectInEvent("scroll", set, true); + + return [rect, ref]; +};