feat: auto resize mini webview when main webview resized
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { Spinner } from "@lume/ui";
|
||||
@@ -7,33 +7,50 @@ export function Col({
|
||||
column,
|
||||
account,
|
||||
isScroll,
|
||||
isResize,
|
||||
}: {
|
||||
column: LumeColumn;
|
||||
account: string;
|
||||
isScroll: boolean;
|
||||
isResize: boolean;
|
||||
}) {
|
||||
const webview = useRef<string | undefined>(undefined);
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const [webview, setWebview] = useState<string | undefined>(undefined);
|
||||
|
||||
const repositionWebview = async () => {
|
||||
if (webview.current && webview.current.length > 1) {
|
||||
if (webview && webview.length > 1) {
|
||||
const newRect = container.current.getBoundingClientRect();
|
||||
await invoke("reposition_column", {
|
||||
label: webview.current,
|
||||
label: webview,
|
||||
x: newRect.x,
|
||||
y: newRect.y,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isScroll) {
|
||||
repositionWebview();
|
||||
const resizeWebview = async () => {
|
||||
if (webview && webview.length > 1) {
|
||||
const newRect = container.current.getBoundingClientRect();
|
||||
await invoke("resize_column", {
|
||||
label: webview,
|
||||
width: newRect.width,
|
||||
height: newRect.height,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
resizeWebview();
|
||||
}, [isResize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isScroll) repositionWebview();
|
||||
}, [isScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (webview && webview.length > 1) return;
|
||||
|
||||
const rect = container.current.getBoundingClientRect();
|
||||
const windowLabel = `column-${column.label}`;
|
||||
const url =
|
||||
@@ -41,7 +58,7 @@ export function Col({
|
||||
`?account=${account}&label=${column.label}&name=${column.name}`;
|
||||
|
||||
// create new webview
|
||||
webview.current = await invoke("create_column", {
|
||||
const label: string = await invoke("create_column", {
|
||||
label: windowLabel,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
@@ -49,19 +66,19 @@ export function Col({
|
||||
height: rect.height,
|
||||
url,
|
||||
});
|
||||
|
||||
setWebview(label);
|
||||
})();
|
||||
|
||||
// close webview when unmounted
|
||||
return () => {
|
||||
if (webview.current && webview.current.length > 1) {
|
||||
if (webview && webview.length > 1) {
|
||||
invoke("close_column", {
|
||||
label: webview.current,
|
||||
}).then(() => {
|
||||
webview.current = undefined;
|
||||
label: webview,
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [webview]);
|
||||
|
||||
return (
|
||||
<div ref={container} className="h-full w-[440px] shrink-0 p-2">
|
||||
|
||||
@@ -104,6 +104,7 @@ export function RepostNote({
|
||||
<div className="-ml-1 inline-flex items-center gap-4">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
<Note.Pin />
|
||||
{settings.zap ? <Note.Zap /> : null}
|
||||
</div>
|
||||
<Note.Menu />
|
||||
|
||||
@@ -30,6 +30,7 @@ export function TextNote({
|
||||
<div className="-ml-1 inline-flex items-center gap-4">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
<Note.Pin />
|
||||
{settings.zap ? <Note.Zap /> : null}
|
||||
</div>
|
||||
<Note.Menu />
|
||||
|
||||
@@ -6,10 +6,11 @@ import { Spinner } from "@lume/ui";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { useDebounce, useDebouncedCallback } from "use-debounce";
|
||||
import { VList, VListHandle } from "virtua";
|
||||
|
||||
export const Route = createFileRoute("/$account/home")({
|
||||
@@ -30,14 +31,15 @@ export const Route = createFileRoute("/$account/home")({
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const vlistRef = useRef<VListHandle>(null);
|
||||
|
||||
const { account } = Route.useParams();
|
||||
const { ark, storedColumns } = Route.useRouteContext();
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
const [isScroll, setIsScroll] = useState(false);
|
||||
const [columns, setColumns] = useState(storedColumns);
|
||||
|
||||
const vlistRef = useRef<VListHandle>(null);
|
||||
const [isScroll, setIsScroll] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
|
||||
const goLeft = () => {
|
||||
const prevIndex = Math.max(selectedIndex - 1, 0);
|
||||
@@ -56,13 +58,23 @@ function Screen() {
|
||||
};
|
||||
|
||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||
// update col label
|
||||
column["label"] = column.label + "-" + nanoid();
|
||||
|
||||
setColumns((state) => [...state, column]);
|
||||
setSelectedIndex(columns.length + 1);
|
||||
// create new cols
|
||||
const cols = [...columns];
|
||||
const openColIndex = cols.findIndex((col) => col.label === "open");
|
||||
const newCols = [
|
||||
...cols.slice(0, openColIndex),
|
||||
column,
|
||||
...cols.slice(openColIndex),
|
||||
];
|
||||
|
||||
// scroll to the last column
|
||||
vlistRef.current.scrollToIndex(columns.length + 1, {
|
||||
setColumns(newCols);
|
||||
setSelectedIndex(cols.length - 1);
|
||||
|
||||
// scroll to the newest column
|
||||
vlistRef.current.scrollToIndex(cols.length - 1, {
|
||||
align: "end",
|
||||
});
|
||||
}, 150);
|
||||
@@ -77,24 +89,38 @@ function Screen() {
|
||||
});
|
||||
}, 150);
|
||||
|
||||
const startResize = useDebouncedCallback(
|
||||
() => setIsResize((prev) => !prev),
|
||||
150,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// save state
|
||||
ark.set_columns(columns);
|
||||
}, [columns]);
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: Awaited<ReturnType<typeof listen>> | undefined = undefined;
|
||||
let unlistenColEvent: Awaited<ReturnType<typeof listen>> | undefined =
|
||||
undefined;
|
||||
let unlistenWindowResize: Awaited<ReturnType<typeof listen>> | undefined =
|
||||
undefined;
|
||||
|
||||
(async () => {
|
||||
if (unlisten) return;
|
||||
unlisten = await listen<EventColumns>("columns", (data) => {
|
||||
if (unlistenColEvent && unlistenWindowResize) return;
|
||||
|
||||
unlistenColEvent = await listen<EventColumns>("columns", (data) => {
|
||||
if (data.payload.type === "add") add(data.payload.column);
|
||||
if (data.payload.type === "remove") remove(data.payload.label);
|
||||
});
|
||||
|
||||
unlistenWindowResize = await getCurrent().listen("tauri://resize", () => {
|
||||
startResize();
|
||||
});
|
||||
})();
|
||||
|
||||
return () => {
|
||||
if (unlisten) unlisten();
|
||||
if (unlistenColEvent) unlistenColEvent();
|
||||
if (unlistenWindowResize) unlistenWindowResize();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -106,12 +132,8 @@ function Screen() {
|
||||
tabIndex={-1}
|
||||
itemSize={440}
|
||||
overscan={3}
|
||||
onScroll={() => {
|
||||
setIsScroll(true);
|
||||
}}
|
||||
onScrollEnd={() => {
|
||||
setIsScroll(false);
|
||||
}}
|
||||
onScroll={() => setIsScroll(true)}
|
||||
onScrollEnd={() => setIsScroll(false)}
|
||||
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
|
||||
>
|
||||
{columns.map((column, index) => (
|
||||
@@ -120,6 +142,7 @@ function Screen() {
|
||||
column={column}
|
||||
account={account}
|
||||
isScroll={isScroll}
|
||||
isResize={isResize}
|
||||
/>
|
||||
))}
|
||||
</VList>
|
||||
|
||||
@@ -38,6 +38,7 @@ function Screen() {
|
||||
const { ark, settings } = Route.useRouteContext();
|
||||
|
||||
const [newSettings, setNewSettings] = useState<Settings>(settings);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const toggleNofitication = async () => {
|
||||
await requestPermission();
|
||||
@@ -70,11 +71,18 @@ function Screen() {
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
|
||||
// publish settings
|
||||
const eventId = await ark.set_settings(settings);
|
||||
|
||||
if (eventId) {
|
||||
console.log("event_id: ", eventId);
|
||||
navigate({ to: "/$account/home", params: { account }, replace: true });
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error(e);
|
||||
}
|
||||
};
|
||||
@@ -169,6 +177,7 @@ function Screen() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
disabled={loading}
|
||||
className="inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{t("global.continue")}
|
||||
|
||||
@@ -93,6 +93,8 @@ export function Screen() {
|
||||
}
|
||||
|
||||
function Empty() {
|
||||
const search = Route.useSearch();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col py-10 gap-10">
|
||||
<div className="text-center flex flex-col items-center justify-center">
|
||||
@@ -107,6 +109,7 @@ function Empty() {
|
||||
<div className="flex flex-col px-3 gap-2">
|
||||
<Link
|
||||
to="/global"
|
||||
search={search}
|
||||
className="h-11 w-full flex items-center hover:bg-neutral-200 text-sm font-medium dark:hover:bg-neutral-800 gap-2 bg-neutral-100 rounded-lg dark:bg-neutral-900 px-3"
|
||||
>
|
||||
<ArrowRightIcon className="size-5" />
|
||||
@@ -114,6 +117,7 @@ function Empty() {
|
||||
</Link>
|
||||
<Link
|
||||
to="/trending/notes"
|
||||
search={search}
|
||||
className="h-11 w-full flex items-center hover:bg-neutral-200 text-sm font-medium dark:hover:bg-neutral-800 gap-2 bg-neutral-100 rounded-lg dark:bg-neutral-900 px-3"
|
||||
>
|
||||
<ArrowRightIcon className="size-5" />
|
||||
@@ -121,6 +125,7 @@ function Empty() {
|
||||
</Link>
|
||||
<Link
|
||||
to="/trending/users"
|
||||
search={search}
|
||||
className="h-11 w-full flex items-center hover:bg-neutral-200 text-sm font-medium dark:hover:bg-neutral-800 gap-2 bg-neutral-100 rounded-lg dark:bg-neutral-900 px-3"
|
||||
>
|
||||
<ArrowRightIcon className="size-5" />
|
||||
|
||||
Reference in New Issue
Block a user