From 618a45d3496737251ebf8be265fd061d482e06b4 Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 30 Oct 2024 10:57:43 +0700 Subject: [PATCH] refactor: app settings --- package.json | 2 - pnpm-lock.yaml | 6 - src-tauri/Cargo.lock | 16 +- src-tauri/src/commands/account.rs | 20 +- src-tauri/src/commands/event.rs | 1 + src-tauri/src/commands/metadata.rs | 54 +--- src-tauri/src/main.rs | 42 ++- src/app.tsx | 2 +- src/commands.gen.ts | 39 ++- src/commons.ts | 136 +++++----- src/components/note/buttons/repost.tsx | 17 +- src/components/note/buttons/zap.tsx | 9 +- src/components/note/content.tsx | 14 +- src/components/note/mentions/note.tsx | 7 +- src/components/note/preview/image.tsx | 17 +- src/components/note/preview/images.tsx | 36 +-- src/components/note/preview/video.tsx | 8 +- src/components/reply.tsx | 9 +- src/components/user/avatar.tsx | 29 +-- src/components/user/time.tsx | 6 +- src/routes.gen.ts | 57 ++-- src/routes/$id.set-profile.lazy.tsx | 230 ++++++++++++++++ .../profile.tsx => $id.set-profile.tsx} | 2 +- src/routes/__root.tsx | 20 +- src/routes/_app.lazy.tsx | 36 ++- src/routes/_app/index.lazy.tsx | 25 +- src/routes/_app/index.tsx | 6 +- .../columns/_layout/notification.$id.lazy.tsx | 4 +- .../columns/_layout/stories.$id.lazy.tsx | 4 +- src/routes/settings.$id/general.lazy.tsx | 110 +++++--- src/routes/settings.$id/general.tsx | 16 +- src/routes/settings.$id/profile.lazy.tsx | 245 ------------------ src/system/window.ts | 21 +- 33 files changed, 617 insertions(+), 629 deletions(-) create mode 100644 src/routes/$id.set-profile.lazy.tsx rename src/routes/{settings.$id/profile.tsx => $id.set-profile.tsx} (84%) delete mode 100644 src/routes/settings.$id/profile.lazy.tsx diff --git a/package.json b/package.json index 881aa4eb..b483c5c8 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,6 @@ "@tanstack/query-persist-client-core": "^5.59.16", "@tanstack/react-query": "^5.59.16", "@tanstack/react-router": "^1.77.5", - "@tanstack/react-store": "^0.5.6", - "@tanstack/store": "^0.5.5", "@tauri-apps/api": "^2.0.3", "@tauri-apps/plugin-clipboard-manager": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 569dd986..a0e9cc14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,12 +50,6 @@ importers: '@tanstack/react-router': specifier: ^1.77.5 version: 1.77.5(@tanstack/router-generator@1.74.2)(react-dom@19.0.0-rc-cae764ce-20241025(react@19.0.0-rc-cae764ce-20241025))(react@19.0.0-rc-cae764ce-20241025) - '@tanstack/react-store': - specifier: ^0.5.6 - version: 0.5.6(react-dom@19.0.0-rc-cae764ce-20241025(react@19.0.0-rc-cae764ce-20241025))(react@19.0.0-rc-cae764ce-20241025) - '@tanstack/store': - specifier: ^0.5.5 - version: 0.5.5 '@tauri-apps/api': specifier: ^2.0.3 version: 2.0.3 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index cd38d8cc..89b62fbb 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3480,7 +3480,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "aes", "base64 0.22.1", @@ -3510,7 +3510,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "async-trait", "flatbuffers", @@ -3524,7 +3524,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "heed", "nostr", @@ -3537,7 +3537,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "async-utility", "async-wsocket", @@ -3555,7 +3555,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "async-utility", "atomic-destructor", @@ -3575,7 +3575,7 @@ dependencies = [ [[package]] name = "nostr-signer" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "async-utility", "nostr", @@ -3588,7 +3588,7 @@ dependencies = [ [[package]] name = "nostr-zapper" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "async-trait", "nostr", @@ -3732,7 +3732,7 @@ dependencies = [ [[package]] name = "nwc" version = "0.35.0" -source = "git+https://github.com/rust-nostr/nostr#282fbc39373674b7394806d47311a8e483da0ef0" +source = "git+https://github.com/rust-nostr/nostr#939ebbdbc0b7c605411676e810c775ac3d80ef94" dependencies = [ "async-utility", "nostr", diff --git a/src-tauri/src/commands/account.rs b/src-tauri/src/commands/account.rs index 7a99c00c..66564835 100644 --- a/src-tauri/src/commands/account.rs +++ b/src-tauri/src/commands/account.rs @@ -5,7 +5,7 @@ use specta::Type; use std::{str::FromStr, time::Duration}; use tauri::{Emitter, State}; -use crate::{common::get_all_accounts, Nostr}; +use crate::{common::get_all_accounts, Nostr, Settings}; #[derive(Debug, Clone, Serialize, Deserialize, Type)] struct Account { @@ -249,3 +249,21 @@ pub async fn set_signer( } } } + +#[tauri::command] +#[specta::specta] +pub async fn get_app_settings(state: State<'_, Nostr>) -> Result { + let settings = state.settings.read().await.clone(); + Ok(settings) +} + +#[tauri::command] +#[specta::specta] +pub async fn set_app_settings(settings: String, state: State<'_, Nostr>) -> Result<(), String> { + let parsed: Settings = serde_json::from_str(&settings).map_err(|e| e.to_string())?; + let mut settings = state.settings.write().await; + // Update state + settings.clone_from(&parsed); + + Ok(()) +} diff --git a/src-tauri/src/commands/event.rs b/src-tauri/src/commands/event.rs index 4205442d..9bc665f5 100644 --- a/src-tauri/src/commands/event.rs +++ b/src-tauri/src/commands/event.rs @@ -36,6 +36,7 @@ pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result, - picture: String, - banner: Option, - nip05: Option, - lud16: Option, - website: Option, -} - #[derive(Clone, Serialize, Deserialize, Type)] pub struct Mention { pubkey: String, @@ -116,30 +104,9 @@ pub async fn get_contact_list(id: String, state: State<'_, Nostr>) -> Result) -> Result { +pub async fn set_profile(new_profile: String, state: State<'_, Nostr>) -> Result { let client = &state.client; - let mut metadata = Metadata::new() - .name(profile.name) - .display_name(profile.display_name) - .about(profile.about.unwrap_or_default()) - .nip05(profile.nip05.unwrap_or_default()) - .lud16(profile.lud16.unwrap_or_default()); - - if let Ok(url) = Url::parse(&profile.picture) { - metadata = metadata.picture(url) - } - - if let Some(b) = profile.banner { - if let Ok(url) = Url::parse(&b) { - metadata = metadata.banner(url) - } - } - - if let Some(w) = profile.website { - if let Ok(url) = Url::parse(&w) { - metadata = metadata.website(url) - } - } + let metadata = Metadata::from_json(new_profile).map_err(|e| e.to_string())?; match client.set_metadata(&metadata).await { Ok(id) => Ok(id.to_string()), @@ -612,21 +579,6 @@ pub async fn get_notifications(id: String, state: State<'_, Nostr>) -> Result) -> Result { - Ok(state.settings.lock().await.clone()) -} - -#[tauri::command] -#[specta::specta] -pub async fn set_user_settings(settings: String, state: State<'_, Nostr>) -> Result<(), String> { - let parsed: Settings = serde_json::from_str(&settings).map_err(|e| e.to_string())?; - state.settings.lock().await.clone_from(&parsed); - - Ok(()) -} - #[tauri::command] #[specta::specta] pub async fn verify_nip05(id: String, nip05: &str) -> Result { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 017e39a0..fd926255 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -22,7 +22,7 @@ use tauri::{path::BaseDirectory, Emitter, EventTarget, Listener, Manager}; use tauri_plugin_decorum::WebviewWindowExt; use tauri_plugin_notification::{NotificationExt, PermissionState}; use tauri_specta::{collect_commands, Builder}; -use tokio::{sync::Mutex, sync::RwLock, time::sleep}; +use tokio::{sync::RwLock, time::sleep}; pub mod commands; pub mod common; @@ -30,7 +30,7 @@ pub mod common; pub struct Nostr { client: Client, queue: RwLock>, - settings: Mutex, + settings: RwLock, } #[derive(Serialize, Deserialize, Debug)] @@ -40,37 +40,30 @@ pub struct Payload { #[derive(Clone, Serialize, Deserialize, Type)] pub struct Settings { - proxy: Option, - image_resize_service: Option, - use_relay_hint: bool, + resize_service: bool, content_warning: bool, - trusted_only: bool, display_avatar: bool, display_zap_button: bool, display_repost_button: bool, display_media: bool, - transparent: bool, } impl Default for Settings { fn default() -> Self { Self { - proxy: None, - image_resize_service: Some("https://wsrv.nl".to_string()), - use_relay_hint: true, content_warning: true, - trusted_only: false, + resize_service: true, display_avatar: true, display_zap_button: true, display_repost_button: true, display_media: true, - transparent: true, } } } pub const DEFAULT_DIFFICULTY: u8 = 0; pub const FETCH_LIMIT: usize = 50; +pub const QUEUE_DELAY: u64 = 300; pub const NOTIFICATION_SUB_ID: &str = "lume_notification"; fn main() { @@ -113,8 +106,6 @@ fn main() { zap_event, copy_friend, get_notifications, - get_user_settings, - set_user_settings, verify_nip05, get_meta_from_event, get_event, @@ -138,6 +129,8 @@ fn main() { close_column, close_all_columns, open_window, + get_app_settings, + set_app_settings, ]); #[cfg(debug_assertions)] @@ -182,7 +175,7 @@ fn main() { // Config let opts = Options::new() - .gossip(true) + .gossip(false) .max_avg_latency(Duration::from_millis(300)) .automatic_authentication(true) .connection_timeout(Some(Duration::from_secs(5))) @@ -238,7 +231,7 @@ fn main() { app.manage(Nostr { client, queue: RwLock::new(HashSet::new()), - settings: Mutex::new(Settings::default()), + settings: RwLock::new(Settings::default()), }); // Listen for request metadata @@ -250,22 +243,27 @@ fn main() { tauri::async_runtime::spawn(async move { let state = handle.state::(); let client = &state.client; + let mut write_queue = state.queue.write().await; if let Ok(public_key) = PublicKey::parse(parsed_payload.id) { - let mut write_queue = state.queue.write().await; write_queue.insert(public_key); } - sleep(Duration::from_millis(300)).await; + sleep(Duration::from_millis(QUEUE_DELAY)).await; let read_queue = state.queue.read().await; + + let filter_opts = FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(2)); + let opts = SubscribeAutoCloseOptions::default().filter(filter_opts); + + let limit = read_queue.len() * 2; let authors: Vec = read_queue.iter().copied().collect(); - let filter = Filter::new().authors(authors).kind(Kind::Metadata); - let opts = SubscribeAutoCloseOptions::default() - .filter(FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(3))); + let filter = Filter::new() + .authors(authors) + .kind(Kind::Metadata) + .limit(limit); if client.subscribe(vec![filter], Some(opts)).await.is_ok() { - let mut write_queue = state.queue.write().await; write_queue.clear(); } }); diff --git a/src/app.tsx b/src/app.tsx index 428b041c..8da958e5 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -45,7 +45,7 @@ broadcastQueryClient({ const router = createRouter({ routeTree, - context: { queryClient, platform }, + context: { store, queryClient, platform }, Wrap: ({ children }) => { return ( {children} diff --git a/src/commands.gen.ts b/src/commands.gen.ts index 5da3edda..dcd0f64a 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -136,9 +136,9 @@ async getProfile(id: string) : Promise> { else return { status: "error", error: e as any }; } }, -async setProfile(profile: Profile) : Promise> { +async setProfile(newProfile: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("set_profile", { profile }) }; + return { status: "ok", data: await TAURI_INVOKE("set_profile", { newProfile }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -288,22 +288,6 @@ async getNotifications(id: string) : Promise> { else return { status: "error", error: e as any }; } }, -async getUserSettings() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_user_settings") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async setUserSettings(settings: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_user_settings", { settings }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, async verifyNip05(id: string, nip05: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { id, nip05 }) }; @@ -487,6 +471,22 @@ async openWindow(window: NewWindow) : Promise> { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } +}, +async getAppSettings() : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_app_settings") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setAppSettings(settings: string) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("set_app_settings", { settings }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} } } @@ -504,10 +504,9 @@ export type Column = { label: string; url: string; x: number; y: number; width: export type Mention = { pubkey: string; avatar: string; display_name: string; name: string } export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] } export type NewWindow = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean } -export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null } export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null } export type RichEvent = { raw: string; parsed: Meta | null } -export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; trusted_only: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean; transparent: boolean } +export type Settings = { resize_service: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean } export type TAURI_CHANNEL = null /** tauri-specta globals **/ diff --git a/src/commons.ts b/src/commons.ts index d95a2c54..96aa9d34 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -3,7 +3,6 @@ import type { MaybePromise, PersistedQuery, } from "@tanstack/query-persist-client-core"; -import { Store } from "@tanstack/react-store"; import { ask, message, open } from "@tauri-apps/plugin-dialog"; import { readFile } from "@tauri-apps/plugin-fs"; import { relaunch } from "@tauri-apps/plugin-process"; @@ -16,76 +15,79 @@ import relativeTime from "dayjs/plugin/relativeTime"; import updateLocale from "dayjs/plugin/updateLocale"; import { decode } from "light-bolt11-decoder"; import { twMerge } from "tailwind-merge"; -import type { RichEvent, Settings } from "./commands.gen"; +import type { RichEvent } from "./commands.gen"; import { LumeEvent } from "./system"; -import type { LumeColumn, NostrEvent } from "./types"; - -dayjs.extend(relativeTime); -dayjs.extend(updateLocale); - -dayjs.updateLocale("en", { - relativeTime: { - past: "%s ago", - s: "just now", - m: "1m", - mm: "%dm", - h: "1h", - hh: "%dh", - d: "1d", - dd: "%dd", - }, -}); +import type { NostrEvent } from "./types"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export const isImagePath = (path: string) => { - for (const suffix of ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"]) { - if (path.endsWith(suffix)) return true; + const exts = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"]; + + for (const suffix of exts) { + if (path.endsWith(suffix)) { + return true; + } } + return false; }; -export const isImageUrl = (url: string) => { - try { - if (!url) return false; - const ext = new URL(url).pathname.split(".").pop(); - return ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"].includes(ext); - } catch { - return false; - } -}; +export function createdAt(time: number) { + // Config for dayjs + dayjs.extend(relativeTime); + dayjs.extend(updateLocale); -export function formatCreatedAt(time: number, message = false) { - let formated: string; + // Config locale text + dayjs.updateLocale("en", { + relativeTime: { + past: "%s ago", + s: "just now", + m: "1m", + mm: "%dm", + h: "1h", + hh: "%dh", + d: "1d", + dd: "%dd", + }, + }); const now = dayjs(); const inputTime = dayjs.unix(time); const diff = now.diff(inputTime, "hour"); - if (message) { - if (diff < 12) { - formated = inputTime.format("HH:mm A"); - } else { - formated = inputTime.format("MMM DD"); - } + if (diff < 24) { + return inputTime.from(now, true); } else { - if (diff < 24) { - formated = inputTime.from(now, true); - } else { - formated = inputTime.format("MMM DD"); - } + return inputTime.format("MMM DD"); } - - return formated; } -export function replyTime(time: number) { - const inputTime = dayjs.unix(time); - const formated = inputTime.format("MM-DD-YY HH:mm"); +export function replyAt(time: number) { + // Config for dayjs + dayjs.extend(relativeTime); + dayjs.extend(updateLocale); - return formated; + // Config locale text + dayjs.updateLocale("en", { + relativeTime: { + past: "%s ago", + s: "just now", + m: "1m", + mm: "%dm", + h: "1h", + hh: "%dh", + d: "1d", + dd: "%dd", + }, + }); + + const inputTime = dayjs.unix(time); + const format = inputTime.format("MM-DD-YY HH:mm"); + + return format; } export function displayNpub(pubkey: string, len: number) { @@ -114,7 +116,7 @@ export function displayLongHandle(str: string) { return `${handle.substring(0, 16)}...@${service}`; } -// source: https://github.com/synonymdev/bitkit/blob/master/src/utils/displayValues/index.ts +// Source: https://github.com/synonymdev/bitkit/blob/master/src/utils/displayValues/index.ts export function getBitcoinDisplayValues(satoshis: number) { let bitcoinFormatted = new BitcoinUnit(satoshis, "satoshis") .getValue() @@ -145,20 +147,27 @@ export function getBitcoinDisplayValues(satoshis: number) { }; } -export function decodeZapInvoice(tags?: string[][]) { +export function decodeZapInvoice(tags: string[][]) { const invoice = tags.find((tag) => tag[0] === "bolt11")?.[1]; if (!invoice) return; const decodedInvoice = decode(invoice); - const amountSection = decodedInvoice.sections.find( + const section = decodedInvoice.sections.find( (s: { name: string }) => s.name === "amount", ); - // @ts-ignore, its fine. - const amount = Number.parseInt(amountSection.value) / 1000; - const displayValue = getBitcoinDisplayValues(amount); + if (!section) { + return null; + } - return displayValue; + if (section.name === "amount") { + const amount = Number.parseInt(section.value) / 1000; + const displayValue = getBitcoinDisplayValues(amount); + + return displayValue; + } else { + return null; + } } export async function checkForAppUpdates(silent: boolean) { @@ -277,18 +286,3 @@ export function newQueryStorage( (await store.delete(key)) as unknown as MaybePromise, }; } - -export const appSettings = new Store({ - proxy: null, - image_resize_service: "https://wsrv.nl", - use_relay_hint: true, - content_warning: true, - trusted_only: true, - display_avatar: true, - display_zap_button: true, - display_repost_button: true, - display_media: true, - transparent: true, -}); - -export const appColumns = new Store([]); diff --git a/src/components/note/buttons/repost.tsx b/src/components/note/buttons/repost.tsx index 8c5a7a5d..e2e652ec 100644 --- a/src/components/note/buttons/repost.tsx +++ b/src/components/note/buttons/repost.tsx @@ -1,10 +1,15 @@ import { commands } from "@/commands.gen"; -import { appSettings, cn, displayNpub } from "@/commons"; +import { cn, displayNpub } from "@/commons"; import { RepostIcon, Spinner } from "@/components"; +import { settingsQueryOptions } from "@/routes/__root"; import type { Metadata } from "@/types"; import * as Tooltip from "@radix-ui/react-tooltip"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useStore } from "@tanstack/react-store"; +import { + useMutation, + useQuery, + useQueryClient, + useSuspenseQuery, +} from "@tanstack/react-query"; import { Menu, MenuItem } from "@tauri-apps/api/menu"; import { message } from "@tauri-apps/plugin-dialog"; import { useCallback, useTransition } from "react"; @@ -15,7 +20,7 @@ export function NoteRepost({ smol = false, }: { label?: boolean; smol?: boolean }) { const event = useNoteContext(); - const visible = useStore(appSettings, (state) => state.display_repost_button); + const settings = useSuspenseQuery(settingsQueryOptions); const queryClient = useQueryClient(); const { isLoading, data: status } = useQuery({ @@ -28,7 +33,7 @@ export function NoteRepost({ return false; } }, - enabled: visible, + enabled: settings.data.display_repost_button, refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, @@ -120,7 +125,7 @@ export function NoteRepost({ }); }; - if (!visible) return null; + if (!settings.data.display_repost_button) return null; return ( diff --git a/src/components/note/buttons/zap.tsx b/src/components/note/buttons/zap.tsx index 5d49ebc6..621b6ae8 100644 --- a/src/components/note/buttons/zap.tsx +++ b/src/components/note/buttons/zap.tsx @@ -1,8 +1,9 @@ -import { appSettings, cn } from "@/commons"; +import { cn } from "@/commons"; import { ZapIcon } from "@/components"; +import { settingsQueryOptions } from "@/routes/__root"; import { LumeWindow } from "@/system"; +import { useSuspenseQuery } from "@tanstack/react-query"; import { useSearch } from "@tanstack/react-router"; -import { useStore } from "@tanstack/react-store"; import { useNoteContext } from "../provider"; export function NoteZap({ @@ -10,10 +11,10 @@ export function NoteZap({ smol = false, }: { label?: boolean; smol?: boolean }) { const search = useSearch({ strict: false }); - const visible = useStore(appSettings, (state) => state.display_zap_button); + const settings = useSuspenseQuery(settingsQueryOptions); const event = useNoteContext(); - if (!visible) return null; + if (!settings.data.display_zap_button) return null; return ( + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + +
+ + + ); +} + +function AvatarUploader({ + setPicture, + children, + className, +}: { + setPicture: Dispatch>; + children: ReactNode; + className?: string; +}) { + const [isPending, startTransition] = useTransition(); + + const uploadAvatar = () => { + startTransition(async () => { + try { + const image = await upload(); + + if (image) { + setPicture(image); + } + } catch (e) { + await message(String(e)); + return; + } + }); + }; + + return ( + + ); +} diff --git a/src/routes/settings.$id/profile.tsx b/src/routes/$id.set-profile.tsx similarity index 84% rename from src/routes/settings.$id/profile.tsx rename to src/routes/$id.set-profile.tsx index 640ca1cd..4c5fcad9 100644 --- a/src/routes/settings.$id/profile.tsx +++ b/src/routes/$id.set-profile.tsx @@ -1,7 +1,7 @@ import { type Profile, commands } from "@/commands.gen"; import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/settings/$id/profile")({ +export const Route = createFileRoute("/$id/set-profile")({ beforeLoad: async ({ params }) => { const res = await commands.getProfile(params.id); diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 333585cf..2b234383 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,20 +1,38 @@ +import { commands } from "@/commands.gen"; import { Spinner } from "@/components"; import type { Metadata, NostrEvent } from "@/types"; -import type { QueryClient } from "@tanstack/react-query"; +import { type QueryClient, queryOptions } from "@tanstack/react-query"; import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { getCurrentWindow } from "@tauri-apps/api/window"; import type { OsType } from "@tauri-apps/plugin-os"; +import type { Store } from "@tauri-apps/plugin-store"; import { useEffect } from "react"; interface RouterContext { + store: Store; queryClient: QueryClient; platform: OsType; accounts?: string[]; } +export const settingsQueryOptions = queryOptions({ + queryKey: ["settings"], + queryFn: async () => { + const res = await commands.getAppSettings(); + + if (res.status === "ok") { + return res.data; + } else { + throw new Error(res.error); + } + }, +}); + export const Route = createRootRouteWithContext()({ component: Screen, pendingComponent: Pending, + loader: ({ context }) => + context.queryClient.ensureQueryData(settingsQueryOptions), }); function Screen() { diff --git a/src/routes/_app.lazy.tsx b/src/routes/_app.lazy.tsx index 0c5a8b7d..c68b7f2b 100644 --- a/src/routes/_app.lazy.tsx +++ b/src/routes/_app.lazy.tsx @@ -1,5 +1,5 @@ import { commands } from "@/commands.gen"; -import { appColumns, cn } from "@/commons"; +import { cn } from "@/commons"; import { PublishIcon } from "@/components"; import { User } from "@/components/user"; import { LumeWindow } from "@/system"; @@ -14,6 +14,7 @@ import { import { listen } from "@tauri-apps/api/event"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { writeText } from "@tauri-apps/plugin-clipboard-manager"; +import { message } from "@tauri-apps/plugin-dialog"; import { useCallback, useEffect } from "react"; export const Route = createLazyFileRoute("/_app")({ @@ -107,7 +108,7 @@ function Account({ pubkey }: { pubkey: string }) { const items = await Promise.all([ MenuItem.new({ - text: "Activate", + text: "Unlock", enabled: !isActive || true, action: async () => await commands.setSigner(pubkey), }), @@ -116,10 +117,31 @@ function Account({ pubkey }: { pubkey: string }) { text: "View Profile", action: () => LumeWindow.openProfile(pubkey), }), + MenuItem.new({ + text: "Update Profile", + action: () => + LumeWindow.openPopup( + `${pubkey}/set-profile`, + "Update Profile", + true, + ), + }), + PredefinedMenuItem.new({ item: "Separator" }), MenuItem.new({ text: "Copy Public Key", action: async () => await writeText(pubkey), }), + MenuItem.new({ + text: "Copy Private Key", + action: async () => { + const res = await commands.getPrivateKey(pubkey); + if (res.status === "ok") { + await writeText(res.data); + } else { + await message(res.error, { kind: "error" }); + } + }, + }), PredefinedMenuItem.new({ item: "Separator" }), MenuItem.new({ text: "Settings", @@ -127,20 +149,13 @@ function Account({ pubkey }: { pubkey: string }) { }), PredefinedMenuItem.new({ item: "Separator" }), MenuItem.new({ - text: "Delete Account", + text: "Logout", action: async () => { const res = await commands.deleteAccount(pubkey); if (res.status === "ok") { router.invalidate(); - // Delete column associate with this account - appColumns.setState((prev) => - prev.filter((col) => - col.account ? col.account !== pubkey : col, - ), - ); - // Check remain account const newAccounts = context.accounts.filter( (account) => account !== pubkey, @@ -176,6 +191,7 @@ function Account({ pubkey }: { pubkey: string }) { - - - - ); -} - -function PrivkeyButton() { - const { id } = Route.useParams(); - - const [isPending, startTransition] = useTransition(); - const [isCopy, setIsCopy] = useState(false); - - const copyPrivateKey = () => { - startTransition(async () => { - const res = await commands.getPrivateKey(id); - - if (res.status === "ok") { - await writeText(res.data); - setIsCopy(true); - } else { - await message(res.error, { kind: "error" }); - return; - } - }); - }; - - return ( - - ); -} - -function AvatarUploader({ - setPicture, - children, - className, -}: { - setPicture: Dispatch>; - children: ReactNode; - className?: string; -}) { - const [isPending, startTransition] = useTransition(); - - const uploadAvatar = () => { - startTransition(async () => { - try { - const image = await upload(); - setPicture(image); - } catch (e) { - await message(String(e)); - return; - } - }); - }; - - return ( - - ); -} diff --git a/src/system/window.ts b/src/system/window.ts index 4b3a1d0c..1ce8c5a3 100644 --- a/src/system/window.ts +++ b/src/system/window.ts @@ -97,21 +97,12 @@ export const LumeWindow = { }); }, openEditor: async (reply_to?: string, quote?: string) => { - let url: string; - - if (reply_to) { - url = `/new-post?reply_to=${reply_to}`; - } - - if (quote?.length) { - url = `/new-post?quote=${quote}`; - } - - if (!reply_to?.length && !quote?.length) { - url = "/new-post"; - } - const label = `editor-${reply_to ? reply_to : 0}`; + const url = reply_to + ? `/new-post?reply_to=${reply_to}` + : quote?.length + ? `/new-post?quote=${quote}` + : "/new-post"; const query = await commands.openWindow({ label, url, @@ -145,7 +136,7 @@ export const LumeWindow = { hidden_title: true, closable: true, }); - } else { + } else if (account) { await LumeWindow.openSettings(account, "wallet"); } },