wip: basic support multi windows

This commit is contained in:
2024-02-16 09:58:07 +07:00
parent a0d9a729dd
commit 296b11b7b8
17 changed files with 214 additions and 118 deletions

View File

@@ -1,5 +1,6 @@
import type { CurrentAccount, Event, Keys, Metadata } from "@lume/types";
import { invoke } from "@tauri-apps/api/core";
import { WebviewWindow } from "@tauri-apps/api/webview";
export class Ark {
public account: CurrentAccount;
@@ -59,21 +60,56 @@ export class Ark {
public async get_event(id: string) {
try {
const cmd: string = await invoke("get_event", { id });
const eventId: string = id
.replace("nostr:", "")
.split("'")[0]
.split(".")[0];
const cmd: string = await invoke("get_event", { id: eventId });
const event = JSON.parse(cmd) as Event;
return event;
} catch (e) {
return null;
}
}
public async get_text_events(limit: number, asOf?: number) {
public async get_text_events(limit: number, asOf?: number, dedup?: boolean) {
try {
let until: string = undefined;
if (asOf && asOf > 0) until = asOf.toString();
const cmd: Event[] = await invoke("get_text_events", { limit, until });
return cmd;
const seenIds = new Set<string>();
const dedupQueue = new Set<string>();
const nostrEvents: Event[] = await invoke("get_text_events", {
limit,
until,
});
if (dedup) {
for (const event of nostrEvents) {
const tags = event.tags
.filter((el) => el[0] === "e")
?.map((item) => item[1]);
if (tags.length) {
for (const tag of tags) {
if (seenIds.has(tag)) {
dedupQueue.add(event.id);
break;
}
seenIds.add(tag);
}
}
}
return nostrEvents
.filter((event) => !dedupQueue.has(event.id))
.sort((a, b) => b.created_at - a.created_at);
}
return nostrEvents.sort((a, b) => b.created_at - a.created_at);
} catch (e) {
return [];
}
@@ -170,9 +206,16 @@ export class Ark {
};
}
public async get_profile(id: string) {
public async get_profile(pubkey: string) {
try {
const id = pubkey
.replace("nostr:", "")
.split("'")[0]
.split(".")[0]
.split(",")[0]
.split("?")[0];
const cmd: Metadata = await invoke("get_profile", { id });
return cmd;
} catch {
return null;
@@ -202,4 +245,26 @@ export class Ark {
return false;
}
}
public open_thread(id: string) {
return new WebviewWindow(`event-${id}`, {
title: "Thread",
url: `/events/${id}`,
width: 600,
height: 800,
hiddenTitle: true,
titleBarStyle: "overlay",
});
}
public open_profile(pubkey: string) {
return new WebviewWindow(`user-${pubkey}`, {
title: "Profile",
url: `/users/${pubkey}`,
width: 600,
height: 800,
hiddenTitle: true,
titleBarStyle: "overlay",
});
}
}

View File

@@ -2,9 +2,12 @@ import { ReplyIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
export function NoteReply() {
const ark = useArk();
const event = useNoteContext();
const { t } = useTranslation();
return (
@@ -13,6 +16,7 @@ export function NoteReply() {
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => ark.open_thread(event.id)}
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
>
<ReplyIcon className="size-5 group-hover:text-blue-500" />

View File

@@ -110,7 +110,7 @@ export function NoteChild({
</div>
<User.Provider pubkey={data.pubkey}>
<User.Root>
<User.Avatar className="size-10 shrink-0 rounded-lg object-cover" />
<User.Avatar className="size-10 shrink-0 rounded-full object-cover" />
<div className="absolute left-2 top-2 inline-flex items-center gap-1.5 font-semibold leading-tight">
<User.Name className="max-w-[10rem] truncate" />
<div className="font-normal text-neutral-700 dark:text-neutral-300">

View File

@@ -32,10 +32,7 @@ export function NoteMenu() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button
type="button"
className="inline-flex size-6 items-center justify-center"
>
<button type="button">
<HorizontalDotsIcon className="size-4 hover:text-blue-500 dark:text-neutral-200" />
</button>
</DropdownMenu.Trigger>

View File

@@ -73,17 +73,17 @@ export function RepostNote({
return (
<Note.Root
className={cn(
"flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950",
"mb-3 flex flex-col gap-2 border-b border-neutral-100 pb-3 dark:border-neutral-900",
className,
)}
>
<User.Provider pubkey={event.pubkey}>
<User.Root className="flex h-14 gap-2 px-3">
<User.Root className="flex gap-3">
<div className="inline-flex w-10 shrink-0 items-center justify-center">
<RepostIcon className="h-5 w-5 text-blue-500" />
</div>
<div className="inline-flex items-center gap-2">
<User.Avatar className="size-6 shrink-0 rounded object-cover" />
<User.Avatar className="size-6 shrink-0 rounded-full object-cover" />
<div className="inline-flex items-baseline gap-1">
<User.Name className="font-medium text-neutral-900 dark:text-neutral-100" />
<span className="text-blue-500">{t("note.reposted")}</span>
@@ -92,18 +92,23 @@ export function RepostNote({
</User.Root>
</User.Provider>
<Note.Provider event={repostEvent}>
<div className="relative flex flex-col gap-2 px-3">
<div className="flex items-center justify-between">
<div className="flex flex-col gap-2">
<div className="flex items-start justify-between">
<Note.User className="flex-1 pr-2" />
<Note.Menu />
</div>
<Note.Content />
<div className="flex h-14 items-center justify-between">
<Note.Pin />
<div className="inline-flex items-center gap-4">
<Note.Reply />
<Note.Repost />
<Note.Zap />
<div className="flex gap-3">
<div className="size-10 shrink-0" />
<div className="min-w-0 flex-1">
<Note.Content />
<div className="mt-5 flex items-center justify-between">
<Note.Reaction />
<div className="inline-flex items-center gap-4">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
</div>
</div>
</div>
</div>

View File

@@ -24,8 +24,8 @@ export function TextNote({
<div className="flex gap-3">
<div className="size-10 shrink-0" />
<div className="min-w-0 flex-1">
<Note.Content className="mb-2" />
<Note.Thread className="mb-2" />
<Note.Content />
<div className="mt-5 flex items-center justify-between">
<Note.Reaction />
<div className="inline-flex items-center gap-4">

View File

@@ -20,12 +20,12 @@ export function NoteThread({ className }: { className?: string }) {
return (
<div className={cn("w-full", className)}>
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
{thread.replyEventId ? (
<Note.Child eventId={thread.replyEventId} />
) : null}
{thread.rootEventId ? (
<Note.Child eventId={thread.rootEventId} isRoot />
) : null}
{thread.replyEventId ? (
<Note.Child eventId={thread.replyEventId} />
) : null}
<div className="inline-flex items-center justify-between">
<a
href={`/events/${thread?.rootEventId || thread?.replyEventId}`}

View File

@@ -2,22 +2,22 @@ import { cn } from "@lume/utils";
import { useUserContext } from "./provider";
export function UserName({ className }: { className?: string }) {
const user = useUserContext();
const user = useUserContext();
if (!user.profile) {
return (
<div
className={cn(
"h-4 w-20 self-center bg-black/20 dark:bg-white/20 rounded animate-pulse",
className,
)}
/>
);
}
if (!user.profile) {
return (
<div
className={cn(
"mb-1 h-3 w-20 animate-pulse self-center rounded bg-black/20 dark:bg-white/20",
className,
)}
/>
);
}
return (
<div className={cn("max-w-[12rem] truncate", className)}>
{user.profile.display_name || user.profile.name || "Anon"}
</div>
);
return (
<div className={cn("max-w-[12rem] truncate", className)}>
{user.profile.display_name || user.profile.name || "Anon"}
</div>
);
}

View File

@@ -13,7 +13,6 @@ export function UserNip05({ className }: { className?: string }) {
queryFn: async () => {
if (!user.profile?.nip05) return false;
const verify = await ark.verify_nip05(user.pubkey, user.profile?.nip05);
console.log(verify);
return verify;
},
enabled: !!user.profile,
@@ -23,7 +22,7 @@ export function UserNip05({ className }: { className?: string }) {
return (
<div
className={cn(
"h-4 w-20 animate-pulse rounded bg-black/20 dark:bg-white/20",
"h-3 w-20 animate-pulse rounded bg-black/20 dark:bg-white/20",
className,
)}
/>

View File

@@ -2,10 +2,13 @@ import { cn, formatCreatedAt } from "@lume/utils";
import { useMemo } from "react";
export function UserTime({
time,
className,
}: { time: number; className?: string }) {
const createdAt = useMemo(() => formatCreatedAt(time), [time]);
time,
className,
}: {
time: number;
className?: string;
}) {
const createdAt = useMemo(() => formatCreatedAt(time), [time]);
return <div className={cn("", className)}>{createdAt}</div>;
return <div className={cn("leading-tight", className)}>{createdAt}</div>;
}