refactor: use specta for commands (#192)

* feat: add tauri-specta

* refactor: system library

* chore: format
This commit is contained in:
雨宮蓮
2024-05-25 15:21:40 +07:00
committed by GitHub
parent 7449000f5f
commit bba324ea53
92 changed files with 2164 additions and 2071 deletions

View File

@@ -1,6 +1,6 @@
import { NostrQuery } from "@lume/system";
import { Spinner } from "@lume/ui";
import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import {
type Dispatch,
type ReactNode,
@@ -18,21 +18,17 @@ export function AvatarUploader({
children: ReactNode;
className?: string;
}) {
const { ark } = useRouteContext({ strict: false });
const [loading, setLoading] = useState(false);
const uploadAvatar = async () => {
// start loading
setLoading(true);
try {
const image = await ark.upload();
setLoading(true);
const image = await NostrQuery.upload();
setPicture(image);
} catch (e) {
setLoading(false);
toast.error(String(e));
}
// stop loading
setLoading(false);
};
return (

View File

@@ -1,16 +1,15 @@
import { User } from "@/components/user";
import { NostrAccount } from "@lume/system";
import { getBitcoinDisplayValues } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import { useEffect, useMemo, useState } from "react";
export function Balance({ account }: { account: string }) {
const { ark } = useRouteContext({ strict: false });
const [balance, setBalance] = useState(0);
const value = useMemo(() => getBitcoinDisplayValues(balance), [balance]);
useEffect(() => {
async function getBalance() {
const val = await ark.get_balance();
const val = await NostrAccount.getBalance();
setBalance(val);
}

View File

@@ -1,18 +1,17 @@
import { ThreadIcon } from "@lume/icons";
import type { Event } from "@lume/types";
import type { NostrEvent } from "@lume/types";
import { Note } from "@/components/note";
import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import { LumeEvent } from "@lume/system";
export function Conversation({
event,
className,
}: {
event: Event;
event: NostrEvent;
className?: string;
}) {
const { ark } = useRouteContext({ strict: false });
const thread = ark.get_thread(event.tags);
const thread = LumeEvent.getEventThread(event.tags);
return (
<Note.Provider event={event}>

View File

@@ -4,9 +4,7 @@ import { User } from "../user";
export function NoteActivity({ className }: { className?: string }) {
const event = useNoteContext();
const mentions = event.tags
.filter((tag) => tag[0] === "p")
.map((tag) => tag[1]);
const mentions = event.mentions;
return (
<div className={cn("-mt-3 mb-2", className)}>

View File

@@ -1,11 +1,10 @@
import { VisitIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useRouteContext } from "@tanstack/react-router";
import { useNoteContext } from "../provider";
import { LumeWindow } from "@lume/system";
export function NoteOpenThread() {
const event = useNoteContext();
const { ark } = useRouteContext({ strict: false });
return (
<Tooltip.Provider>
@@ -13,7 +12,7 @@ export function NoteOpenThread() {
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => ark.open_event(event)}
onClick={() => LumeWindow.openEvent(event)}
className="group inline-flex h-7 w-14 bg-neutral-100 dark:bg-white/10 rounded-full items-center justify-center text-sm font-medium text-neutral-800 dark:text-neutral-200 hover:text-blue-500 hover:bg-neutral-200 dark:hover:bg-white/20"
>
<VisitIcon className="shrink-0 size-4" />

View File

@@ -1,12 +1,11 @@
import { ReplyIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useRouteContext } from "@tanstack/react-router";
import { useNoteContext } from "../provider";
import { cn } from "@lume/utils";
import { LumeWindow } from "@lume/system";
export function NoteReply({ large = false }: { large?: boolean }) {
const event = useNoteContext();
const { ark } = useRouteContext({ strict: false });
return (
<Tooltip.Provider>
@@ -14,7 +13,7 @@ export function NoteReply({ large = false }: { large?: boolean }) {
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => ark.open_editor(event.id)}
onClick={() => LumeWindow.openEditor(event.id)}
className={cn(
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
large

View File

@@ -2,15 +2,14 @@ import { QuoteIcon, RepostIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useRouteContext } from "@tanstack/react-router";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Spinner } from "@lume/ui";
import { useNoteContext } from "../provider";
import { LumeWindow } from "@lume/system";
export function NoteRepost({ large = false }: { large?: boolean }) {
const { ark } = useRouteContext({ strict: false });
const event = useNoteContext();
const [t] = useTranslation();
@@ -23,7 +22,7 @@ export function NoteRepost({ large = false }: { large?: boolean }) {
setLoading(true);
// repost
await ark.repost(event.id, event.pubkey);
await event.repost();
// update state
setLoading(false);
@@ -86,7 +85,7 @@ export function NoteRepost({ large = false }: { large?: boolean }) {
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => ark.open_editor(event.id, true)}
onClick={() => LumeWindow.openEditor(event.id, true)}
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-white hover:bg-neutral-900 focus:outline-none dark:text-black dark:hover:bg-neutral-100"
>
<QuoteIcon className="size-4" />

View File

@@ -1,33 +1,19 @@
import { ZapIcon } from "@lume/icons";
import { useRouteContext, useSearch } from "@tanstack/react-router";
import { toast } from "sonner";
import { useRouteContext } from "@tanstack/react-router";
import { useNoteContext } from "../provider";
import { cn } from "@lume/utils";
import { LumeWindow } from "@lume/system";
export function NoteZap({ large = false }: { large?: boolean }) {
const event = useNoteContext();
const { ark, settings } = useRouteContext({ strict: false });
const { account } = useSearch({ strict: false });
const zap = async () => {
try {
const nwc = await ark.load_nwc();
if (!nwc) {
ark.open_nwc();
} else {
ark.open_zap(event.id, event.pubkey, account);
}
} catch (e) {
toast.error(String(e));
}
};
const { settings } = useRouteContext({ strict: false });
if (!settings.zap) return null;
return (
<button
type="button"
onClick={() => zap()}
onClick={() => LumeWindow.openZap(event.id, event.pubkey)}
className={cn(
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
large

View File

@@ -1,4 +1,4 @@
import { useEvent } from "@lume/ark";
import { useEvent } from "@lume/system";
import { cn } from "@lume/utils";
import { Note } from ".";
import { InfoIcon } from "@lume/icons";

View File

@@ -1,6 +1,5 @@
import { useEvent } from "@lume/ark";
import { LumeWindow, useEvent } from "@lume/system";
import { LinkIcon } from "@lume/icons";
import { useRouteContext } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
import { cn } from "@lume/utils";
import { User } from "@/components/user";
@@ -13,7 +12,6 @@ export function MentionNote({
eventId: string;
openable?: boolean;
}) {
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
const { isLoading, isError, data } = useEvent(eventId);
@@ -62,9 +60,9 @@ export function MentionNote({
type="button"
onClick={(e) => {
e.stopPropagation();
ark.open_event_id(data.id);
LumeWindow.openEvent(data);
}}
className="z-10 h-7 w-28 inline-flex items-center justify-center gap-1 text-sm bg-neutral-100 dark:bg-white/10 rounded-full text-neutral-600 hover:text-blue-500 dark:text-neutral-400"
className="z-10 h-7 w-28 inline-flex items-center justify-center gap-1 text-sm bg-black/10 dark:bg-white/10 rounded-full text-neutral-600 hover:text-blue-500 dark:text-neutral-400"
>
View post
<LinkIcon className="size-4" />

View File

@@ -1,24 +1,20 @@
import { useProfile } from "@lume/ark";
import { LumeWindow, useProfile } from "@lume/system";
import { displayNpub } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
export function MentionUser({ pubkey }: { pubkey: string }) {
const { ark } = useRouteContext({ strict: false });
const { isLoading, isError, profile } = useProfile(pubkey);
return (
<button
type="button"
onClick={() => ark.open_profile(pubkey)}
onClick={() => LumeWindow.openProfile(pubkey)}
className="break-words text-start text-blue-500 hover:text-blue-600"
>
{isLoading
? "@anon"
: isError
? displayNpub(pubkey, 16)
: `@${
profile?.name || profile?.display_name || profile?.name || "anon"
}`}
: `@${profile?.name || profile?.display_name || "anon"}`}
</button>
);
}

View File

@@ -1,37 +1,28 @@
import { HorizontalDotsIcon } from "@lume/icons";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { useRouteContext } from "@tanstack/react-router";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useNoteContext } from "./provider";
import { LumeWindow } from "@lume/system";
export function NoteMenu() {
const { t } = useTranslation();
const event = useNoteContext();
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
const copyID = async () => {
await writeText(await ark.event_to_bech32(event.id, [""]));
toast.success("Copied");
await writeText(await event.idAsBech32());
};
const copyRaw = async () => {
await writeText(JSON.stringify(event));
toast.success("Copied");
};
const copyNpub = async () => {
await writeText(await ark.user_to_bech32(event.pubkey, [""]));
toast.success("Copied");
await writeText(await event.pubkeyAsBech32());
};
const copyLink = async () => {
await writeText(
`https://njump.me/${await ark.event_to_bech32(event.id, [""])}`,
);
toast.success("Copied");
await writeText(`https://njump.me/${await event.idAsBech32()}`);
};
return (
@@ -49,7 +40,7 @@ export function NoteMenu() {
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => ark.open_event(event)}
onClick={() => LumeWindow.openEvent(event)}
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-white hover:bg-neutral-900 focus:outline-none dark:text-black dark:hover:bg-neutral-100"
>
{t("note.menu.viewThread")}
@@ -84,7 +75,7 @@ export function NoteMenu() {
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
onClick={() => ark.open_profile(event.pubkey)}
onClick={() => LumeWindow.openProfile(event.pubkey)}
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-white hover:bg-neutral-900 focus:outline-none dark:text-black dark:hover:bg-neutral-100"
>
{t("note.menu.viewAuthor")}

View File

@@ -1,19 +1,25 @@
import type { Event } from "@lume/types";
import { LumeEvent } from "@lume/system";
import type { NostrEvent } from "@lume/types";
import { type ReactNode, createContext, useContext } from "react";
const EventContext = createContext<Event>(null);
const NoteContext = createContext<LumeEvent>(null);
export function NoteProvider({
event,
children,
}: { event: Event; children: ReactNode }) {
}: {
event: NostrEvent;
children: ReactNode;
}) {
const lumeEvent = new LumeEvent(event);
return (
<EventContext.Provider value={event}>{children}</EventContext.Provider>
<NoteContext.Provider value={lumeEvent}>{children}</NoteContext.Provider>
);
}
export function useNoteContext() {
const context = useContext(EventContext);
const context = useContext(NoteContext);
if (!context) {
throw new Error("Please import Note Provider to use useNoteContext() hook");
}

View File

@@ -1,11 +1,10 @@
import { cn } from "@lume/utils";
import * as HoverCard from "@radix-ui/react-hover-card";
import { useRouteContext } from "@tanstack/react-router";
import { User } from "../user";
import { useNoteContext } from "./provider";
import { LumeWindow } from "@lume/system";
export function NoteUser({ className }: { className?: string }) {
const { ark } = useRouteContext({ strict: false });
const event = useNoteContext();
return (
@@ -46,7 +45,7 @@ export function NoteUser({ className }: { className?: string }) {
</div>
<User.About className="line-clamp-3 text-sm text-white dark:text-neutral-900" />
<button
onClick={() => ark.open_profile(event.pubkey)}
onClick={() => LumeWindow.openProfile(event.pubkey)}
className="mt-2 inline-flex h-9 w-full items-center justify-center rounded-lg bg-white text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-neutral-200"
>
View profile

View File

@@ -1,4 +1,4 @@
import type { Event } from "@lume/types";
import type { NostrEvent } from "@lume/types";
import { Note } from "@/components/note";
import { cn } from "@lume/utils";
@@ -6,7 +6,7 @@ export function Notification({
event,
className,
}: {
event: Event;
event: NostrEvent;
className?: string;
}) {
return (

View File

@@ -1,5 +1,5 @@
import { QuoteIcon } from "@lume/icons";
import type { Event } from "@lume/types";
import type { NostrEvent } from "@lume/types";
import { Note } from "@/components/note";
import { cn } from "@lume/utils";
@@ -7,7 +7,7 @@ export function Quote({
event,
className,
}: {
event: Event;
event: NostrEvent;
className?: string;
}) {
const quoteEventId = event.tags.find(

View File

@@ -1,19 +1,18 @@
import type { Event } from "@lume/types";
import { Spinner } from "@lume/ui";
import { Note } from "@/components/note";
import { User } from "@/components/user";
import { cn } from "@lume/utils";
import { useQuery } from "@tanstack/react-query";
import { useRouteContext } from "@tanstack/react-router";
import { NostrEvent } from "@lume/types";
import { NostrQuery } from "@lume/system";
export function RepostNote({
event,
className,
}: {
event: Event;
event: NostrEvent;
className?: string;
}) {
const { ark } = useRouteContext({ strict: false });
const {
isLoading,
isError,
@@ -23,12 +22,12 @@ export function RepostNote({
queryFn: async () => {
try {
if (event.content.length > 50) {
const embed: Event = JSON.parse(event.content);
const embed: NostrEvent = JSON.parse(event.content);
return embed;
}
const id = event.tags.find((el) => el[0] === "e")?.[1];
const repostEvent = await ark.get_event(id);
const repostEvent = await NostrQuery.getEvent(id);
return repostEvent;
} catch (e) {

View File

@@ -1,4 +1,4 @@
import type { Event } from "@lume/types";
import type { NostrEvent } from "@lume/types";
import { cn } from "@lume/utils";
import { Note } from "@/components/note";
@@ -6,7 +6,7 @@ export function TextNote({
event,
className,
}: {
event: Event;
event: NostrEvent;
className?: string;
}) {
return (

View File

@@ -1,9 +1,9 @@
import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Spinner } from "@lume/ui";
import { useUserContext } from "./provider";
import { NostrAccount } from "@lume/system";
export function UserFollowButton({
simple = false,
@@ -12,7 +12,6 @@ export function UserFollowButton({
simple?: boolean;
className?: string;
}) {
const { ark } = useRouteContext({ strict: false });
const user = useUserContext();
const [t] = useTranslation();
@@ -22,10 +21,10 @@ export function UserFollowButton({
const toggleFollow = async () => {
setLoading(true);
if (!followed) {
const add = await ark.follow(user.pubkey);
const add = await NostrAccount.follow(user.pubkey, user.profile?.name);
if (add) setFollowed(true);
} else {
const remove = await ark.unfollow(user.pubkey);
const remove = await NostrAccount.unfollow(user.pubkey);
if (remove) setFollowed(false);
}
setLoading(false);
@@ -35,7 +34,7 @@ export function UserFollowButton({
async function status() {
setLoading(true);
const contacts = await ark.get_contact_list();
const contacts = await NostrAccount.getContactList();
if (contacts?.includes(user.pubkey)) {
setFollowed(true);
}

View File

@@ -2,21 +2,21 @@ import { VerifiedIcon } from "@lume/icons";
import { displayLongHandle, displayNpub } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useQuery } from "@tanstack/react-query";
import { useRouteContext } from "@tanstack/react-router";
import { useUserContext } from "./provider";
import { NostrQuery } from "@lume/system";
export function UserNip05() {
const user = useUserContext();
const { ark } = useRouteContext({ strict: false });
const { isLoading, data: verified } = useQuery({
queryKey: ["nip05", user?.pubkey],
queryFn: async () => {
if (!user.profile?.nip05) return false;
const verify = await ark.verify_nip05(user.pubkey, user.profile?.nip05);
const verify = await NostrQuery.verifyNip05(
user.pubkey,
user.profile?.nip05,
);
return verify;
},
enabled: !!user.profile,
enabled: !!user.profile?.nip05,
});
if (!user.profile?.nip05?.length) return;

View File

@@ -1,12 +1,12 @@
import { useProfile } from "@lume/ark";
import { useProfile } from "@lume/system";
import type { Metadata } from "@lume/types";
import { type ReactNode, createContext, useContext } from "react";
const UserContext = createContext<{
pubkey: string;
profile: Metadata;
isError: boolean;
isLoading: boolean;
profile: Metadata;
}>(null);
export function UserProvider({
@@ -21,7 +21,7 @@ export function UserProvider({
const { isLoading, isError, profile } = useProfile(pubkey, embedProfile);
return (
<UserContext.Provider value={{ pubkey, isError, isLoading, profile }}>
<UserContext.Provider value={{ pubkey, profile, isError, isLoading }}>
{children}
</UserContext.Provider>
);