chore: clean up
This commit is contained in:
@@ -6,7 +6,7 @@ use std::{str::FromStr, time::Duration};
|
|||||||
use tauri::State;
|
use tauri::State;
|
||||||
use tauri_specta::Event;
|
use tauri_specta::Event;
|
||||||
|
|
||||||
use crate::{NewSettings, Nostr, Settings};
|
use crate::{common::get_latest_event, NewSettings, Nostr, Settings};
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
@@ -229,14 +229,14 @@ pub async fn get_lume_store(key: String, state: State<'_, Nostr>) -> Result<Stri
|
|||||||
.author(public_key)
|
.author(public_key)
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.identifier(key)
|
.identifier(key)
|
||||||
.limit(1);
|
.limit(10);
|
||||||
|
|
||||||
match client
|
match client
|
||||||
.get_events_of(vec![filter], EventSource::Database)
|
.get_events_of(vec![filter], EventSource::Database)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
if let Some(event) = events.first() {
|
if let Some(event) = get_latest_event(&events) {
|
||||||
match signer.nip44_decrypt(public_key, event.content()).await {
|
match signer.nip44_decrypt(public_key, event.content()).await {
|
||||||
Ok(decrypted) => Ok(decrypted),
|
Ok(decrypted) => Ok(decrypted),
|
||||||
Err(_) => Err(event.content.to_string()),
|
Err(_) => Err(event.content.to_string()),
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import type {
|
|||||||
PersistedQuery,
|
PersistedQuery,
|
||||||
} from "@tanstack/query-persist-client-core";
|
} from "@tanstack/query-persist-client-core";
|
||||||
import { Store } from "@tanstack/store";
|
import { Store } from "@tanstack/store";
|
||||||
import { ask, message } from "@tauri-apps/plugin-dialog";
|
import { ask, message, open } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { readFile } from "@tauri-apps/plugin-fs";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
import type { Store as TauriStore } from "@tauri-apps/plugin-store";
|
import type { Store as TauriStore } from "@tauri-apps/plugin-store";
|
||||||
import { check } from "@tauri-apps/plugin-updater";
|
import { check } from "@tauri-apps/plugin-updater";
|
||||||
@@ -241,6 +242,61 @@ export async function checkForAppUpdates(silent: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function upload(filePath?: string) {
|
||||||
|
const allowExts = [
|
||||||
|
"png",
|
||||||
|
"jpeg",
|
||||||
|
"jpg",
|
||||||
|
"gif",
|
||||||
|
"mp4",
|
||||||
|
"mp3",
|
||||||
|
"webm",
|
||||||
|
"mkv",
|
||||||
|
"avi",
|
||||||
|
"mov",
|
||||||
|
];
|
||||||
|
|
||||||
|
const selected =
|
||||||
|
filePath ||
|
||||||
|
(
|
||||||
|
await open({
|
||||||
|
multiple: false,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Media",
|
||||||
|
extensions: allowExts,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).path;
|
||||||
|
|
||||||
|
// User cancelled action
|
||||||
|
if (!selected) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = await readFile(selected);
|
||||||
|
const blob = new Blob([file]);
|
||||||
|
|
||||||
|
const data = new FormData();
|
||||||
|
data.append("fileToUpload", blob);
|
||||||
|
data.append("submit", "Upload Image");
|
||||||
|
|
||||||
|
const res = await fetch("https://nostr.build/api/v2/upload/files", {
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) return null;
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
const content = json.data[0];
|
||||||
|
|
||||||
|
return content.url as string;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function toLumeEvents(richEvents: RichEvent[]) {
|
export function toLumeEvents(richEvents: RichEvent[]) {
|
||||||
const events = richEvents.map((item) => {
|
const events = richEvents.map((item) => {
|
||||||
const nostrEvent: NostrEvent = JSON.parse(item.raw);
|
const nostrEvent: NostrEvent = JSON.parse(item.raw);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { appSettings, cn } from "@/commons";
|
import { appSettings, cn } from "@/commons";
|
||||||
import { LumeWindow } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import { Lightning } from "@phosphor-icons/react";
|
import { Lightning } from "@phosphor-icons/react";
|
||||||
|
import { useSearch } from "@tanstack/react-router";
|
||||||
import { useStore } from "@tanstack/react-store";
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
|
|
||||||
export function NoteZap({ large = false }: { large?: boolean }) {
|
export function NoteZap({ large = false }: { large?: boolean }) {
|
||||||
|
const search = useSearch({ strict: false });
|
||||||
const visible = useStore(appSettings, (state) => state.display_zap_button);
|
const visible = useStore(appSettings, (state) => state.display_zap_button);
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ export function NoteZap({ large = false }: { large?: boolean }) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => LumeWindow.openZap(event.id)}
|
onClick={() => LumeWindow.openZap(event.id, search.account)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
|
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
|
||||||
large
|
large
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { cn } from "@/commons";
|
|||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { Note } from "@/components/note";
|
import { Note } from "@/components/note";
|
||||||
import { User } from "@/components/user";
|
import { User } from "@/components/user";
|
||||||
import { type LumeEvent, NostrQuery } from "@/system";
|
import { type LumeEvent, useEvent } from "@/system";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
|
||||||
export const RepostNote = memo(function RepostNote({
|
export const RepostNote = memo(function RepostNote({
|
||||||
@@ -13,22 +12,7 @@ export const RepostNote = memo(function RepostNote({
|
|||||||
event: LumeEvent;
|
event: LumeEvent;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, isError, data } = useQuery({
|
const { isLoading, isError, data } = useEvent(event.repostId);
|
||||||
queryKey: ["event", event.repostId],
|
|
||||||
queryFn: async () => {
|
|
||||||
try {
|
|
||||||
const data = await NostrQuery.getRepostEvent(event);
|
|
||||||
return data;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnMount: false,
|
|
||||||
refetchOnReconnect: false,
|
|
||||||
staleTime: Number.POSITIVE_INFINITY,
|
|
||||||
retry: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Note.Root
|
<Note.Root
|
||||||
|
|||||||
@@ -1,21 +1,34 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import { cn } from "@/commons";
|
import { cn } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { NostrAccount } from "@/system";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useRouteContext } from "@tanstack/react-router";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useEffect, useState, useTransition } from "react";
|
import { useTransition } from "react";
|
||||||
import { useUserContext } from "./provider";
|
import { useUserContext } from "./provider";
|
||||||
|
|
||||||
export function UserFollowButton({
|
export function UserFollowButton({ className }: { className?: string }) {
|
||||||
simple = false,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
simple?: boolean;
|
|
||||||
className?: string;
|
|
||||||
}) {
|
|
||||||
const user = useUserContext();
|
const user = useUserContext();
|
||||||
|
|
||||||
const [followed, setFollowed] = useState(false);
|
const { queryClient } = useRouteContext({ strict: false });
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
data: isFollow,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["status", user.pubkey],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await commands.checkContact(user.pubkey);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const toggleFollow = () => {
|
const toggleFollow = () => {
|
||||||
@@ -23,7 +36,17 @@ export function UserFollowButton({
|
|||||||
const res = await commands.toggleContact(user.pubkey, null);
|
const res = await commands.toggleContact(user.pubkey, null);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
setFollowed((prev) => !prev);
|
queryClient.setQueryData(
|
||||||
|
["status", user.pubkey],
|
||||||
|
(prev: boolean) => !prev,
|
||||||
|
);
|
||||||
|
|
||||||
|
// invalidate cache
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["status", user.pubkey],
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
await message(res.error, { kind: "error" });
|
await message(res.error, { kind: "error" });
|
||||||
return;
|
return;
|
||||||
@@ -31,18 +54,6 @@ export function UserFollowButton({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let mounted = true;
|
|
||||||
|
|
||||||
NostrAccount.checkContact(user.pubkey).then((status) => {
|
|
||||||
if (mounted) setFollowed(status);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
mounted = false;
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -50,15 +61,9 @@ export function UserFollowButton({
|
|||||||
onClick={() => toggleFollow()}
|
onClick={() => toggleFollow()}
|
||||||
className={cn("w-max", className)}
|
className={cn("w-max", className)}
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isError ? "Error" : null}
|
||||||
<Spinner className="size-4" />
|
{isPending || isLoading ? <Spinner className="size-4" /> : null}
|
||||||
) : followed ? (
|
{isFollow ? "Unfollow" : "Follow"}
|
||||||
!simple ? (
|
|
||||||
"Unfollow"
|
|
||||||
) : null
|
|
||||||
) : (
|
|
||||||
"Follow"
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
import { displayLongHandle, displayNpub } from "@/commons";
|
import { displayLongHandle, displayNpub } from "@/commons";
|
||||||
import { NostrQuery } from "@/system";
|
|
||||||
import { SealCheck } from "@phosphor-icons/react";
|
import { SealCheck } from "@phosphor-icons/react";
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
@@ -10,14 +10,13 @@ export function UserNip05() {
|
|||||||
const { isLoading, data: verified } = useQuery({
|
const { isLoading, data: verified } = useQuery({
|
||||||
queryKey: ["nip05", user?.pubkey],
|
queryKey: ["nip05", user?.pubkey],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!user.profile?.nip05?.length) return false;
|
const res = await commands.verifyNip05(user.pubkey, user.profile?.nip05);
|
||||||
|
|
||||||
const verify = await NostrQuery.verifyNip05(
|
if (res.status === "ok") {
|
||||||
user.pubkey,
|
return res.data;
|
||||||
user.profile?.nip05,
|
} else {
|
||||||
);
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
return verify;
|
|
||||||
},
|
},
|
||||||
enabled: !!user.profile?.nip05?.length,
|
enabled: !!user.profile?.nip05?.length,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { Column } from "@/components/column";
|
import { Column } from "@/components/column";
|
||||||
import { LumeWindow, NostrQuery } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import type { ColumnEvent, LumeColumn } from "@/types";
|
import type { ColumnEvent, LumeColumn } from "@/types";
|
||||||
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
@@ -24,7 +25,7 @@ export const Route = createLazyFileRoute("/$account/_app/home")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const initialColumnList = Route.useLoaderData();
|
const { initialColumns } = Route.useRouteContext();
|
||||||
|
|
||||||
const [columns, setColumns] = useState<LumeColumn[]>([]);
|
const [columns, setColumns] = useState<LumeColumn[]>([]);
|
||||||
const [emblaRef, emblaApi] = useEmblaCarousel({
|
const [emblaRef, emblaApi] = useEmblaCarousel({
|
||||||
@@ -105,6 +106,18 @@ function Screen() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, 150);
|
}, 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(() => {
|
useEffect(() => {
|
||||||
if (emblaApi) {
|
if (emblaApi) {
|
||||||
emblaApi.on("scroll", emitScrollEvent);
|
emblaApi.on("scroll", emitScrollEvent);
|
||||||
@@ -120,14 +133,12 @@ function Screen() {
|
|||||||
}, [emblaApi, emitScrollEvent, emitResizeEvent]);
|
}, [emblaApi, emitScrollEvent, emitResizeEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (columns) {
|
if (columns) saveAllColumns();
|
||||||
NostrQuery.setColumns(columns).then(() => console.log("saved"));
|
|
||||||
}
|
|
||||||
}, [columns]);
|
}, [columns]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setColumns(initialColumnList);
|
setColumns(initialColumns);
|
||||||
}, [initialColumnList]);
|
}, [initialColumns]);
|
||||||
|
|
||||||
// Listen for keyboard event
|
// Listen for keyboard event
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import type { LumeColumn } from "@/types";
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/$account/_app/home")({
|
export const Route = createFileRoute("/$account/_app/home")({
|
||||||
loader: async ({ context }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
const key = "lume_v4:columns";
|
const key = "lume_v4:columns";
|
||||||
const defaultColumns = context.systemColumns.filter((col) => col.default);
|
const defaultColumns = context.systemColumns.filter((col) => col.default);
|
||||||
const query = await commands.getLumeStore(key);
|
const query = await commands.getLumeStore(key);
|
||||||
|
|
||||||
|
let initialColumns: LumeColumn[] = defaultColumns;
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
const columns: LumeColumn[] = JSON.parse(query.data);
|
initialColumns = JSON.parse(query.data);
|
||||||
return columns;
|
return { initialColumns };
|
||||||
} else {
|
|
||||||
return defaultColumns;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { initialColumns };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
import { NostrAccount } from "@/system";
|
import { NostrAccount } from "@/system";
|
||||||
import { Button } from "@getalby/bitcoin-connect-react";
|
import { Button } from "@getalby/bitcoin-connect-react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
@@ -11,8 +12,13 @@ export const Route = createLazyFileRoute("/$account/_settings/bitcoin-connect")(
|
|||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const setNwcUri = async (uri: string) => {
|
const setNwcUri = async (uri: string) => {
|
||||||
const cmd = await NostrAccount.setWallet(uri);
|
const res = await commands.setWallet(uri);
|
||||||
if (cmd) getCurrentWebviewWindow().close();
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
await getCurrentWebviewWindow().close();
|
||||||
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { type Profile, commands } from "@/commands.gen";
|
||||||
import { cn } from "@/commons";
|
import { cn, upload } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { NostrAccount, NostrQuery } from "@/system";
|
|
||||||
import type { Metadata } from "@/types";
|
|
||||||
import { Plus } from "@phosphor-icons/react";
|
import { Plus } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
@@ -27,15 +25,16 @@ function Screen() {
|
|||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [picture, setPicture] = useState<string>("");
|
const [picture, setPicture] = useState<string>("");
|
||||||
|
|
||||||
const onSubmit = (data: Metadata) => {
|
const onSubmit = (data: Profile) => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
const newProfile: Profile = { ...profile, ...data, picture };
|
||||||
const newProfile: Metadata = { ...profile, ...data, picture };
|
const res = await commands.setProfile(newProfile);
|
||||||
await NostrAccount.createProfile(newProfile);
|
|
||||||
} catch (e) {
|
if (res.status === "error") {
|
||||||
await message(String(e), { title: "Profile", kind: "error" });
|
await message(res.error, { title: "Profile", kind: "error" });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,17 +219,18 @@ function AvatarUploader({
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const uploadAvatar = async () => {
|
const uploadAvatar = () => {
|
||||||
try {
|
startTransition(async () => {
|
||||||
setLoading(true);
|
try {
|
||||||
const image = await NostrQuery.upload();
|
const image = await upload();
|
||||||
setPicture(image);
|
setPicture(image);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
await message(String(e));
|
||||||
await message(String(e), { title: "Lume", kind: "error" });
|
return;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -239,7 +239,7 @@ function AvatarUploader({
|
|||||||
onClick={() => uploadAvatar()}
|
onClick={() => uploadAvatar()}
|
||||||
className={cn("size-4", className)}
|
className={cn("size-4", className)}
|
||||||
>
|
>
|
||||||
{loading ? <Spinner className="size-4" /> : children}
|
{isPending ? <Spinner className="size-4" /> : children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { type Profile, commands } from "@/commands.gen";
|
||||||
import type { Metadata } from "@/types";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/$account/_settings/profile")({
|
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);
|
const res = await commands.getProfile(params.account);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const profile: Metadata = JSON.parse(res.data);
|
const profile: Profile = JSON.parse(res.data);
|
||||||
return { profile };
|
return { profile };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import { NostrQuery } from "@/system";
|
|
||||||
import { Plus, X } from "@phosphor-icons/react";
|
import { Plus, X } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
@@ -16,6 +15,16 @@ function Screen() {
|
|||||||
const [newRelay, setNewRelay] = useState("");
|
const [newRelay, setNewRelay] = useState("");
|
||||||
const [isPending, startTransition] = useTransition();
|
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 = () => {
|
const addNewRelay = () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -69,7 +78,7 @@ function Screen() {
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="inline-flex items-center justify-center rounded-md size-7 hover:bg-black/10 dark:hover:bg-white/10"
|
||||||
>
|
>
|
||||||
<X className="size-4" />
|
<X className="size-4" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NostrAccount } from "@/system";
|
import { commands } from "@/commands.gen";
|
||||||
import { createLazyFileRoute, redirect } from "@tanstack/react-router";
|
import { createLazyFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/$account/_settings/wallet")({
|
export const Route = createLazyFileRoute("/$account/_settings/wallet")({
|
||||||
@@ -10,10 +10,14 @@ function Screen() {
|
|||||||
const { balance } = Route.useRouteContext();
|
const { balance } = Route.useRouteContext();
|
||||||
|
|
||||||
const disconnect = async () => {
|
const disconnect = async () => {
|
||||||
window.localStorage.removeItem("bc:config");
|
const res = await commands.removeWallet();
|
||||||
await NostrAccount.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 (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
|
import { upload } from "@/commons";
|
||||||
import { Frame, GoBack, Spinner } from "@/components";
|
import { Frame, GoBack, Spinner } from "@/components";
|
||||||
import { NostrQuery } from "@/system";
|
|
||||||
import { Plus } from "@phosphor-icons/react";
|
import { Plus } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
@@ -20,7 +20,7 @@ function Screen() {
|
|||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const uploadAvatar = async () => {
|
const uploadAvatar = async () => {
|
||||||
const file = await NostrQuery.upload();
|
const file = await upload();
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
setPicture(file);
|
setPicture(file);
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { NostrAccount } from "@/system";
|
import { commands } from "@/commands.gen";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/create-group")({
|
export const Route = createFileRoute("/columns/_layout/create-group")({
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
const contacts = await NostrAccount.getContactList();
|
const res = await commands.getContactList();
|
||||||
return contacts;
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ function Screen() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => install(column)}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Spinner } from "@/components";
|
import { commands } from "@/commands.gen";
|
||||||
import { Conversation } from "@/components/conversation";
|
import { toLumeEvents } from "@/commons";
|
||||||
import { Quote } from "@/components/quote";
|
import { Quote, RepostNote, Spinner, TextNote } from "@/components";
|
||||||
import { RepostNote } from "@/components/repost";
|
import type { LumeEvent } from "@/system";
|
||||||
import { TextNote } from "@/components/text";
|
import { Kind } from "@/types";
|
||||||
import { type LumeEvent, NostrQuery } from "@/system";
|
|
||||||
import { type ColumnRouteSearch, Kind } from "@/types";
|
|
||||||
import { ArrowCircleRight } from "@phosphor-icons/react";
|
import { ArrowCircleRight } from "@phosphor-icons/react";
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
@@ -13,17 +11,6 @@ import { useCallback, useRef } from "react";
|
|||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/global")({
|
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,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,8 +27,14 @@ export function Screen() {
|
|||||||
queryKey: [label, account],
|
queryKey: [label, account],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const events = await NostrQuery.getGlobalEvents(pageParam);
|
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||||
return events;
|
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,
|
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
@@ -57,15 +50,11 @@ export function Screen() {
|
|||||||
case Kind.Repost:
|
case Kind.Repost:
|
||||||
return <RepostNote key={event.id} event={event} className="mb-3" />;
|
return <RepostNote key={event.id} event={event} className="mb-3" />;
|
||||||
default: {
|
default: {
|
||||||
if (event.isConversation) {
|
|
||||||
return (
|
|
||||||
<Conversation key={event.id} className="mb-3" event={event} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (event.isQuote) {
|
if (event.isQuote) {
|
||||||
return <Quote key={event.id} event={event} className="mb-3" />;
|
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">
|
<ScrollArea.Viewport ref={ref} className="h-full px-3 pb-3">
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{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="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<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-5" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">
|
Getting new notes...
|
||||||
Getting new notes...
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -96,13 +83,13 @@ export function Screen() {
|
|||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data.length ? (
|
||||||
<div className="flex items-center justify-center">
|
<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 the things happening around you.
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
data.map((item) => renderItem(item))
|
data.map((item) => renderItem(item))
|
||||||
)}
|
)}
|
||||||
{data?.length && hasNextPage ? (
|
{hasNextPage ? (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
import { decodeZapInvoice, formatCreatedAt } from "@/commons";
|
import { decodeZapInvoice, formatCreatedAt } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Note, Spinner, User } from "@/components";
|
||||||
import { Note } from "@/components/note";
|
import { LumeEvent, useEvent } from "@/system";
|
||||||
import { User } from "@/components/user";
|
import { Kind, type NostrEvent } from "@/types";
|
||||||
import { type LumeEvent, NostrQuery, useEvent } from "@/system";
|
|
||||||
import { Kind } from "@/types";
|
|
||||||
import { Info, Repeat } from "@phosphor-icons/react";
|
import { Info, Repeat } from "@phosphor-icons/react";
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import * as Tabs from "@radix-ui/react-tabs";
|
import * as Tabs from "@radix-ui/react-tabs";
|
||||||
@@ -24,7 +23,15 @@ function Screen() {
|
|||||||
const { isLoading, data } = useQuery({
|
const { isLoading, data } = useQuery({
|
||||||
queryKey: ["notification", account],
|
queryKey: ["notification", account],
|
||||||
queryFn: async () => {
|
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;
|
return events;
|
||||||
},
|
},
|
||||||
select: (events) => {
|
select: (events) => {
|
||||||
|
|||||||
@@ -1,72 +1,62 @@
|
|||||||
import { insertImage, isImagePath } from "@/commons";
|
import { insertImage, isImagePath, upload } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { NostrQuery } from "@/system";
|
|
||||||
import { Images } from "@phosphor-icons/react";
|
import { Images } from "@phosphor-icons/react";
|
||||||
import type { UnlistenFn } from "@tauri-apps/api/event";
|
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useTransition } from "react";
|
||||||
import { useSlateStatic } from "slate-react";
|
import { useSlateStatic } from "slate-react";
|
||||||
|
|
||||||
export function MediaButton() {
|
export function MediaButton() {
|
||||||
const editor = useSlateStatic();
|
const editor = useSlateStatic();
|
||||||
const [loading, setLoading] = useState(false);
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const upload = async () => {
|
const uploadMedia = () => {
|
||||||
try {
|
startTransition(async () => {
|
||||||
// start loading
|
try {
|
||||||
setLoading(true);
|
const image = await upload();
|
||||||
|
return insertImage(editor, image);
|
||||||
const image = await NostrQuery.upload();
|
} catch (e) {
|
||||||
insertImage(editor, image);
|
await message(String(e), { title: "Upload", kind: "error" });
|
||||||
|
return;
|
||||||
// reset loading
|
}
|
||||||
setLoading(false);
|
});
|
||||||
} catch (e) {
|
|
||||||
setLoading(false);
|
|
||||||
await message(String(e), { title: "Upload", kind: "error" });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
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() {
|
// upload all images
|
||||||
const window = getCurrentWindow();
|
for (const item of items) {
|
||||||
if (!unlisten) {
|
if (isImagePath(item)) {
|
||||||
unlisten = await window.listen("tauri://file-drop", async (event) => {
|
const image = await upload(item);
|
||||||
// @ts-ignore, lfg !!!
|
insertImage(editor, image);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// stop loading
|
}
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listenFileDrop();
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (unlisten) unlisten();
|
unlisten.then((f) => f());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => upload()}
|
onClick={() => uploadMedia()}
|
||||||
disabled={loading}
|
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"
|
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
|
Add media
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
import { checkForAppUpdates } from "@/commons";
|
import { checkForAppUpdates } from "@/commons";
|
||||||
import { NostrAccount } from "@/system";
|
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
@@ -8,7 +8,7 @@ export const Route = createFileRoute("/")({
|
|||||||
await checkForAppUpdates(true);
|
await checkForAppUpdates(true);
|
||||||
|
|
||||||
// Get all accounts
|
// Get all accounts
|
||||||
const accounts = await NostrAccount.getAccounts();
|
const accounts = await commands.getAccounts();
|
||||||
|
|
||||||
if (accounts.length < 1) {
|
if (accounts.length < 1) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { User } from "@/components/user";
|
|||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useState } from "react";
|
import { useState, useTransition } from "react";
|
||||||
import CurrencyInput from "react-currency-input-field";
|
import CurrencyInput from "react-currency-input-field";
|
||||||
|
|
||||||
const DEFAULT_VALUES = [21, 50, 100, 200];
|
const DEFAULT_VALUES = [21, 50, 100, 200];
|
||||||
@@ -17,28 +17,26 @@ function Screen() {
|
|||||||
const [amount, setAmount] = useState(21);
|
const [amount, setAmount] = useState(21);
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [isCompleted, setIsCompleted] = useState(false);
|
const [isCompleted, setIsCompleted] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = () => {
|
||||||
try {
|
startTransition(async () => {
|
||||||
// start loading
|
try {
|
||||||
setIsLoading(true);
|
const val = await event.zap(amount, content);
|
||||||
|
|
||||||
// Zap
|
if (val) {
|
||||||
const val = await event.zap(amount, content);
|
setIsCompleted(true);
|
||||||
|
// close current window
|
||||||
if (val) {
|
await getCurrentWebviewWindow().close();
|
||||||
setIsCompleted(true);
|
}
|
||||||
// close current window
|
} catch (e) {
|
||||||
await getCurrentWebviewWindow().close();
|
await message(String(e), {
|
||||||
|
title: "Zap",
|
||||||
|
kind: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
});
|
||||||
setIsLoading(false);
|
|
||||||
await message(String(e), {
|
|
||||||
title: "Zap",
|
|
||||||
kind: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -104,7 +102,7 @@ function Screen() {
|
|||||||
onClick={() => submit()}
|
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"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/zap/$id")({
|
export const Route = createFileRoute("/zap/$id")({
|
||||||
beforeLoad: async ({ params }) => {
|
beforeLoad: async ({ params }) => {
|
||||||
const event = await NostrQuery.getEvent(params.id);
|
const res = await commands.getEvent(params.id);
|
||||||
return { event };
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
import { type Result, commands } from "@/commands.gen";
|
|
||||||
import type { Metadata } from "@/types";
|
|
||||||
|
|
||||||
export const NostrAccount = {
|
|
||||||
getAccounts: async () => {
|
|
||||||
const query = await commands.getAccounts();
|
|
||||||
return query;
|
|
||||||
},
|
|
||||||
loadAccount: async (npub: string) => {
|
|
||||||
const bunker: string = localStorage.getItem(`${npub}_bunker`);
|
|
||||||
let query: Result<boolean, string>;
|
|
||||||
|
|
||||||
if (bunker?.length && bunker?.startsWith("bunker://")) {
|
|
||||||
query = await commands.loadAccount(npub, bunker);
|
|
||||||
} else {
|
|
||||||
query = await commands.loadAccount(npub, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createAccount: async () => {
|
|
||||||
const query = await commands.createAccount();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createProfile: async (profile: Metadata) => {
|
|
||||||
const query = await commands.createProfile(
|
|
||||||
profile.name || "",
|
|
||||||
profile.display_name || "",
|
|
||||||
profile.about || "",
|
|
||||||
profile.picture || "",
|
|
||||||
profile.banner || "",
|
|
||||||
profile.nip05 || "",
|
|
||||||
profile.lud16 || "",
|
|
||||||
profile.website || "",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveAccount: async (nsec: string, password = "") => {
|
|
||||||
const query = await commands.saveAccount(nsec, password);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
connectRemoteAccount: async (uri: string) => {
|
|
||||||
const connect = await commands.connectRemoteAccount(uri);
|
|
||||||
|
|
||||||
if (connect.status === "ok") {
|
|
||||||
const npub = connect.data;
|
|
||||||
const parsed = new URL(uri);
|
|
||||||
parsed.searchParams.delete("secret");
|
|
||||||
|
|
||||||
// save connection string
|
|
||||||
localStorage.setItem(`${npub}_bunker`, parsed.toString());
|
|
||||||
|
|
||||||
return npub;
|
|
||||||
} else {
|
|
||||||
throw new Error(connect.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setContactList: async (pubkeys: string[]) => {
|
|
||||||
const query = await commands.setContactList(pubkeys);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadWallet: async () => {
|
|
||||||
const query = await commands.loadWallet();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return Number.parseInt(query.data);
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setWallet: async (uri: string) => {
|
|
||||||
const query = await commands.setWallet(uri);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeWallet: async () => {
|
|
||||||
const query = await commands.removeWallet();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getProfile: async () => {
|
|
||||||
const query = await commands.getCurrentProfile();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return JSON.parse(query.data) as Metadata;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getContactList: async () => {
|
|
||||||
const query = await commands.getContactList();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isContactListEmpty: async () => {
|
|
||||||
const query = await commands.isContactListEmpty();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkContact: async (pubkey: string) => {
|
|
||||||
const query = await commands.checkContact(pubkey);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleContact: async (pubkey: string, alias?: string) => {
|
|
||||||
const query = await commands.toggleContact(pubkey, alias);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
f2f: async (npub: string) => {
|
|
||||||
const query = await commands.copyFriend(npub);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -16,7 +16,7 @@ export function useEvent(id: string) {
|
|||||||
|
|
||||||
// Define query
|
// Define query
|
||||||
let query: Result<RichEvent, string>;
|
let query: Result<RichEvent, string>;
|
||||||
let relayHint: string;
|
let relayHint: string = null;
|
||||||
|
|
||||||
if (normalizeId.startsWith("nevent1")) {
|
if (normalizeId.startsWith("nevent1")) {
|
||||||
const decoded = nip19.decode(normalizeId);
|
const decoded = nip19.decode(normalizeId);
|
||||||
@@ -27,14 +27,11 @@ export function useEvent(id: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(relayHint);
|
||||||
|
|
||||||
// Build query
|
// Build query
|
||||||
if (relayHint) {
|
if (relayHint?.length) {
|
||||||
try {
|
query = await commands.getEventFrom(normalizeId, relayHint);
|
||||||
const url = new URL(relayHint);
|
|
||||||
query = await commands.getEventFrom(normalizeId, url.toString());
|
|
||||||
} catch {
|
|
||||||
query = await commands.getEvent(normalizeId);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
query = await commands.getEvent(normalizeId);
|
query = await commands.getEvent(normalizeId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
export * from "./event";
|
export * from "./event";
|
||||||
export * from "./account";
|
|
||||||
export * from "./query";
|
|
||||||
export * from "./window";
|
export * from "./window";
|
||||||
export * from "./hooks/useEvent";
|
export * from "./hooks/useEvent";
|
||||||
export * from "./hooks/useProfile";
|
export * from "./hooks/useProfile";
|
||||||
|
|||||||
@@ -1,336 +0,0 @@
|
|||||||
import { type Result, type RichEvent, commands } from "@/commands.gen";
|
|
||||||
import type { LumeColumn, Metadata, NostrEvent, Relay } from "@/types";
|
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { readFile } from "@tauri-apps/plugin-fs";
|
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
|
||||||
import { nip19 } from "nostr-tools";
|
|
||||||
import { LumeEvent } from "./event";
|
|
||||||
|
|
||||||
function toLumeEvents(richEvents: RichEvent[]) {
|
|
||||||
const events = richEvents.map((item) => {
|
|
||||||
const nostrEvent: NostrEvent = JSON.parse(item.raw);
|
|
||||||
|
|
||||||
if (item.parsed) {
|
|
||||||
nostrEvent.meta = item.parsed;
|
|
||||||
} else {
|
|
||||||
nostrEvent.meta = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lumeEvent = new LumeEvent(nostrEvent);
|
|
||||||
|
|
||||||
return lumeEvent;
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NostrQuery = {
|
|
||||||
upload: async (filePath?: string) => {
|
|
||||||
const allowExts = [
|
|
||||||
"png",
|
|
||||||
"jpeg",
|
|
||||||
"jpg",
|
|
||||||
"gif",
|
|
||||||
"mp4",
|
|
||||||
"mp3",
|
|
||||||
"webm",
|
|
||||||
"mkv",
|
|
||||||
"avi",
|
|
||||||
"mov",
|
|
||||||
];
|
|
||||||
|
|
||||||
const selected =
|
|
||||||
filePath ||
|
|
||||||
(
|
|
||||||
await open({
|
|
||||||
multiple: false,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "Media",
|
|
||||||
extensions: allowExts,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
).path;
|
|
||||||
|
|
||||||
// User cancelled action
|
|
||||||
if (!selected) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const file = await readFile(selected);
|
|
||||||
const blob = new Blob([file]);
|
|
||||||
|
|
||||||
const data = new FormData();
|
|
||||||
data.append("fileToUpload", blob);
|
|
||||||
data.append("submit", "Upload Image");
|
|
||||||
|
|
||||||
const res = await fetch("https://nostr.build/api/v2/upload/files", {
|
|
||||||
method: "POST",
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) return null;
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
const content = json.data[0];
|
|
||||||
|
|
||||||
return content.url as string;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(String(e));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getNotifications: async () => {
|
|
||||||
const query = await commands.getNotifications();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = query.data.map((item) => JSON.parse(item) as NostrEvent);
|
|
||||||
const events = data.map((ev) => new LumeEvent(ev));
|
|
||||||
|
|
||||||
return events;
|
|
||||||
} else {
|
|
||||||
console.error(query.error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getProfile: async (pubkey: string) => {
|
|
||||||
const normalize = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
|
||||||
const query = await commands.getProfile(normalize);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const profile: Metadata = JSON.parse(query.data);
|
|
||||||
return profile;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getEvent: async (id: string, hint?: string) => {
|
|
||||||
// Validate ID
|
|
||||||
const normalizeId: string = id
|
|
||||||
.replace("nostr:", "")
|
|
||||||
.replace(/[^\w\s]/gi, "");
|
|
||||||
|
|
||||||
// Define query
|
|
||||||
let query: Result<RichEvent, string>;
|
|
||||||
let relayHint: string = hint;
|
|
||||||
|
|
||||||
if (normalizeId.startsWith("nevent1")) {
|
|
||||||
const decoded = nip19.decode(normalizeId);
|
|
||||||
if (decoded.type === "nevent") relayHint = decoded.data.relays[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build query
|
|
||||||
if (relayHint) {
|
|
||||||
try {
|
|
||||||
const url = new URL(relayHint);
|
|
||||||
query = await commands.getEventFrom(normalizeId, url.toString());
|
|
||||||
} catch {
|
|
||||||
query = await commands.getEvent(normalizeId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query = await commands.getEvent(normalizeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = query.data;
|
|
||||||
const raw = JSON.parse(data.raw) as NostrEvent;
|
|
||||||
|
|
||||||
if (data?.parsed) {
|
|
||||||
raw.meta = data.parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = new LumeEvent(raw);
|
|
||||||
|
|
||||||
return event;
|
|
||||||
} else {
|
|
||||||
console.log("[getEvent]: ", query.error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getRepostEvent: async (event: LumeEvent) => {
|
|
||||||
try {
|
|
||||||
const embed: NostrEvent = JSON.parse(event.content);
|
|
||||||
const query = await commands.getEventMeta(embed.content);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
embed.meta = query.data;
|
|
||||||
const lumeEvent = new LumeEvent(embed);
|
|
||||||
return lumeEvent;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
const query = await commands.getEvent(event.repostId);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = query.data;
|
|
||||||
const raw = JSON.parse(data.raw) as NostrEvent;
|
|
||||||
|
|
||||||
if (data?.parsed) {
|
|
||||||
raw.meta = data.parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = new LumeEvent(raw);
|
|
||||||
|
|
||||||
return event;
|
|
||||||
} else {
|
|
||||||
console.log("[getRepostEvent]: ", query.error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getGroupEvents: async (pubkeys: string[], asOf?: number) => {
|
|
||||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
|
||||||
const query = await commands.getGroupEvents(pubkeys, until);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = toLumeEvents(query.data);
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getGlobalEvents: async (asOf?: number) => {
|
|
||||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
|
||||||
const query = await commands.getGlobalEvents(until);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = toLumeEvents(query.data);
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getHashtagEvents: async (hashtags: string[], asOf?: number) => {
|
|
||||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
|
||||||
const nostrTags = hashtags.map((tag) => tag.replace("#", ""));
|
|
||||||
const query = await commands.getHashtagEvents(nostrTags, until);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = toLumeEvents(query.data);
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
verifyNip05: async (pubkey: string, nip05?: string) => {
|
|
||||||
const query = await commands.verifyNip05(pubkey, nip05);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getNstore: async (key: string) => {
|
|
||||||
const query = await commands.getLumeStore(key);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const data = query.data ? JSON.parse(query.data) : null;
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setNstore: async (key: string, value: string) => {
|
|
||||||
const query = await commands.setLumeStore(key, value);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getUserSettings: async () => {
|
|
||||||
const query = await commands.getSettings();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
return query.error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setUserSettings: async (newSettings: string) => {
|
|
||||||
const query = await commands.setNewSettings(newSettings);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
return query.error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setColumns: async (columns: LumeColumn[]) => {
|
|
||||||
const key = "lume_v4:columns";
|
|
||||||
const content = JSON.stringify(columns);
|
|
||||||
const query = await commands.setLumeStore(key, content);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getRelays: async () => {
|
|
||||||
const query = await commands.getRelays();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
connectRelay: async (url: string) => {
|
|
||||||
const relayUrl = new URL(url);
|
|
||||||
|
|
||||||
if (relayUrl.protocol === "wss:" || relayUrl.protocol === "ws:") {
|
|
||||||
const query = await commands.connectRelay(relayUrl.toString());
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeRelay: async (url: string) => {
|
|
||||||
const relayUrl = new URL(url);
|
|
||||||
|
|
||||||
if (relayUrl.protocol === "wss:" || relayUrl.protocol === "ws:") {
|
|
||||||
const query = await commands.removeRelay(relayUrl.toString());
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getBootstrapRelays: async () => {
|
|
||||||
const query = await commands.getBootstrapRelays();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
const relays: Relay[] = [];
|
|
||||||
|
|
||||||
for (const item of query.data) {
|
|
||||||
const line = item.split(",");
|
|
||||||
const url = line[0];
|
|
||||||
const purpose = line[1] ?? "";
|
|
||||||
|
|
||||||
relays.push({ url, purpose });
|
|
||||||
}
|
|
||||||
|
|
||||||
return relays;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveBootstrapRelays: async (relays: string[]) => {
|
|
||||||
const text = relays
|
|
||||||
.map((relay) => Object.values(relay).join(","))
|
|
||||||
.join("\n");
|
|
||||||
const query = await commands.saveBootstrapRelays(text);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return await relaunch();
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -114,7 +114,7 @@ export const LumeWindow = {
|
|||||||
throw new Error(query.error);
|
throw new Error(query.error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openZap: async (id: string) => {
|
openZap: async (id: string, account?: string) => {
|
||||||
const wallet = await commands.loadWallet();
|
const wallet = await commands.loadWallet();
|
||||||
|
|
||||||
if (wallet.status === "ok") {
|
if (wallet.status === "ok") {
|
||||||
@@ -129,7 +129,7 @@ export const LumeWindow = {
|
|||||||
hidden_title: true,
|
hidden_title: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await LumeWindow.openSettings("bitcoin-connect");
|
await LumeWindow.openSettings(account, "bitcoin-connect");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openSettings: async (account: string, path?: string) => {
|
openSettings: async (account: string, path?: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user