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 { LumeColumn } from "@lume/types";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { Spinner } from "@lume/ui";
|
import { Spinner } from "@lume/ui";
|
||||||
@@ -7,33 +7,50 @@ export function Col({
|
|||||||
column,
|
column,
|
||||||
account,
|
account,
|
||||||
isScroll,
|
isScroll,
|
||||||
|
isResize,
|
||||||
}: {
|
}: {
|
||||||
column: LumeColumn;
|
column: LumeColumn;
|
||||||
account: string;
|
account: string;
|
||||||
isScroll: boolean;
|
isScroll: boolean;
|
||||||
|
isResize: boolean;
|
||||||
}) {
|
}) {
|
||||||
const webview = useRef<string | undefined>(undefined);
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
const [webview, setWebview] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
const repositionWebview = async () => {
|
const repositionWebview = async () => {
|
||||||
if (webview.current && webview.current.length > 1) {
|
if (webview && webview.length > 1) {
|
||||||
const newRect = container.current.getBoundingClientRect();
|
const newRect = container.current.getBoundingClientRect();
|
||||||
await invoke("reposition_column", {
|
await invoke("reposition_column", {
|
||||||
label: webview.current,
|
label: webview,
|
||||||
x: newRect.x,
|
x: newRect.x,
|
||||||
y: newRect.y,
|
y: newRect.y,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const resizeWebview = async () => {
|
||||||
if (isScroll) {
|
if (webview && webview.length > 1) {
|
||||||
repositionWebview();
|
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]);
|
}, [isScroll]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
if (webview && webview.length > 1) return;
|
||||||
|
|
||||||
const rect = container.current.getBoundingClientRect();
|
const rect = container.current.getBoundingClientRect();
|
||||||
const windowLabel = `column-${column.label}`;
|
const windowLabel = `column-${column.label}`;
|
||||||
const url =
|
const url =
|
||||||
@@ -41,7 +58,7 @@ export function Col({
|
|||||||
`?account=${account}&label=${column.label}&name=${column.name}`;
|
`?account=${account}&label=${column.label}&name=${column.name}`;
|
||||||
|
|
||||||
// create new webview
|
// create new webview
|
||||||
webview.current = await invoke("create_column", {
|
const label: string = await invoke("create_column", {
|
||||||
label: windowLabel,
|
label: windowLabel,
|
||||||
x: rect.x,
|
x: rect.x,
|
||||||
y: rect.y,
|
y: rect.y,
|
||||||
@@ -49,19 +66,19 @@ export function Col({
|
|||||||
height: rect.height,
|
height: rect.height,
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setWebview(label);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// close webview when unmounted
|
// close webview when unmounted
|
||||||
return () => {
|
return () => {
|
||||||
if (webview.current && webview.current.length > 1) {
|
if (webview && webview.length > 1) {
|
||||||
invoke("close_column", {
|
invoke("close_column", {
|
||||||
label: webview.current,
|
label: webview,
|
||||||
}).then(() => {
|
|
||||||
webview.current = undefined;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [webview]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={container} className="h-full w-[440px] shrink-0 p-2">
|
<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">
|
<div className="-ml-1 inline-flex items-center gap-4">
|
||||||
<Note.Reply />
|
<Note.Reply />
|
||||||
<Note.Repost />
|
<Note.Repost />
|
||||||
|
<Note.Pin />
|
||||||
{settings.zap ? <Note.Zap /> : null}
|
{settings.zap ? <Note.Zap /> : null}
|
||||||
</div>
|
</div>
|
||||||
<Note.Menu />
|
<Note.Menu />
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export function TextNote({
|
|||||||
<div className="-ml-1 inline-flex items-center gap-4">
|
<div className="-ml-1 inline-flex items-center gap-4">
|
||||||
<Note.Reply />
|
<Note.Reply />
|
||||||
<Note.Repost />
|
<Note.Repost />
|
||||||
|
<Note.Pin />
|
||||||
{settings.zap ? <Note.Zap /> : null}
|
{settings.zap ? <Note.Zap /> : null}
|
||||||
</div>
|
</div>
|
||||||
<Note.Menu />
|
<Note.Menu />
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import { Spinner } from "@lume/ui";
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { resolveResource } from "@tauri-apps/api/path";
|
import { resolveResource } from "@tauri-apps/api/path";
|
||||||
|
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
||||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebounce, useDebouncedCallback } from "use-debounce";
|
||||||
import { VList, VListHandle } from "virtua";
|
import { VList, VListHandle } from "virtua";
|
||||||
|
|
||||||
export const Route = createFileRoute("/$account/home")({
|
export const Route = createFileRoute("/$account/home")({
|
||||||
@@ -30,14 +31,15 @@ export const Route = createFileRoute("/$account/home")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
|
const vlistRef = useRef<VListHandle>(null);
|
||||||
|
|
||||||
const { account } = Route.useParams();
|
const { account } = Route.useParams();
|
||||||
const { ark, storedColumns } = Route.useRouteContext();
|
const { ark, storedColumns } = Route.useRouteContext();
|
||||||
|
|
||||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||||
const [isScroll, setIsScroll] = useState(false);
|
|
||||||
const [columns, setColumns] = useState(storedColumns);
|
const [columns, setColumns] = useState(storedColumns);
|
||||||
|
const [isScroll, setIsScroll] = useState(false);
|
||||||
const vlistRef = useRef<VListHandle>(null);
|
const [isResize, setIsResize] = useState(false);
|
||||||
|
|
||||||
const goLeft = () => {
|
const goLeft = () => {
|
||||||
const prevIndex = Math.max(selectedIndex - 1, 0);
|
const prevIndex = Math.max(selectedIndex - 1, 0);
|
||||||
@@ -56,13 +58,23 @@ function Screen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||||
|
// update col label
|
||||||
column["label"] = column.label + "-" + nanoid();
|
column["label"] = column.label + "-" + nanoid();
|
||||||
|
|
||||||
setColumns((state) => [...state, column]);
|
// create new cols
|
||||||
setSelectedIndex(columns.length + 1);
|
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
|
setColumns(newCols);
|
||||||
vlistRef.current.scrollToIndex(columns.length + 1, {
|
setSelectedIndex(cols.length - 1);
|
||||||
|
|
||||||
|
// scroll to the newest column
|
||||||
|
vlistRef.current.scrollToIndex(cols.length - 1, {
|
||||||
align: "end",
|
align: "end",
|
||||||
});
|
});
|
||||||
}, 150);
|
}, 150);
|
||||||
@@ -77,24 +89,38 @@ function Screen() {
|
|||||||
});
|
});
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
|
const startResize = useDebouncedCallback(
|
||||||
|
() => setIsResize((prev) => !prev),
|
||||||
|
150,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// save state
|
// save state
|
||||||
ark.set_columns(columns);
|
ark.set_columns(columns);
|
||||||
}, [columns]);
|
}, [columns]);
|
||||||
|
|
||||||
useEffect(() => {
|
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 () => {
|
(async () => {
|
||||||
if (unlisten) return;
|
if (unlistenColEvent && unlistenWindowResize) return;
|
||||||
unlisten = await listen<EventColumns>("columns", (data) => {
|
|
||||||
|
unlistenColEvent = await listen<EventColumns>("columns", (data) => {
|
||||||
if (data.payload.type === "add") add(data.payload.column);
|
if (data.payload.type === "add") add(data.payload.column);
|
||||||
if (data.payload.type === "remove") remove(data.payload.label);
|
if (data.payload.type === "remove") remove(data.payload.label);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
unlistenWindowResize = await getCurrent().listen("tauri://resize", () => {
|
||||||
|
startResize();
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (unlisten) unlisten();
|
if (unlistenColEvent) unlistenColEvent();
|
||||||
|
if (unlistenWindowResize) unlistenWindowResize();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -106,12 +132,8 @@ function Screen() {
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
itemSize={440}
|
itemSize={440}
|
||||||
overscan={3}
|
overscan={3}
|
||||||
onScroll={() => {
|
onScroll={() => setIsScroll(true)}
|
||||||
setIsScroll(true);
|
onScrollEnd={() => setIsScroll(false)}
|
||||||
}}
|
|
||||||
onScrollEnd={() => {
|
|
||||||
setIsScroll(false);
|
|
||||||
}}
|
|
||||||
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
|
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
|
||||||
>
|
>
|
||||||
{columns.map((column, index) => (
|
{columns.map((column, index) => (
|
||||||
@@ -120,6 +142,7 @@ function Screen() {
|
|||||||
column={column}
|
column={column}
|
||||||
account={account}
|
account={account}
|
||||||
isScroll={isScroll}
|
isScroll={isScroll}
|
||||||
|
isResize={isResize}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</VList>
|
</VList>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ function Screen() {
|
|||||||
const { ark, settings } = Route.useRouteContext();
|
const { ark, settings } = Route.useRouteContext();
|
||||||
|
|
||||||
const [newSettings, setNewSettings] = useState<Settings>(settings);
|
const [newSettings, setNewSettings] = useState<Settings>(settings);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const toggleNofitication = async () => {
|
const toggleNofitication = async () => {
|
||||||
await requestPermission();
|
await requestPermission();
|
||||||
@@ -70,11 +71,18 @@ function Screen() {
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
try {
|
||||||
|
// start loading
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// publish settings
|
||||||
const eventId = await ark.set_settings(settings);
|
const eventId = await ark.set_settings(settings);
|
||||||
|
|
||||||
if (eventId) {
|
if (eventId) {
|
||||||
|
console.log("event_id: ", eventId);
|
||||||
navigate({ to: "/$account/home", params: { account }, replace: true });
|
navigate({ to: "/$account/home", params: { account }, replace: true });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
setLoading(false);
|
||||||
toast.error(e);
|
toast.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -169,6 +177,7 @@ function Screen() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={submit}
|
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"
|
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")}
|
{t("global.continue")}
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ export function Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Empty() {
|
function Empty() {
|
||||||
|
const search = Route.useSearch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col py-10 gap-10">
|
<div className="flex flex-col py-10 gap-10">
|
||||||
<div className="text-center flex flex-col items-center justify-center">
|
<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">
|
<div className="flex flex-col px-3 gap-2">
|
||||||
<Link
|
<Link
|
||||||
to="/global"
|
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"
|
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" />
|
<ArrowRightIcon className="size-5" />
|
||||||
@@ -114,6 +117,7 @@ function Empty() {
|
|||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/trending/notes"
|
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"
|
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" />
|
<ArrowRightIcon className="size-5" />
|
||||||
@@ -121,6 +125,7 @@ function Empty() {
|
|||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/trending/users"
|
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"
|
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" />
|
<ArrowRightIcon className="size-5" />
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { ArrowDownIcon } from "@lume/icons";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useNoteContext } from "../provider";
|
|
||||||
import { cn } from "@lume/utils";
|
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
|
||||||
import { Spinner } from "../../spinner";
|
|
||||||
|
|
||||||
export function NoteDownvote() {
|
|
||||||
const ark = useRouteContext({ strict: false });
|
|
||||||
const event = useNoteContext();
|
|
||||||
|
|
||||||
const [t] = useTranslation();
|
|
||||||
const [reaction, setReaction] = useState<"-" | null>(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const down = async () => {
|
|
||||||
// start loading
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const res = await ark.downvote(event.id, event.pubkey);
|
|
||||||
if (res) setReaction("-");
|
|
||||||
|
|
||||||
// stop loading
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip.Provider>
|
|
||||||
<Tooltip.Root delayDuration={150}>
|
|
||||||
<Tooltip.Trigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={down}
|
|
||||||
disabled={!!reaction || loading}
|
|
||||||
className={cn(
|
|
||||||
"inline-flex size-7 items-center justify-center rounded-full",
|
|
||||||
reaction === "-"
|
|
||||||
? "bg-blue-500 text-white"
|
|
||||||
: "bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<Spinner className="size-4" />
|
|
||||||
) : (
|
|
||||||
<ArrowDownIcon className="size-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</Tooltip.Trigger>
|
|
||||||
<Tooltip.Portal>
|
|
||||||
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
|
|
||||||
{t("note.buttons.downvote")}
|
|
||||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
|
||||||
</Tooltip.Content>
|
|
||||||
</Tooltip.Portal>
|
|
||||||
</Tooltip.Root>
|
|
||||||
</Tooltip.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { PinIcon } from "@lume/icons";
|
import { LinkIcon } from "@lume/icons";
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useRouteContext } from "@tanstack/react-router";
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
|
|
||||||
export function NotePin() {
|
export function NotePin() {
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
const { t } = useTranslation();
|
const { ark } = useRouteContext({ strict: false });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Provider>
|
<Tooltip.Provider>
|
||||||
@@ -13,15 +13,15 @@ export function NotePin() {
|
|||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center gap-2 pl-2 pr-3 text-sm font-medium rounded-full h-7 w-max bg-neutral-100 hover:bg-neutral-200 dark:hover:bg-neutral-800 dark:bg-neutral-900"
|
onClick={() => ark.open_thread(event.id)}
|
||||||
|
className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
|
||||||
>
|
>
|
||||||
<PinIcon className="size-4" />
|
<LinkIcon className="size-5 group-hover:text-blue-500" />
|
||||||
{t("note.buttons.pin")}
|
|
||||||
</button>
|
</button>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Portal>
|
<Tooltip.Portal>
|
||||||
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
||||||
{t("note.buttons.pinTooltip")}
|
Open as new window
|
||||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Portal>
|
</Tooltip.Portal>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { NoteUpvote } from "./upvote";
|
|
||||||
import { NoteDownvote } from "./downvote";
|
|
||||||
|
|
||||||
export function NoteReaction() {
|
|
||||||
return (
|
|
||||||
<div className="inline-flex items-center gap-2">
|
|
||||||
<NoteUpvote />
|
|
||||||
<NoteDownvote />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import { ReplyIcon } from "@lume/icons";
|
import { ReplyIcon } from "@lume/icons";
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
import { useRouteContext } from "@tanstack/react-router";
|
||||||
|
|
||||||
export function NoteReply() {
|
export function NoteReply() {
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
|
|
||||||
const { ark } = useRouteContext({ strict: false });
|
const { ark } = useRouteContext({ strict: false });
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Provider>
|
<Tooltip.Provider>
|
||||||
@@ -24,7 +21,7 @@ export function NoteReply() {
|
|||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Portal>
|
<Tooltip.Portal>
|
||||||
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
|
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
|
||||||
{t("note.menu.viewThread")}
|
Reply
|
||||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Portal>
|
</Tooltip.Portal>
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { ArrowUpIcon } from "@lume/icons";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useNoteContext } from "../provider";
|
|
||||||
import { cn } from "@lume/utils";
|
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
|
||||||
import { Spinner } from "../../spinner";
|
|
||||||
|
|
||||||
export function NoteUpvote() {
|
|
||||||
const { ark } = useRouteContext({ strict: false });
|
|
||||||
const event = useNoteContext();
|
|
||||||
|
|
||||||
const [t] = useTranslation();
|
|
||||||
const [reaction, setReaction] = useState<"+" | null>(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const up = async () => {
|
|
||||||
// start loading
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const res = await ark.upvote(event.id, event.pubkey);
|
|
||||||
if (res) setReaction("+");
|
|
||||||
|
|
||||||
// stop loading
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip.Provider>
|
|
||||||
<Tooltip.Root delayDuration={150}>
|
|
||||||
<Tooltip.Trigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={up}
|
|
||||||
disabled={!!reaction || loading}
|
|
||||||
className={cn(
|
|
||||||
"inline-flex size-7 items-center justify-center rounded-full",
|
|
||||||
reaction === "+"
|
|
||||||
? "bg-blue-500 text-white"
|
|
||||||
: "bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<Spinner className="size-4" />
|
|
||||||
) : (
|
|
||||||
<ArrowUpIcon className="size-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</Tooltip.Trigger>
|
|
||||||
<Tooltip.Portal>
|
|
||||||
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
|
|
||||||
{t("note.buttons.upvote")}
|
|
||||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
|
||||||
</Tooltip.Content>
|
|
||||||
</Tooltip.Portal>
|
|
||||||
</Tooltip.Root>
|
|
||||||
</Tooltip.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { NotePin } from "./buttons/pin";
|
import { NotePin } from "./buttons/pin";
|
||||||
import { NoteReaction } from "./buttons/reaction";
|
|
||||||
import { NoteReply } from "./buttons/reply";
|
import { NoteReply } from "./buttons/reply";
|
||||||
import { NoteRepost } from "./buttons/repost";
|
import { NoteRepost } from "./buttons/repost";
|
||||||
import { NoteZap } from "./buttons/zap";
|
import { NoteZap } from "./buttons/zap";
|
||||||
@@ -18,7 +17,7 @@ export const Note = {
|
|||||||
Menu: NoteMenu,
|
Menu: NoteMenu,
|
||||||
Reply: NoteReply,
|
Reply: NoteReply,
|
||||||
Repost: NoteRepost,
|
Repost: NoteRepost,
|
||||||
Reaction: NoteReaction,
|
Pin: NotePin,
|
||||||
Content: NoteContent,
|
Content: NoteContent,
|
||||||
Zap: NoteZap,
|
Zap: NoteZap,
|
||||||
Pin: NotePin,
|
Pin: NotePin,
|
||||||
|
|||||||
@@ -66,3 +66,22 @@ pub fn reposition_column(
|
|||||||
None => Err("Webview not found".into()),
|
None => Err("Webview not found".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn resize_column(
|
||||||
|
label: &str,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match app_handle.get_webview(label) {
|
||||||
|
Some(webview) => {
|
||||||
|
if let Ok(_) = webview.set_size(LogicalSize::new(width, height)) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Reposition column failed".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err("Webview not found".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ fn main() {
|
|||||||
commands::opg::fetch_opg,
|
commands::opg::fetch_opg,
|
||||||
commands::window::create_column,
|
commands::window::create_column,
|
||||||
commands::window::close_column,
|
commands::window::close_column,
|
||||||
commands::window::reposition_column
|
commands::window::reposition_column,
|
||||||
|
commands::window::resize_column
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application")
|
.expect("error while running tauri application")
|
||||||
|
|||||||
Reference in New Issue
Block a user