chore: clean up

This commit is contained in:
reya
2024-08-28 08:48:17 +07:00
parent f6eb5eea44
commit d128af1db8
27 changed files with 311 additions and 741 deletions

View File

@@ -1,6 +1,7 @@
import { commands } from "@/commands.gen";
import { Spinner } from "@/components";
import { Column } from "@/components/column";
import { LumeWindow, NostrQuery } from "@/system";
import { LumeWindow } from "@/system";
import type { ColumnEvent, LumeColumn } from "@/types";
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
import { createLazyFileRoute } from "@tanstack/react-router";
@@ -24,7 +25,7 @@ export const Route = createLazyFileRoute("/$account/_app/home")({
});
function Screen() {
const initialColumnList = Route.useLoaderData();
const { initialColumns } = Route.useRouteContext();
const [columns, setColumns] = useState<LumeColumn[]>([]);
const [emblaRef, emblaApi] = useEmblaCarousel({
@@ -105,6 +106,18 @@ function Screen() {
event.preventDefault();
}, 150);
const saveAllColumns = useDebouncedCallback(async () => {
const key = "lume_v4:columns";
const content = JSON.stringify(columns);
const res = await commands.setLumeStore(key, content);
if (res.status === "ok") {
return res.data;
} else {
console.log(res.error);
}
}, 200);
useEffect(() => {
if (emblaApi) {
emblaApi.on("scroll", emitScrollEvent);
@@ -120,14 +133,12 @@ function Screen() {
}, [emblaApi, emitScrollEvent, emitResizeEvent]);
useEffect(() => {
if (columns) {
NostrQuery.setColumns(columns).then(() => console.log("saved"));
}
if (columns) saveAllColumns();
}, [columns]);
useEffect(() => {
setColumns(initialColumnList);
}, [initialColumnList]);
setColumns(initialColumns);
}, [initialColumns]);
// Listen for keyboard event
useEffect(() => {

View File

@@ -3,16 +3,18 @@ import type { LumeColumn } from "@/types";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/$account/_app/home")({
loader: async ({ context }) => {
beforeLoad: async ({ context }) => {
const key = "lume_v4:columns";
const defaultColumns = context.systemColumns.filter((col) => col.default);
const query = await commands.getLumeStore(key);
let initialColumns: LumeColumn[] = defaultColumns;
if (query.status === "ok") {
const columns: LumeColumn[] = JSON.parse(query.data);
return columns;
} else {
return defaultColumns;
initialColumns = JSON.parse(query.data);
return { initialColumns };
}
return { initialColumns };
},
});

View File

@@ -1,3 +1,4 @@
import { commands } from "@/commands.gen";
import { NostrAccount } from "@/system";
import { Button } from "@getalby/bitcoin-connect-react";
import { createLazyFileRoute } from "@tanstack/react-router";
@@ -11,8 +12,13 @@ export const Route = createLazyFileRoute("/$account/_settings/bitcoin-connect")(
function Screen() {
const setNwcUri = async (uri: string) => {
const cmd = await NostrAccount.setWallet(uri);
if (cmd) getCurrentWebviewWindow().close();
const res = await commands.setWallet(uri);
if (res.status === "ok") {
await getCurrentWebviewWindow().close();
} else {
throw new Error(res.error);
}
};
return (

View File

@@ -1,8 +1,6 @@
import { commands } from "@/commands.gen";
import { cn } from "@/commons";
import { type Profile, commands } from "@/commands.gen";
import { cn, upload } from "@/commons";
import { Spinner } from "@/components";
import { NostrAccount, NostrQuery } from "@/system";
import type { Metadata } from "@/types";
import { Plus } from "@phosphor-icons/react";
import { createLazyFileRoute } from "@tanstack/react-router";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
@@ -27,15 +25,16 @@ function Screen() {
const [isPending, startTransition] = useTransition();
const [picture, setPicture] = useState<string>("");
const onSubmit = (data: Metadata) => {
const onSubmit = (data: Profile) => {
startTransition(async () => {
try {
const newProfile: Metadata = { ...profile, ...data, picture };
await NostrAccount.createProfile(newProfile);
} catch (e) {
await message(String(e), { title: "Profile", kind: "error" });
return;
const newProfile: Profile = { ...profile, ...data, picture };
const res = await commands.setProfile(newProfile);
if (res.status === "error") {
await message(res.error, { title: "Profile", kind: "error" });
}
return;
});
};
@@ -220,17 +219,18 @@ function AvatarUploader({
children: ReactNode;
className?: string;
}) {
const [loading, setLoading] = useState(false);
const [isPending, startTransition] = useTransition();
const uploadAvatar = async () => {
try {
setLoading(true);
const image = await NostrQuery.upload();
setPicture(image);
} catch (e) {
setLoading(false);
await message(String(e), { title: "Lume", kind: "error" });
}
const uploadAvatar = () => {
startTransition(async () => {
try {
const image = await upload();
setPicture(image);
} catch (e) {
await message(String(e));
return;
}
});
};
return (
@@ -239,7 +239,7 @@ function AvatarUploader({
onClick={() => uploadAvatar()}
className={cn("size-4", className)}
>
{loading ? <Spinner className="size-4" /> : children}
{isPending ? <Spinner className="size-4" /> : children}
</button>
);
}

View File

@@ -1,5 +1,4 @@
import { commands } from "@/commands.gen";
import type { Metadata } from "@/types";
import { type Profile, commands } from "@/commands.gen";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/$account/_settings/profile")({
@@ -7,7 +6,7 @@ export const Route = createFileRoute("/$account/_settings/profile")({
const res = await commands.getProfile(params.account);
if (res.status === "ok") {
const profile: Metadata = JSON.parse(res.data);
const profile: Profile = JSON.parse(res.data);
return { profile };
} else {
throw new Error(res.error);

View File

@@ -1,5 +1,4 @@
import { commands } from "@/commands.gen";
import { NostrQuery } from "@/system";
import { Plus, X } from "@phosphor-icons/react";
import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
@@ -16,6 +15,16 @@ function Screen() {
const [newRelay, setNewRelay] = useState("");
const [isPending, startTransition] = useTransition();
const removeRelay = async (relay: string) => {
const res = await commands.removeRelay(relay);
if (res.status === "ok") {
return res.data;
} else {
throw new Error(res.error);
}
};
const addNewRelay = () => {
startTransition(async () => {
try {
@@ -69,7 +78,7 @@ function Screen() {
<div>
<button
type="button"
onClick={() => NostrQuery.removeRelay(relay)}
onClick={() => removeRelay(relay)}
className="inline-flex items-center justify-center rounded-md size-7 hover:bg-black/10 dark:hover:bg-white/10"
>
<X className="size-4" />

View File

@@ -1,4 +1,4 @@
import { NostrAccount } from "@/system";
import { commands } from "@/commands.gen";
import { createLazyFileRoute, redirect } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/$account/_settings/wallet")({
@@ -10,10 +10,14 @@ function Screen() {
const { balance } = Route.useRouteContext();
const disconnect = async () => {
window.localStorage.removeItem("bc:config");
await NostrAccount.removeWallet();
const res = await commands.removeWallet();
return redirect({ to: "/$account/bitcoin-connect", params: { account } });
if (res.status === "ok") {
window.localStorage.removeItem("bc:config");
return redirect({ to: "/$account/bitcoin-connect", params: { account } });
} else {
throw new Error(res.error);
}
};
return (

View File

@@ -1,6 +1,6 @@
import { commands } from "@/commands.gen";
import { upload } from "@/commons";
import { Frame, GoBack, Spinner } from "@/components";
import { NostrQuery } from "@/system";
import { Plus } from "@phosphor-icons/react";
import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
@@ -20,7 +20,7 @@ function Screen() {
const [isPending, startTransition] = useTransition();
const uploadAvatar = async () => {
const file = await NostrQuery.upload();
const file = await upload();
if (file) {
setPicture(file);

View File

@@ -1,9 +1,14 @@
import { NostrAccount } from "@/system";
import { commands } from "@/commands.gen";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/columns/_layout/create-group")({
loader: async () => {
const contacts = await NostrAccount.getContactList();
return contacts;
const res = await commands.getContactList();
if (res.status === "ok") {
return res.data;
} else {
throw new Error(res.error);
}
},
});

View File

@@ -56,9 +56,9 @@ function Screen() {
<button
type="button"
onClick={() => install(column)}
className="text-xs uppercase font-semibold w-max h-7 pl-2.5 pr-2 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
className="text-xs uppercase font-semibold w-16 h-7 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
>
Install
Open
</button>
</div>
))}

View File

@@ -1,10 +1,8 @@
import { Spinner } from "@/components";
import { Conversation } from "@/components/conversation";
import { Quote } from "@/components/quote";
import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text";
import { type LumeEvent, NostrQuery } from "@/system";
import { type ColumnRouteSearch, Kind } from "@/types";
import { commands } from "@/commands.gen";
import { toLumeEvents } from "@/commons";
import { Quote, RepostNote, Spinner, TextNote } from "@/components";
import type { LumeEvent } from "@/system";
import { Kind } from "@/types";
import { ArrowCircleRight } from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useInfiniteQuery } from "@tanstack/react-query";
@@ -13,17 +11,6 @@ import { useCallback, useRef } from "react";
import { Virtualizer } from "virtua";
export const Route = createFileRoute("/columns/_layout/global")({
validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
return {
account: search.account,
label: search.label,
name: search.name,
};
},
beforeLoad: async () => {
const settings = await NostrQuery.getUserSettings();
return { settings };
},
component: Screen,
});
@@ -40,8 +27,14 @@ export function Screen() {
queryKey: [label, account],
initialPageParam: 0,
queryFn: async ({ pageParam }: { pageParam: number }) => {
const events = await NostrQuery.getGlobalEvents(pageParam);
return events;
const until = pageParam > 0 ? pageParam.toString() : undefined;
const res = await commands.getGlobalEvents(until);
if (res.status === "error") {
throw new Error(res.error);
}
return toLumeEvents(res.data);
},
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
select: (data) => data?.pages.flat(),
@@ -57,15 +50,11 @@ export function Screen() {
case Kind.Repost:
return <RepostNote key={event.id} event={event} className="mb-3" />;
default: {
if (event.isConversation) {
return (
<Conversation key={event.id} className="mb-3" event={event} />
);
}
if (event.isQuote) {
return <Quote key={event.id} event={event} className="mb-3" />;
} else {
return <TextNote key={event.id} event={event} className="mb-3" />;
}
return <TextNote key={event.id} event={event} className="mb-3" />;
}
}
},
@@ -81,12 +70,10 @@ export function Screen() {
<ScrollArea.Viewport ref={ref} className="h-full px-3 pb-3">
<Virtualizer scrollRef={ref}>
{isFetching && !isLoading && !isFetchingNextPage ? (
<div className="flex items-center justify-center w-full mb-3 h-12 bg-black/5 dark:bg-white/5 rounded-xl">
<div className="flex items-center justify-center gap-2">
<Spinner className="size-5" />
<span className="text-sm font-medium">
Getting new notes...
</span>
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
<Spinner className="size-4" />
Getting new notes...
</div>
</div>
) : null}
@@ -96,13 +83,13 @@ export function Screen() {
<span className="text-sm font-medium">Loading...</span>
</div>
) : !data.length ? (
<div className="flex items-center justify-center">
Yo. You're catching up on all the things happening around you.
<div className="mb-3 flex items-center justify-center h-20 text-sm rounded-xl bg-black/5 dark:bg-white/5">
🎉 Yo. You're catching up on all latest notes.
</div>
) : (
data.map((item) => renderItem(item))
)}
{data?.length && hasNextPage ? (
{hasNextPage ? (
<div>
<button
type="button"

View File

@@ -1,9 +1,8 @@
import { commands } from "@/commands.gen";
import { decodeZapInvoice, formatCreatedAt } from "@/commons";
import { Spinner } from "@/components";
import { Note } from "@/components/note";
import { User } from "@/components/user";
import { type LumeEvent, NostrQuery, useEvent } from "@/system";
import { Kind } from "@/types";
import { Note, Spinner, User } from "@/components";
import { LumeEvent, useEvent } from "@/system";
import { Kind, type NostrEvent } from "@/types";
import { Info, Repeat } from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import * as Tabs from "@radix-ui/react-tabs";
@@ -24,7 +23,15 @@ function Screen() {
const { isLoading, data } = useQuery({
queryKey: ["notification", account],
queryFn: async () => {
const events = await NostrQuery.getNotifications();
const res = await commands.getNotifications();
if (res.status === "error") {
throw new Error(res.error);
}
const data: NostrEvent[] = res.data.map((item) => JSON.parse(item));
const events = data.map((ev) => new LumeEvent(ev));
return events;
},
select: (events) => {

View File

@@ -1,72 +1,62 @@
import { insertImage, isImagePath } from "@/commons";
import { insertImage, isImagePath, upload } from "@/commons";
import { Spinner } from "@/components";
import { NostrQuery } from "@/system";
import { Images } from "@phosphor-icons/react";
import type { UnlistenFn } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { message } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react";
import { useEffect, useTransition } from "react";
import { useSlateStatic } from "slate-react";
export function MediaButton() {
const editor = useSlateStatic();
const [loading, setLoading] = useState(false);
const [isPending, startTransition] = useTransition();
const upload = async () => {
try {
// start loading
setLoading(true);
const image = await NostrQuery.upload();
insertImage(editor, image);
// reset loading
setLoading(false);
} catch (e) {
setLoading(false);
await message(String(e), { title: "Upload", kind: "error" });
}
const uploadMedia = () => {
startTransition(async () => {
try {
const image = await upload();
return insertImage(editor, image);
} catch (e) {
await message(String(e), { title: "Upload", kind: "error" });
return;
}
});
};
useEffect(() => {
let unlisten: UnlistenFn = undefined;
const unlisten = getCurrentWindow().listen("tauri://file-drop", (event) => {
startTransition(async () => {
// @ts-ignore, lfg !!!
const items: string[] = event.payload.paths;
async function listenFileDrop() {
const window = getCurrentWindow();
if (!unlisten) {
unlisten = await window.listen("tauri://file-drop", async (event) => {
// @ts-ignore, lfg !!!
const items: string[] = event.payload.paths;
// start loading
setLoading(true);
// upload all images
for (const item of items) {
if (isImagePath(item)) {
const image = await NostrQuery.upload(item);
insertImage(editor, image);
}
// upload all images
for (const item of items) {
if (isImagePath(item)) {
const image = await upload(item);
insertImage(editor, image);
}
// stop loading
setLoading(false);
});
}
}
}
listenFileDrop();
return;
});
});
return () => {
if (unlisten) unlisten();
unlisten.then((f) => f());
};
}, []);
return (
<button
type="button"
onClick={() => upload()}
disabled={loading}
onClick={() => uploadMedia()}
disabled={isPending}
className="inline-flex items-center h-8 gap-2 px-2.5 text-sm rounded-lg text-black/70 dark:text-white/70 w-max hover:bg-black/10 dark:hover:bg-white/10"
>
{loading ? <Spinner className="size-4" /> : <Images className="size-4" />}
{isPending ? (
<Spinner className="size-4" />
) : (
<Images className="size-4" />
)}
Add media
</button>
);

View File

@@ -1,5 +1,5 @@
import { commands } from "@/commands.gen";
import { checkForAppUpdates } from "@/commons";
import { NostrAccount } from "@/system";
import { createFileRoute, redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/")({
@@ -8,7 +8,7 @@ export const Route = createFileRoute("/")({
await checkForAppUpdates(true);
// Get all accounts
const accounts = await NostrAccount.getAccounts();
const accounts = await commands.getAccounts();
if (accounts.length < 1) {
throw redirect({

View File

@@ -2,7 +2,7 @@ import { User } from "@/components/user";
import { createLazyFileRoute } from "@tanstack/react-router";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react";
import { useState, useTransition } from "react";
import CurrencyInput from "react-currency-input-field";
const DEFAULT_VALUES = [21, 50, 100, 200];
@@ -17,28 +17,26 @@ function Screen() {
const [amount, setAmount] = useState(21);
const [content, setContent] = useState("");
const [isCompleted, setIsCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isPending, startTransition] = useTransition();
const submit = async () => {
try {
// start loading
setIsLoading(true);
const submit = () => {
startTransition(async () => {
try {
const val = await event.zap(amount, content);
// Zap
const val = await event.zap(amount, content);
if (val) {
setIsCompleted(true);
// close current window
await getCurrentWebviewWindow().close();
if (val) {
setIsCompleted(true);
// close current window
await getCurrentWebviewWindow().close();
}
} catch (e) {
await message(String(e), {
title: "Zap",
kind: "error",
});
return;
}
} catch (e) {
setIsLoading(false);
await message(String(e), {
title: "Zap",
kind: "error",
});
}
});
};
return (
@@ -104,7 +102,7 @@ function Screen() {
onClick={() => submit()}
className="inline-flex items-center justify-center w-full h-10 font-medium rounded-xl bg-neutral-950 text-neutral-50 hover:bg-neutral-900 dark:bg-white/20 dark:hover:bg-white/30"
>
{isCompleted ? "Zapped" : isLoading ? "Processing..." : "Zap"}
{isCompleted ? "Zapped" : isPending ? "Processing..." : "Zap"}
</button>
</div>
</div>

View File

@@ -1,9 +1,23 @@
import { NostrQuery } from "@/system";
import { commands } from "@/commands.gen";
import { LumeEvent } from "@/system";
import type { NostrEvent } from "@/types";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/zap/$id")({
beforeLoad: async ({ params }) => {
const event = await NostrQuery.getEvent(params.id);
return { event };
const res = await commands.getEvent(params.id);
if (res.status === "ok") {
const data = res.data;
const raw: NostrEvent = JSON.parse(data.raw);
if (data.parsed) {
raw.meta = data.parsed;
}
return { event: new LumeEvent(raw) };
} else {
throw new Error(res.error);
}
},
});