1
0
mirror of https://github.com/lumehq/lume.git synced 2025-04-09 04:18:24 +02:00

feat: move column manager to rust

This commit is contained in:
reya 2024-03-19 14:56:24 +07:00
parent ea5120e2f0
commit 7fabf949c6
4 changed files with 106 additions and 74 deletions

@ -1,13 +1,7 @@
import { useCallback, useEffect, useMemo, useRef } from "react";
import {
LogicalPosition,
LogicalSize,
getCurrent,
} from "@tauri-apps/api/window";
import { Webview } from "@tauri-apps/api/webview";
import { useEffect, useMemo, useRef, useState } from "react";
import { getCurrent } from "@tauri-apps/api/window";
import { LumeColumn } from "@lume/types";
import { useDebouncedCallback } from "use-debounce";
import { type UnlistenFn } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
export function Col({
column,
@ -18,66 +12,62 @@ export function Col({
account: string;
isScroll: boolean;
}) {
const mainWindow = useMemo(() => getCurrent(), []);
const childWindow = useRef<Webview>(null);
const window = useMemo(() => getCurrent(), []);
const container = useRef<HTMLDivElement>(null);
const initialRect = useRef<DOMRect>(null);
const unlisten = useRef<UnlistenFn>(null);
const handleResize = useDebouncedCallback(() => {
if (!childWindow.current) return;
const newRect = container.current.getBoundingClientRect();
if (initialRect.current.height !== newRect.height) {
childWindow.current.setSize(
new LogicalSize(newRect.width, newRect.height),
);
}
}, 500);
const trackResize = useCallback(async () => {
unlisten.current = await mainWindow.onResized(() => {
handleResize();
});
}, []);
useEffect(() => {
if (!childWindow.current) return;
if (isScroll) {
const newRect = container.current.getBoundingClientRect();
childWindow.current.setPosition(
new LogicalPosition(newRect.x, newRect.y),
);
}
}, [isScroll]);
useEffect(() => {
if (!mainWindow) return;
if (!container.current) return;
if (childWindow.current) return;
const [webview, setWebview] = useState("");
const createWebview = async () => {
const rect = container.current.getBoundingClientRect();
const name = `column-${column.name.toLowerCase().replace(/\W/g, "")}`;
const url = column.content + `?account=${account}&name=${column.name}`;
// create new webview
initialRect.current = rect;
childWindow.current = new Webview(mainWindow, name, {
url,
const label: string = await invoke("create_column", {
label: name,
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
transparent: true,
userAgent: "Lume/4.0",
url,
});
// track window resize event
trackResize();
setWebview(label);
};
const closeWebview = async () => {
await invoke("close_column", {
label: webview,
});
};
const repositionWebview = async () => {
const newRect = container.current.getBoundingClientRect();
await invoke("reposition_column", {
label: webview,
x: newRect.x,
y: newRect.y,
});
};
useEffect(() => {
if (isScroll) {
repositionWebview();
}
}, [isScroll]);
useEffect(() => {
if (!window) return;
if (!container.current) return;
// create webview for current column
createWebview();
// close webview when unmounted
return () => {
if (unlisten.current) unlisten.current();
if (childWindow.current) childWindow.current.close();
closeWebview();
};
}, []);
}, [window]);
return <div ref={container} className="h-full w-[440px] shrink-0 p-2" />;
}

@ -1,31 +1,33 @@
import { Col } from "@/components/col";
import { Toolbar } from "@/components/toolbar";
import { LoaderIcon } from "@lume/icons";
import { EventColumns, LumeColumn } from "@lume/types";
import { createFileRoute } from "@tanstack/react-router";
import { useRef, useState } from "react";
import { UnlistenFn } from "@tauri-apps/api/event";
import { getCurrent } from "@tauri-apps/api/window";
import { useEffect, useRef, useState } from "react";
import { VList, VListHandle } from "virtua";
export const Route = createFileRoute("/$account/home")({
component: Screen,
pendingComponent: Pending,
loader: async () => {
const columns = [
{ name: "Newsfeed", content: "/newsfeed" },
{ name: "Lume Store", content: "/store/official" },
];
return columns;
},
});
const COLS: LumeColumn[] = [
{ id: 1, name: "Newsfeed", content: "/newsfeed" },
{ id: 2, name: "Lume Store", content: "/store/official" },
];
function Screen() {
const data = Route.useLoaderData();
const search = Route.useSearch();
const vlistRef = useRef<VListHandle>(null);
const unlisten = useRef<UnlistenFn>(null);
const [columns, setColumns] = useState(COLS);
const [selectedIndex, setSelectedIndex] = useState(-1);
const [isScroll, setIsScroll] = useState(false);
const moveLeft = () => {
const goLeft = () => {
const prevIndex = Math.max(selectedIndex - 1, 0);
setSelectedIndex(prevIndex);
vlistRef.current.scrollToIndex(prevIndex, {
@ -33,14 +35,44 @@ function Screen() {
});
};
const moveRight = () => {
const nextIndex = Math.min(selectedIndex + 1, data.length - 1);
const goRight = () => {
const nextIndex = Math.min(selectedIndex + 1, columns.length - 1);
setSelectedIndex(nextIndex);
vlistRef.current.scrollToIndex(nextIndex, {
align: "end",
});
};
const add = async (column: LumeColumn) => {
setColumns((prev) => [...prev, column]);
vlistRef?.current.scrollToIndex(columns.length);
};
const remove = async (id: number) => {
setColumns((prev) => prev.filter((t) => t.id !== id));
};
useEffect(() => {
async function listenUpdateColumn() {
const mainWindow = getCurrent();
unlisten.current = await mainWindow.listen<EventColumns>(
"columns",
(data) => {
if (data.payload.type === "add") add(data.payload.column);
if (data.payload.type === "remove") remove(data.payload.id);
},
);
}
// listen for column changes
listenUpdateColumn();
// clean up
return () => {
if (unlisten.current) unlisten.current();
};
}, []);
return (
<div className="h-full w-full">
<VList
@ -54,13 +86,13 @@ function Screen() {
case "ArrowUp":
case "ArrowLeft": {
e.preventDefault();
moveLeft();
goLeft();
break;
}
case "ArrowDown":
case "ArrowRight": {
e.preventDefault();
moveRight();
goRight();
break;
}
}
@ -73,9 +105,9 @@ function Screen() {
}}
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
>
{data.map((column, index) => (
{columns.map((column, index) => (
<Col
key={column.name + index}
key={column.id + index}
column={column}
// @ts-ignore, yolo !!!
account={search.acccount}
@ -83,7 +115,7 @@ function Screen() {
/>
))}
</VList>
<Toolbar moveLeft={moveLeft} moveRight={moveRight} />
<Toolbar moveLeft={goLeft} moveRight={goRight} />
</div>
);
}

@ -5,6 +5,13 @@ export const Route = createFileRoute("/store/official")({
});
function Screen() {
/*
const add = async (column: LumeColumn) => {
const mainWindow = getCurrent();
await mainWindow.emit("columns", { type: "add", column });
};
*/
return (
<div className="flex flex-col gap-3 px-3 pt-3">
<div className="relative h-[200px] w-full overflow-hidden rounded-xl bg-gradient-to-tr from-orange-100 to-blue-200 px-3 pt-3">

@ -1,3 +1,4 @@
import { GroupFeedsIcon, LaurelIcon } from "@lume/icons";
import { Column } from "@lume/ui";
import { cn } from "@lume/utils";
import { Link } from "@tanstack/react-router";
@ -11,17 +12,18 @@ function Screen() {
return (
<Column.Root>
<Column.Content>
<div className="flex h-14 shrink-0 items-center gap-3 border-b border-neutral-100 px-3 dark:border-neutral-900">
<div className="flex h-14 shrink-0 items-center gap-2 border-b border-neutral-100 px-3 dark:border-neutral-900">
<Link to="/store/official">
{({ isActive }) => (
<div
className={cn(
"inline-flex h-8 w-max items-center justify-center rounded-full px-6 text-sm",
"inline-flex h-8 w-max items-center justify-center gap-2 rounded-full px-6 text-sm font-medium",
isActive
? "bg-neutral-100 font-medium dark:bg-neutral-900"
? "bg-neutral-100 dark:bg-neutral-900"
: "opacity-50",
)}
>
<LaurelIcon className="size-5" />
Official
</div>
)}
@ -30,12 +32,13 @@ function Screen() {
{({ isActive }) => (
<div
className={cn(
"inline-flex h-8 w-max items-center justify-center rounded-full px-6 text-sm",
"inline-flex h-8 w-max items-center justify-center gap-2 rounded-full px-6 text-sm font-medium",
isActive
? "bg-neutral-100 font-medium dark:bg-neutral-900"
? "bg-neutral-100 dark:bg-neutral-900"
: "opacity-50",
)}
>
<GroupFeedsIcon className="size-5" />
Community
</div>
)}