diff --git a/src-tauri/resources/columns.json b/src-tauri/resources/columns.json index 8120758b..bfebe7e5 100644 --- a/src-tauri/resources/columns.json +++ b/src-tauri/resources/columns.json @@ -1,74 +1,37 @@ [ { "default": true, - "official": true, "label": "onboarding", "name": "Onboarding", "description": "Tips for Mastering Lume.", - "url": "/columns/onboarding", - "picture": "" + "url": "/columns/onboarding" }, { "default": true, - "official": true, "label": "Launchpad", "name": "Launchpad", "description": "Expand your experiences.", - "url": "/columns/launchpad", - "picture": "" + "url": "/columns/launchpad" }, { "default": false, - "official": true, - "label": "newsfeed", - "name": "Newsfeed", - "description": "All notes from you're following.", - "url": "/columns/newsfeed", - "picture": "" - }, - { - "default": false, - "official": true, - "label": "notification", - "name": "Notification", - "description": "All things around you.", - "url": "/columns/notification", - "picture": "" - }, - { - "default": false, - "official": true, "label": "search", "name": "Search", "description": "Find anything.", - "url": "/columns/search", - "picture": "" + "url": "/columns/search" }, { "default": false, - "official": true, - "label": "stories", - "name": "Stories", - "description": "Keep up to date with your follows.", - "url": "/columns/stories", - "picture": "" - }, - { - "default": false, - "official": true, "label": "global_feeds", "name": "Global Feeds", "description": "All global notes from all connected relays.", - "url": "/columns/global", - "picture": "" + "url": "/columns/global" }, { "default": false, - "official": true, "label": "trending", "name": "Trending", "description": "Discover all trending notes.", - "url": "/columns/trending", - "picture": "" + "url": "/columns/trending" } ] diff --git a/src-tauri/src/commands/account.rs b/src-tauri/src/commands/account.rs index b30191cb..c6e8047a 100644 --- a/src-tauri/src/commands/account.rs +++ b/src-tauri/src/commands/account.rs @@ -1,5 +1,4 @@ use keyring::Entry; -use keyring_search::{Limit, List, Search}; use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use specta::Type; @@ -11,7 +10,7 @@ use std::{ }; use tauri::{Emitter, Manager, State}; -use crate::{Nostr, NOTIFICATION_SUB_ID}; +use crate::{common::get_all_accounts, Nostr, NOTIFICATION_SUB_ID}; #[derive(Debug, Clone, Serialize, Deserialize, Type)] struct Account { @@ -22,16 +21,7 @@ struct Account { #[tauri::command] #[specta::specta] pub fn get_accounts() -> Vec { - let search = Search::new().expect("Unexpected."); - let results = search.by_service("Lume Secret Storage"); - let list = List::list_credentials(&results, Limit::All); - let accounts: HashSet = list - .split_whitespace() - .filter(|v| v.starts_with("npub1")) - .map(String::from) - .collect(); - - accounts.into_iter().collect() + get_all_accounts() } #[tauri::command] @@ -235,7 +225,7 @@ pub async fn login( password: String, state: State<'_, Nostr>, handle: tauri::AppHandle, -) -> Result { +) -> Result<(), String> { let client = &state.client; let keyring = Entry::new("Lume Secret Storage", &account).map_err(|e| e.to_string())?; @@ -255,7 +245,7 @@ pub async fn login( .to_secret_key(password) .map_err(|_| "Wrong password.")?; let keys = Keys::new(secret_key); - let public_key = keys.public_key().to_bech32().unwrap(); + let public_key = keys.public_key(); let signer = NostrSigner::Keys(keys); // Update signer @@ -265,7 +255,7 @@ pub async fn login( } Some(bunker) => { let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?; - let public_key = uri.signer_public_key().unwrap().to_bech32().unwrap(); + let public_key = uri.signer_public_key().unwrap(); let app_keys = Keys::from_str(&account.password).map_err(|e| e.to_string())?; match Nip46Signer::new(uri, app_keys, Duration::from_secs(120), None) { @@ -285,22 +275,25 @@ pub async fn login( // NIP-03: Get user's contact list let contact_list = { if let Ok(contacts) = client.get_contact_list(Some(Duration::from_secs(5))).await { - state.contact_list.lock().unwrap().clone_from(&contacts); + state + .contact_list + .lock() + .unwrap() + .insert(public_key, contacts.clone()); + contacts } else { Vec::new() } }; - let public_key_clone = public_key.clone(); - // Run seperate thread for sync tauri::async_runtime::spawn(async move { let state = handle.state::(); let client = &state.client; + let author = public_key; let bootstrap_relays: Vec = client.pool().all_relays().await.into_keys().collect(); - let author = PublicKey::from_str(&public_key).unwrap(); // Subscribe for new notification if let Ok(e) = client @@ -453,5 +446,5 @@ pub async fn login( .expect("Something wrong!"); }); - Ok(public_key_clone) + Ok(()) } diff --git a/src-tauri/src/commands/metadata.rs b/src-tauri/src/commands/metadata.rs index d29046bd..8f4af983 100644 --- a/src-tauri/src/commands/metadata.rs +++ b/src-tauri/src/commands/metadata.rs @@ -7,7 +7,7 @@ use tauri::{Emitter, Manager, State}; use tauri_specta::Event; use crate::{ - common::{get_latest_event, process_event}, + common::{get_all_accounts, get_latest_event, process_event}, NewSettings, Nostr, RichEvent, Settings, }; @@ -104,14 +104,17 @@ pub async fn set_contact_list( #[tauri::command] #[specta::specta] -pub fn get_contact_list(state: State<'_, Nostr>) -> Result, String> { - let contact_list = state.contact_list.lock().unwrap().clone(); - let vec: Vec = contact_list - .into_iter() - .map(|f| f.public_key.to_hex()) - .collect(); +pub fn get_contact_list(id: String, state: State<'_, Nostr>) -> Result, String> { + let contact_state = state.contact_list.lock().unwrap().clone(); + let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?; - Ok(vec) + match contact_state.get(&public_key) { + Some(contact_list) => { + let vec: Vec = contact_list.iter().map(|f| f.public_key.to_hex()).collect(); + Ok(vec) + } + None => Err("Contact list is empty.".into()), + } } #[tauri::command] @@ -150,12 +153,15 @@ pub async fn set_profile(profile: Profile, state: State<'_, Nostr>) -> Result) -> Result { - let contact_list = &state.contact_list.lock().unwrap(); + let contact_state = &state.contact_list.lock().unwrap(); let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?; - match contact_list.iter().position(|x| x.public_key == public_key) { - Some(_) => Ok(true), - None => Ok(false), + match contact_state.get(&public_key) { + Some(contact_list) => match contact_list.iter().position(|x| x.public_key == public_key) { + Some(_) => Ok(true), + None => Ok(false), + }, + None => Err("Contact list is empty.".into()), } } @@ -267,9 +273,18 @@ pub async fn get_group(id: String, state: State<'_, Nostr>) -> Result) -> Result, String> { let client = &state.client; - let signer = client.signer().await.map_err(|e| e.to_string())?; - let public_key = signer.public_key().await.map_err(|e| e.to_string())?; - let filter = Filter::new().kind(Kind::FollowSet).author(public_key); + let accounts = get_all_accounts(); + let authors: Vec = accounts + .iter() + .filter_map(|acc| { + if let Ok(pk) = PublicKey::from_str(acc) { + Some(pk) + } else { + None + } + }) + .collect(); + let filter = Filter::new().kind(Kind::FollowSet).authors(authors); match client.database().query(vec![filter]).await { Ok(events) => Ok(process_event(client, events).await), @@ -347,11 +362,20 @@ pub async fn get_interest(id: String, state: State<'_, Nostr>) -> Result) -> Result, String> { let client = &state.client; - let signer = client.signer().await.map_err(|e| e.to_string())?; - let public_key = signer.public_key().await.map_err(|e| e.to_string())?; + let accounts = get_all_accounts(); + let authors: Vec = accounts + .iter() + .filter_map(|acc| { + if let Ok(pk) = PublicKey::from_str(acc) { + Some(pk) + } else { + None + } + }) + .collect(); let filter = Filter::new() .kinds(vec![Kind::InterestSet, Kind::Interests]) - .author(public_key); + .authors(authors); match client.database().query(vec![filter]).await { Ok(events) => Ok(process_event(client, events).await), diff --git a/src-tauri/src/commands/sync.rs b/src-tauri/src/commands/sync.rs index ed31f19b..2f929401 100644 --- a/src-tauri/src/commands/sync.rs +++ b/src-tauri/src/commands/sync.rs @@ -151,13 +151,6 @@ pub fn run_sync(handle: tauri::AppHandle, reader: Channel) { }; }; - reader - .send(NegentropyEvent::Progress { - message: "Ok".to_string(), - total_event: 0, - }) - .unwrap(); - let tagged = Filter::new() .pubkeys(public_keys) .kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt]) @@ -170,10 +163,17 @@ pub fn run_sync(handle: tauri::AppHandle, reader: Channel) { { reader .send(NegentropyEvent::Progress { - message: "Syncing all tagged events for your accounts.".to_string(), + message: "Syncing all tagged events for your accounts".to_string(), total_event: report.received.len() as i32, }) .unwrap(); } + + reader + .send(NegentropyEvent::Progress { + message: "Ok".to_string(), + total_event: 0, + }) + .unwrap(); }); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4df86c07..6b3812b1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use specta::Type; use specta_typescript::Typescript; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, fs, io::{self, BufRead}, str::FromStr, @@ -30,7 +30,7 @@ pub mod common; pub struct Nostr { client: Client, settings: Mutex, - contact_list: Mutex>, + contact_list: Mutex>>, trusted_list: Mutex>, } @@ -287,10 +287,11 @@ fn main() { app.manage(Nostr { client, settings: Mutex::new(Settings::default()), - contact_list: Mutex::new(Vec::new()), + contact_list: Mutex::new(HashMap::new()), trusted_list: Mutex::new(HashSet::new()), }); + /* Subscription::listen_any(app, move |event| { let handle = handle_clone_child.to_owned(); let payload = event.payload; @@ -318,6 +319,7 @@ fn main() { } None => { let contact_list = state.contact_list.lock().unwrap().clone(); + if !contact_list.is_empty() { let authors: Vec = contact_list.iter().map(|f| f.public_key).collect(); @@ -344,6 +346,7 @@ fn main() { } }); }); + */ // Run local relay thread //tauri::async_runtime::spawn(async move { diff --git a/src/commands.gen.ts b/src/commands.gen.ts index 374f46d2..3cd0a4e7 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -105,7 +105,7 @@ async isAccountSync(id: string) : Promise { async createSyncFile(id: string) : Promise { return await TAURI_INVOKE("create_sync_file", { id }); }, -async login(account: string, password: string) : Promise> { +async login(account: string, password: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("login", { account, password }) }; } catch (e) { @@ -129,9 +129,9 @@ async setProfile(profile: Profile) : Promise> { else return { status: "error", error: e as any }; } }, -async getContactList() : Promise> { +async getContactList(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_contact_list") }; + return { status: "ok", data: await TAURI_INVOKE("get_contact_list", { id }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; diff --git a/src/components/column.tsx b/src/components/column.tsx index 06d29ff3..997f9bf3 100644 --- a/src/components/column.tsx +++ b/src/components/column.tsx @@ -3,21 +3,16 @@ import { appColumns } from "@/commons"; import { useRect } from "@/system"; import type { LumeColumn } from "@/types"; import { CaretDown, Check } from "@phosphor-icons/react"; -import { useParams } from "@tanstack/react-router"; import { useStore } from "@tanstack/react-store"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { useCallback, useEffect, useMemo, useState } from "react"; export function Column({ column }: { column: LumeColumn }) { - const params = useParams({ strict: false }); - const webviewLabel = useMemo( - () => `column-${params.account}_${column.label}`, - [params.account, column.label], - ); + const webviewLabel = useMemo(() => `column-${column.label}`, [column.label]); const [rect, ref] = useRect(); - const [error, setError] = useState(null); + const [_error, setError] = useState(null); useEffect(() => { (async () => { @@ -52,7 +47,7 @@ export function Column({ column }: { column: LumeColumn }) { y: initialRect.y, width: initialRect.width, height: initialRect.height, - url: `${column.url}?account=${params.account}&label=${column.label}&name=${column.name}`, + url: `${column.url}?label=${column.label}&name=${column.name}`, }) .then((res) => { if (res.status === "ok") { @@ -73,7 +68,7 @@ export function Column({ column }: { column: LumeColumn }) { }); }; } - }, [params.account]); + }, []); return (
@@ -94,14 +89,11 @@ function Header({ label }: { label: string }) { ); const saveNewTitle = async () => { - const mainWindow = getCurrentWindow(); - await mainWindow.emit("columns", { type: "set_title", label, title }); - - // update search params - // @ts-ignore, hahaha - search.name = title; - - // reset state + await getCurrentWindow().emit("columns", { + type: "set_title", + label, + title, + }); setIsChanged(false); }; diff --git a/src/routes.gen.ts b/src/routes.gen.ts index 3f47bf65..91d8bac8 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -15,23 +15,21 @@ import { createFileRoute } from '@tanstack/react-router' import { Route as rootRoute } from './routes/__root' import { Route as SetInterestImport } from './routes/set-interest' import { Route as SetGroupImport } from './routes/set-group' -import { Route as LoadingImport } from './routes/loading' import { Route as BootstrapRelaysImport } from './routes/bootstrap-relays' -import { Route as IndexImport } from './routes/index' +import { Route as LayoutImport } from './routes/_layout' import { Route as EditorIndexImport } from './routes/editor/index' +import { Route as LayoutIndexImport } from './routes/_layout/index' import { Route as ZapIdImport } from './routes/zap.$id' import { Route as ColumnsLayoutImport } from './routes/columns/_layout' -import { Route as AccountBackupImport } from './routes/$account/backup' -import { Route as AccountAppImport } from './routes/$account/_app' -import { Route as ColumnsLayoutStoriesImport } from './routes/columns/_layout/stories' -import { Route as ColumnsLayoutNewsfeedImport } from './routes/columns/_layout/newsfeed' +import { Route as SettingsWalletImport } from './routes/_settings/wallet' +import { Route as SettingsRelayImport } from './routes/_settings/relay' +import { Route as SettingsProfileImport } from './routes/_settings/profile' +import { Route as SettingsGeneralImport } from './routes/_settings/general' +import { Route as SettingsBitcoinConnectImport } from './routes/_settings/bitcoin-connect' import { Route as ColumnsLayoutGlobalImport } from './routes/columns/_layout/global' import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed' -import { Route as AccountSettingsWalletImport } from './routes/$account/_settings/wallet' -import { Route as AccountSettingsRelayImport } from './routes/$account/_settings/relay' -import { Route as AccountSettingsProfileImport } from './routes/$account/_settings/profile' -import { Route as AccountSettingsGeneralImport } from './routes/$account/_settings/general' -import { Route as AccountSettingsBitcoinConnectImport } from './routes/$account/_settings/bitcoin-connect' +import { Route as ColumnsLayoutStoriesIdImport } from './routes/columns/_layout/stories.$id' +import { Route as ColumnsLayoutNewsfeedIdImport } from './routes/columns/_layout/newsfeed.$id' import { Route as ColumnsLayoutInterestsIdImport } from './routes/columns/_layout/interests.$id' import { Route as ColumnsLayoutGroupsIdImport } from './routes/columns/_layout/groups.$id' import { Route as ColumnsLayoutCreateNewsfeedUsersImport } from './routes/columns/_layout/create-newsfeed.users' @@ -40,13 +38,12 @@ import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/ // Create Virtual Routes const ColumnsImport = createFileRoute('/columns')() -const AccountImport = createFileRoute('/$account')() const ResetLazyImport = createFileRoute('/reset')() const NewLazyImport = createFileRoute('/new')() +const SettingsLazyImport = createFileRoute('/_settings')() const AuthNewLazyImport = createFileRoute('/auth/new')() const AuthImportLazyImport = createFileRoute('/auth/import')() const AuthConnectLazyImport = createFileRoute('/auth/connect')() -const AccountSettingsLazyImport = createFileRoute('/$account/_settings')() const ColumnsLayoutTrendingLazyImport = createFileRoute( '/columns/_layout/trending', )() @@ -56,19 +53,18 @@ const ColumnsLayoutSearchLazyImport = createFileRoute( const ColumnsLayoutOnboardingLazyImport = createFileRoute( '/columns/_layout/onboarding', )() -const ColumnsLayoutNotificationLazyImport = createFileRoute( - '/columns/_layout/notification', -)() const ColumnsLayoutLaunchpadLazyImport = createFileRoute( '/columns/_layout/launchpad', )() -const AccountAppHomeLazyImport = createFileRoute('/$account/_app/home')() const ColumnsLayoutUsersIdLazyImport = createFileRoute( '/columns/_layout/users/$id', )() const ColumnsLayoutRepliesIdLazyImport = createFileRoute( '/columns/_layout/replies/$id', )() +const ColumnsLayoutNotificationIdLazyImport = createFileRoute( + '/columns/_layout/notification/$id', +)() const ColumnsLayoutEventsIdLazyImport = createFileRoute( '/columns/_layout/events/$id', )() @@ -80,11 +76,6 @@ const ColumnsRoute = ColumnsImport.update({ getParentRoute: () => rootRoute, } as any) -const AccountRoute = AccountImport.update({ - path: '/$account', - getParentRoute: () => rootRoute, -} as any) - const ResetLazyRoute = ResetLazyImport.update({ path: '/reset', getParentRoute: () => rootRoute, @@ -95,6 +86,11 @@ const NewLazyRoute = NewLazyImport.update({ getParentRoute: () => rootRoute, } as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route)) +const SettingsLazyRoute = SettingsLazyImport.update({ + id: '/_settings', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/_settings.lazy').then((d) => d.Route)) + const SetInterestRoute = SetInterestImport.update({ path: '/set-interest', getParentRoute: () => rootRoute, @@ -105,11 +101,6 @@ const SetGroupRoute = SetGroupImport.update({ getParentRoute: () => rootRoute, } as any).lazy(() => import('./routes/set-group.lazy').then((d) => d.Route)) -const LoadingRoute = LoadingImport.update({ - path: '/loading', - getParentRoute: () => rootRoute, -} as any) - const BootstrapRelaysRoute = BootstrapRelaysImport.update({ path: '/bootstrap-relays', getParentRoute: () => rootRoute, @@ -117,16 +108,21 @@ const BootstrapRelaysRoute = BootstrapRelaysImport.update({ import('./routes/bootstrap-relays.lazy').then((d) => d.Route), ) -const IndexRoute = IndexImport.update({ - path: '/', +const LayoutRoute = LayoutImport.update({ + id: '/_layout', getParentRoute: () => rootRoute, -} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route)) +} as any).lazy(() => import('./routes/_layout.lazy').then((d) => d.Route)) const EditorIndexRoute = EditorIndexImport.update({ path: '/editor/', getParentRoute: () => rootRoute, } as any) +const LayoutIndexRoute = LayoutIndexImport.update({ + path: '/', + getParentRoute: () => LayoutRoute, +} as any).lazy(() => import('./routes/_layout/index.lazy').then((d) => d.Route)) + const AuthNewLazyRoute = AuthNewLazyImport.update({ path: '/auth/new', getParentRoute: () => rootRoute, @@ -142,13 +138,6 @@ const AuthConnectLazyRoute = AuthConnectLazyImport.update({ getParentRoute: () => rootRoute, } as any).lazy(() => import('./routes/auth/connect.lazy').then((d) => d.Route)) -const AccountSettingsLazyRoute = AccountSettingsLazyImport.update({ - id: '/_settings', - getParentRoute: () => AccountRoute, -} as any).lazy(() => - import('./routes/$account/_settings.lazy').then((d) => d.Route), -) - const ZapIdRoute = ZapIdImport.update({ path: '/zap/$id', getParentRoute: () => rootRoute, @@ -159,15 +148,40 @@ const ColumnsLayoutRoute = ColumnsLayoutImport.update({ getParentRoute: () => ColumnsRoute, } as any) -const AccountBackupRoute = AccountBackupImport.update({ - path: '/backup', - getParentRoute: () => AccountRoute, -} as any) +const SettingsWalletRoute = SettingsWalletImport.update({ + path: '/wallet', + getParentRoute: () => SettingsLazyRoute, +} as any).lazy(() => + import('./routes/_settings/wallet.lazy').then((d) => d.Route), +) -const AccountAppRoute = AccountAppImport.update({ - id: '/_app', - getParentRoute: () => AccountRoute, -} as any).lazy(() => import('./routes/$account/_app.lazy').then((d) => d.Route)) +const SettingsRelayRoute = SettingsRelayImport.update({ + path: '/relay', + getParentRoute: () => SettingsLazyRoute, +} as any).lazy(() => + import('./routes/_settings/relay.lazy').then((d) => d.Route), +) + +const SettingsProfileRoute = SettingsProfileImport.update({ + path: '/profile', + getParentRoute: () => SettingsLazyRoute, +} as any).lazy(() => + import('./routes/_settings/profile.lazy').then((d) => d.Route), +) + +const SettingsGeneralRoute = SettingsGeneralImport.update({ + path: '/general', + getParentRoute: () => SettingsLazyRoute, +} as any).lazy(() => + import('./routes/_settings/general.lazy').then((d) => d.Route), +) + +const SettingsBitcoinConnectRoute = SettingsBitcoinConnectImport.update({ + path: '/bitcoin-connect', + getParentRoute: () => SettingsLazyRoute, +} as any).lazy(() => + import('./routes/_settings/bitcoin-connect.lazy').then((d) => d.Route), +) const ColumnsLayoutTrendingLazyRoute = ColumnsLayoutTrendingLazyImport.update({ path: '/trending', @@ -191,14 +205,6 @@ const ColumnsLayoutOnboardingLazyRoute = import('./routes/columns/_layout/onboarding.lazy').then((d) => d.Route), ) -const ColumnsLayoutNotificationLazyRoute = - ColumnsLayoutNotificationLazyImport.update({ - path: '/notification', - getParentRoute: () => ColumnsLayoutRoute, - } as any).lazy(() => - import('./routes/columns/_layout/notification.lazy').then((d) => d.Route), - ) - const ColumnsLayoutLaunchpadLazyRoute = ColumnsLayoutLaunchpadLazyImport.update( { path: '/launchpad', @@ -208,27 +214,6 @@ const ColumnsLayoutLaunchpadLazyRoute = ColumnsLayoutLaunchpadLazyImport.update( import('./routes/columns/_layout/launchpad.lazy').then((d) => d.Route), ) -const AccountAppHomeLazyRoute = AccountAppHomeLazyImport.update({ - path: '/home', - getParentRoute: () => AccountAppRoute, -} as any).lazy(() => - import('./routes/$account/_app.home.lazy').then((d) => d.Route), -) - -const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({ - path: '/stories', - getParentRoute: () => ColumnsLayoutRoute, -} as any).lazy(() => - import('./routes/columns/_layout/stories.lazy').then((d) => d.Route), -) - -const ColumnsLayoutNewsfeedRoute = ColumnsLayoutNewsfeedImport.update({ - path: '/newsfeed', - getParentRoute: () => ColumnsLayoutRoute, -} as any).lazy(() => - import('./routes/columns/_layout/newsfeed.lazy').then((d) => d.Route), -) - const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({ path: '/global', getParentRoute: () => ColumnsLayoutRoute, @@ -240,44 +225,6 @@ const ColumnsLayoutCreateNewsfeedRoute = getParentRoute: () => ColumnsLayoutRoute, } as any) -const AccountSettingsWalletRoute = AccountSettingsWalletImport.update({ - path: '/wallet', - getParentRoute: () => AccountSettingsLazyRoute, -} as any).lazy(() => - import('./routes/$account/_settings/wallet.lazy').then((d) => d.Route), -) - -const AccountSettingsRelayRoute = AccountSettingsRelayImport.update({ - path: '/relay', - getParentRoute: () => AccountSettingsLazyRoute, -} as any).lazy(() => - import('./routes/$account/_settings/relay.lazy').then((d) => d.Route), -) - -const AccountSettingsProfileRoute = AccountSettingsProfileImport.update({ - path: '/profile', - getParentRoute: () => AccountSettingsLazyRoute, -} as any).lazy(() => - import('./routes/$account/_settings/profile.lazy').then((d) => d.Route), -) - -const AccountSettingsGeneralRoute = AccountSettingsGeneralImport.update({ - path: '/general', - getParentRoute: () => AccountSettingsLazyRoute, -} as any).lazy(() => - import('./routes/$account/_settings/general.lazy').then((d) => d.Route), -) - -const AccountSettingsBitcoinConnectRoute = - AccountSettingsBitcoinConnectImport.update({ - path: '/bitcoin-connect', - getParentRoute: () => AccountSettingsLazyRoute, - } as any).lazy(() => - import('./routes/$account/_settings/bitcoin-connect.lazy').then( - (d) => d.Route, - ), - ) - const ColumnsLayoutUsersIdLazyRoute = ColumnsLayoutUsersIdLazyImport.update({ path: '/users/$id', getParentRoute: () => ColumnsLayoutRoute, @@ -294,6 +241,16 @@ const ColumnsLayoutRepliesIdLazyRoute = ColumnsLayoutRepliesIdLazyImport.update( import('./routes/columns/_layout/replies.$id.lazy').then((d) => d.Route), ) +const ColumnsLayoutNotificationIdLazyRoute = + ColumnsLayoutNotificationIdLazyImport.update({ + path: '/notification/$id', + getParentRoute: () => ColumnsLayoutRoute, + } as any).lazy(() => + import('./routes/columns/_layout/notification.$id.lazy').then( + (d) => d.Route, + ), + ) + const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({ path: '/events/$id', getParentRoute: () => ColumnsLayoutRoute, @@ -301,6 +258,20 @@ const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({ import('./routes/columns/_layout/events.$id.lazy').then((d) => d.Route), ) +const ColumnsLayoutStoriesIdRoute = ColumnsLayoutStoriesIdImport.update({ + path: '/stories/$id', + getParentRoute: () => ColumnsLayoutRoute, +} as any).lazy(() => + import('./routes/columns/_layout/stories.$id.lazy').then((d) => d.Route), +) + +const ColumnsLayoutNewsfeedIdRoute = ColumnsLayoutNewsfeedIdImport.update({ + path: '/newsfeed/$id', + getParentRoute: () => ColumnsLayoutRoute, +} as any).lazy(() => + import('./routes/columns/_layout/newsfeed.$id.lazy').then((d) => d.Route), +) + const ColumnsLayoutInterestsIdRoute = ColumnsLayoutInterestsIdImport.update({ path: '/interests/$id', getParentRoute: () => ColumnsLayoutRoute, @@ -331,11 +302,11 @@ const ColumnsLayoutCreateNewsfeedF2fRoute = declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutImport parentRoute: typeof rootRoute } '/bootstrap-relays': { @@ -345,13 +316,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BootstrapRelaysImport parentRoute: typeof rootRoute } - '/loading': { - id: '/loading' - path: '/loading' - fullPath: '/loading' - preLoaderRoute: typeof LoadingImport - parentRoute: typeof rootRoute - } '/set-group': { id: '/set-group' path: '/set-group' @@ -366,6 +330,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SetInterestImport parentRoute: typeof rootRoute } + '/_settings': { + id: '/_settings' + path: '' + fullPath: '' + preLoaderRoute: typeof SettingsLazyImport + parentRoute: typeof rootRoute + } '/new': { id: '/new' path: '/new' @@ -380,26 +351,40 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ResetLazyImport parentRoute: typeof rootRoute } - '/$account': { - id: '/$account' - path: '/$account' - fullPath: '/$account' - preLoaderRoute: typeof AccountImport - parentRoute: typeof rootRoute + '/_settings/bitcoin-connect': { + id: '/_settings/bitcoin-connect' + path: '/bitcoin-connect' + fullPath: '/bitcoin-connect' + preLoaderRoute: typeof SettingsBitcoinConnectImport + parentRoute: typeof SettingsLazyImport } - '/$account/_app': { - id: '/$account/_app' - path: '/$account' - fullPath: '/$account' - preLoaderRoute: typeof AccountAppImport - parentRoute: typeof AccountRoute + '/_settings/general': { + id: '/_settings/general' + path: '/general' + fullPath: '/general' + preLoaderRoute: typeof SettingsGeneralImport + parentRoute: typeof SettingsLazyImport } - '/$account/backup': { - id: '/$account/backup' - path: '/backup' - fullPath: '/$account/backup' - preLoaderRoute: typeof AccountBackupImport - parentRoute: typeof AccountImport + '/_settings/profile': { + id: '/_settings/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof SettingsProfileImport + parentRoute: typeof SettingsLazyImport + } + '/_settings/relay': { + id: '/_settings/relay' + path: '/relay' + fullPath: '/relay' + preLoaderRoute: typeof SettingsRelayImport + parentRoute: typeof SettingsLazyImport + } + '/_settings/wallet': { + id: '/_settings/wallet' + path: '/wallet' + fullPath: '/wallet' + preLoaderRoute: typeof SettingsWalletImport + parentRoute: typeof SettingsLazyImport } '/columns': { id: '/columns' @@ -422,13 +407,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ZapIdImport parentRoute: typeof rootRoute } - '/$account/_settings': { - id: '/$account/_settings' - path: '' - fullPath: '/$account' - preLoaderRoute: typeof AccountSettingsLazyImport - parentRoute: typeof AccountImport - } '/auth/connect': { id: '/auth/connect' path: '/auth/connect' @@ -450,6 +428,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthNewLazyImport parentRoute: typeof rootRoute } + '/_layout/': { + id: '/_layout/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof LayoutIndexImport + parentRoute: typeof LayoutImport + } '/editor/': { id: '/editor/' path: '/editor' @@ -457,41 +442,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof EditorIndexImport parentRoute: typeof rootRoute } - '/$account/_settings/bitcoin-connect': { - id: '/$account/_settings/bitcoin-connect' - path: '/bitcoin-connect' - fullPath: '/$account/bitcoin-connect' - preLoaderRoute: typeof AccountSettingsBitcoinConnectImport - parentRoute: typeof AccountSettingsLazyImport - } - '/$account/_settings/general': { - id: '/$account/_settings/general' - path: '/general' - fullPath: '/$account/general' - preLoaderRoute: typeof AccountSettingsGeneralImport - parentRoute: typeof AccountSettingsLazyImport - } - '/$account/_settings/profile': { - id: '/$account/_settings/profile' - path: '/profile' - fullPath: '/$account/profile' - preLoaderRoute: typeof AccountSettingsProfileImport - parentRoute: typeof AccountSettingsLazyImport - } - '/$account/_settings/relay': { - id: '/$account/_settings/relay' - path: '/relay' - fullPath: '/$account/relay' - preLoaderRoute: typeof AccountSettingsRelayImport - parentRoute: typeof AccountSettingsLazyImport - } - '/$account/_settings/wallet': { - id: '/$account/_settings/wallet' - path: '/wallet' - fullPath: '/$account/wallet' - preLoaderRoute: typeof AccountSettingsWalletImport - parentRoute: typeof AccountSettingsLazyImport - } '/columns/_layout/create-newsfeed': { id: '/columns/_layout/create-newsfeed' path: '/create-newsfeed' @@ -506,27 +456,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutGlobalImport parentRoute: typeof ColumnsLayoutImport } - '/columns/_layout/newsfeed': { - id: '/columns/_layout/newsfeed' - path: '/newsfeed' - fullPath: '/columns/newsfeed' - preLoaderRoute: typeof ColumnsLayoutNewsfeedImport - parentRoute: typeof ColumnsLayoutImport - } - '/columns/_layout/stories': { - id: '/columns/_layout/stories' - path: '/stories' - fullPath: '/columns/stories' - preLoaderRoute: typeof ColumnsLayoutStoriesImport - parentRoute: typeof ColumnsLayoutImport - } - '/$account/_app/home': { - id: '/$account/_app/home' - path: '/home' - fullPath: '/$account/home' - preLoaderRoute: typeof AccountAppHomeLazyImport - parentRoute: typeof AccountAppImport - } '/columns/_layout/launchpad': { id: '/columns/_layout/launchpad' path: '/launchpad' @@ -534,13 +463,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutLaunchpadLazyImport parentRoute: typeof ColumnsLayoutImport } - '/columns/_layout/notification': { - id: '/columns/_layout/notification' - path: '/notification' - fullPath: '/columns/notification' - preLoaderRoute: typeof ColumnsLayoutNotificationLazyImport - parentRoute: typeof ColumnsLayoutImport - } '/columns/_layout/onboarding': { id: '/columns/_layout/onboarding' path: '/onboarding' @@ -590,6 +512,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutInterestsIdImport parentRoute: typeof ColumnsLayoutImport } + '/columns/_layout/newsfeed/$id': { + id: '/columns/_layout/newsfeed/$id' + path: '/newsfeed/$id' + fullPath: '/columns/newsfeed/$id' + preLoaderRoute: typeof ColumnsLayoutNewsfeedIdImport + parentRoute: typeof ColumnsLayoutImport + } + '/columns/_layout/stories/$id': { + id: '/columns/_layout/stories/$id' + path: '/stories/$id' + fullPath: '/columns/stories/$id' + preLoaderRoute: typeof ColumnsLayoutStoriesIdImport + parentRoute: typeof ColumnsLayoutImport + } '/columns/_layout/events/$id': { id: '/columns/_layout/events/$id' path: '/events/$id' @@ -597,6 +533,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport parentRoute: typeof ColumnsLayoutImport } + '/columns/_layout/notification/$id': { + id: '/columns/_layout/notification/$id' + path: '/notification/$id' + fullPath: '/columns/notification/$id' + preLoaderRoute: typeof ColumnsLayoutNotificationIdLazyImport + parentRoute: typeof ColumnsLayoutImport + } '/columns/_layout/replies/$id': { id: '/columns/_layout/replies/$id' path: '/replies/$id' @@ -616,52 +559,37 @@ declare module '@tanstack/react-router' { // Create and export the route tree -interface AccountAppRouteChildren { - AccountAppHomeLazyRoute: typeof AccountAppHomeLazyRoute +interface LayoutRouteChildren { + LayoutIndexRoute: typeof LayoutIndexRoute } -const AccountAppRouteChildren: AccountAppRouteChildren = { - AccountAppHomeLazyRoute: AccountAppHomeLazyRoute, +const LayoutRouteChildren: LayoutRouteChildren = { + LayoutIndexRoute: LayoutIndexRoute, } -const AccountAppRouteWithChildren = AccountAppRoute._addFileChildren( - AccountAppRouteChildren, +const LayoutRouteWithChildren = + LayoutRoute._addFileChildren(LayoutRouteChildren) + +interface SettingsLazyRouteChildren { + SettingsBitcoinConnectRoute: typeof SettingsBitcoinConnectRoute + SettingsGeneralRoute: typeof SettingsGeneralRoute + SettingsProfileRoute: typeof SettingsProfileRoute + SettingsRelayRoute: typeof SettingsRelayRoute + SettingsWalletRoute: typeof SettingsWalletRoute +} + +const SettingsLazyRouteChildren: SettingsLazyRouteChildren = { + SettingsBitcoinConnectRoute: SettingsBitcoinConnectRoute, + SettingsGeneralRoute: SettingsGeneralRoute, + SettingsProfileRoute: SettingsProfileRoute, + SettingsRelayRoute: SettingsRelayRoute, + SettingsWalletRoute: SettingsWalletRoute, +} + +const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren( + SettingsLazyRouteChildren, ) -interface AccountSettingsLazyRouteChildren { - AccountSettingsBitcoinConnectRoute: typeof AccountSettingsBitcoinConnectRoute - AccountSettingsGeneralRoute: typeof AccountSettingsGeneralRoute - AccountSettingsProfileRoute: typeof AccountSettingsProfileRoute - AccountSettingsRelayRoute: typeof AccountSettingsRelayRoute - AccountSettingsWalletRoute: typeof AccountSettingsWalletRoute -} - -const AccountSettingsLazyRouteChildren: AccountSettingsLazyRouteChildren = { - AccountSettingsBitcoinConnectRoute: AccountSettingsBitcoinConnectRoute, - AccountSettingsGeneralRoute: AccountSettingsGeneralRoute, - AccountSettingsProfileRoute: AccountSettingsProfileRoute, - AccountSettingsRelayRoute: AccountSettingsRelayRoute, - AccountSettingsWalletRoute: AccountSettingsWalletRoute, -} - -const AccountSettingsLazyRouteWithChildren = - AccountSettingsLazyRoute._addFileChildren(AccountSettingsLazyRouteChildren) - -interface AccountRouteChildren { - AccountAppRoute: typeof AccountAppRouteWithChildren - AccountBackupRoute: typeof AccountBackupRoute - AccountSettingsLazyRoute: typeof AccountSettingsLazyRouteWithChildren -} - -const AccountRouteChildren: AccountRouteChildren = { - AccountAppRoute: AccountAppRouteWithChildren, - AccountBackupRoute: AccountBackupRoute, - AccountSettingsLazyRoute: AccountSettingsLazyRouteWithChildren, -} - -const AccountRouteWithChildren = - AccountRoute._addFileChildren(AccountRouteChildren) - interface ColumnsLayoutCreateNewsfeedRouteChildren { ColumnsLayoutCreateNewsfeedF2fRoute: typeof ColumnsLayoutCreateNewsfeedF2fRoute ColumnsLayoutCreateNewsfeedUsersRoute: typeof ColumnsLayoutCreateNewsfeedUsersRoute @@ -682,16 +610,16 @@ const ColumnsLayoutCreateNewsfeedRouteWithChildren = interface ColumnsLayoutRouteChildren { ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute - ColumnsLayoutNewsfeedRoute: typeof ColumnsLayoutNewsfeedRoute - ColumnsLayoutStoriesRoute: typeof ColumnsLayoutStoriesRoute ColumnsLayoutLaunchpadLazyRoute: typeof ColumnsLayoutLaunchpadLazyRoute - ColumnsLayoutNotificationLazyRoute: typeof ColumnsLayoutNotificationLazyRoute ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute ColumnsLayoutGroupsIdRoute: typeof ColumnsLayoutGroupsIdRoute ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute + ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute + ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute + ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute } @@ -700,16 +628,16 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = { ColumnsLayoutCreateNewsfeedRoute: ColumnsLayoutCreateNewsfeedRouteWithChildren, ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute, - ColumnsLayoutNewsfeedRoute: ColumnsLayoutNewsfeedRoute, - ColumnsLayoutStoriesRoute: ColumnsLayoutStoriesRoute, ColumnsLayoutLaunchpadLazyRoute: ColumnsLayoutLaunchpadLazyRoute, - ColumnsLayoutNotificationLazyRoute: ColumnsLayoutNotificationLazyRoute, ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute, ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute, ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute, ColumnsLayoutGroupsIdRoute: ColumnsLayoutGroupsIdRoute, ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute, + ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute, + ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute, ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute, + ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute, ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute, ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute, } @@ -730,33 +658,27 @@ const ColumnsRouteWithChildren = ColumnsRoute._addFileChildren(ColumnsRouteChildren) export interface FileRoutesByFullPath { - '/': typeof IndexRoute + '': typeof SettingsLazyRouteWithChildren '/bootstrap-relays': typeof BootstrapRelaysRoute - '/loading': typeof LoadingRoute '/set-group': typeof SetGroupRoute '/set-interest': typeof SetInterestRoute '/new': typeof NewLazyRoute '/reset': typeof ResetLazyRoute - '/$account': typeof AccountSettingsLazyRouteWithChildren - '/$account/backup': typeof AccountBackupRoute + '/bitcoin-connect': typeof SettingsBitcoinConnectRoute + '/general': typeof SettingsGeneralRoute + '/profile': typeof SettingsProfileRoute + '/relay': typeof SettingsRelayRoute + '/wallet': typeof SettingsWalletRoute '/columns': typeof ColumnsLayoutRouteWithChildren '/zap/$id': typeof ZapIdRoute '/auth/connect': typeof AuthConnectLazyRoute '/auth/import': typeof AuthImportLazyRoute '/auth/new': typeof AuthNewLazyRoute + '/': typeof LayoutIndexRoute '/editor': typeof EditorIndexRoute - '/$account/bitcoin-connect': typeof AccountSettingsBitcoinConnectRoute - '/$account/general': typeof AccountSettingsGeneralRoute - '/$account/profile': typeof AccountSettingsProfileRoute - '/$account/relay': typeof AccountSettingsRelayRoute - '/$account/wallet': typeof AccountSettingsWalletRoute '/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren '/columns/global': typeof ColumnsLayoutGlobalRoute - '/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute - '/columns/stories': typeof ColumnsLayoutStoriesRoute - '/$account/home': typeof AccountAppHomeLazyRoute '/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute - '/columns/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/search': typeof ColumnsLayoutSearchLazyRoute '/columns/trending': typeof ColumnsLayoutTrendingLazyRoute @@ -764,39 +686,36 @@ export interface FileRoutesByFullPath { '/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute '/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute '/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute + '/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute + '/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute '/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute + '/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute '/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute '/bootstrap-relays': typeof BootstrapRelaysRoute - '/loading': typeof LoadingRoute '/set-group': typeof SetGroupRoute '/set-interest': typeof SetInterestRoute + '': typeof SettingsLazyRouteWithChildren '/new': typeof NewLazyRoute '/reset': typeof ResetLazyRoute - '/$account': typeof AccountSettingsLazyRouteWithChildren - '/$account/backup': typeof AccountBackupRoute + '/bitcoin-connect': typeof SettingsBitcoinConnectRoute + '/general': typeof SettingsGeneralRoute + '/profile': typeof SettingsProfileRoute + '/relay': typeof SettingsRelayRoute + '/wallet': typeof SettingsWalletRoute '/columns': typeof ColumnsLayoutRouteWithChildren '/zap/$id': typeof ZapIdRoute '/auth/connect': typeof AuthConnectLazyRoute '/auth/import': typeof AuthImportLazyRoute '/auth/new': typeof AuthNewLazyRoute + '/': typeof LayoutIndexRoute '/editor': typeof EditorIndexRoute - '/$account/bitcoin-connect': typeof AccountSettingsBitcoinConnectRoute - '/$account/general': typeof AccountSettingsGeneralRoute - '/$account/profile': typeof AccountSettingsProfileRoute - '/$account/relay': typeof AccountSettingsRelayRoute - '/$account/wallet': typeof AccountSettingsWalletRoute '/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren '/columns/global': typeof ColumnsLayoutGlobalRoute - '/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute - '/columns/stories': typeof ColumnsLayoutStoriesRoute - '/$account/home': typeof AccountAppHomeLazyRoute '/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute - '/columns/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/search': typeof ColumnsLayoutSearchLazyRoute '/columns/trending': typeof ColumnsLayoutTrendingLazyRoute @@ -804,43 +723,39 @@ export interface FileRoutesByTo { '/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute '/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute '/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute + '/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute + '/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute '/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute + '/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute '/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } export interface FileRoutesById { __root__: typeof rootRoute - '/': typeof IndexRoute + '/_layout': typeof LayoutRouteWithChildren '/bootstrap-relays': typeof BootstrapRelaysRoute - '/loading': typeof LoadingRoute '/set-group': typeof SetGroupRoute '/set-interest': typeof SetInterestRoute + '/_settings': typeof SettingsLazyRouteWithChildren '/new': typeof NewLazyRoute '/reset': typeof ResetLazyRoute - '/$account': typeof AccountRouteWithChildren - '/$account/_app': typeof AccountAppRouteWithChildren - '/$account/backup': typeof AccountBackupRoute + '/_settings/bitcoin-connect': typeof SettingsBitcoinConnectRoute + '/_settings/general': typeof SettingsGeneralRoute + '/_settings/profile': typeof SettingsProfileRoute + '/_settings/relay': typeof SettingsRelayRoute + '/_settings/wallet': typeof SettingsWalletRoute '/columns': typeof ColumnsRouteWithChildren '/columns/_layout': typeof ColumnsLayoutRouteWithChildren '/zap/$id': typeof ZapIdRoute - '/$account/_settings': typeof AccountSettingsLazyRouteWithChildren '/auth/connect': typeof AuthConnectLazyRoute '/auth/import': typeof AuthImportLazyRoute '/auth/new': typeof AuthNewLazyRoute + '/_layout/': typeof LayoutIndexRoute '/editor/': typeof EditorIndexRoute - '/$account/_settings/bitcoin-connect': typeof AccountSettingsBitcoinConnectRoute - '/$account/_settings/general': typeof AccountSettingsGeneralRoute - '/$account/_settings/profile': typeof AccountSettingsProfileRoute - '/$account/_settings/relay': typeof AccountSettingsRelayRoute - '/$account/_settings/wallet': typeof AccountSettingsWalletRoute '/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren '/columns/_layout/global': typeof ColumnsLayoutGlobalRoute - '/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedRoute - '/columns/_layout/stories': typeof ColumnsLayoutStoriesRoute - '/$account/_app/home': typeof AccountAppHomeLazyRoute '/columns/_layout/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute - '/columns/_layout/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute '/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute @@ -848,7 +763,10 @@ export interface FileRoutesById { '/columns/_layout/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute '/columns/_layout/groups/$id': typeof ColumnsLayoutGroupsIdRoute '/columns/_layout/interests/$id': typeof ColumnsLayoutInterestsIdRoute + '/columns/_layout/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute + '/columns/_layout/stories/$id': typeof ColumnsLayoutStoriesIdRoute '/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute + '/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute '/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -856,33 +774,27 @@ export interface FileRoutesById { export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: - | '/' + | '' | '/bootstrap-relays' - | '/loading' | '/set-group' | '/set-interest' | '/new' | '/reset' - | '/$account' - | '/$account/backup' + | '/bitcoin-connect' + | '/general' + | '/profile' + | '/relay' + | '/wallet' | '/columns' | '/zap/$id' | '/auth/connect' | '/auth/import' | '/auth/new' + | '/' | '/editor' - | '/$account/bitcoin-connect' - | '/$account/general' - | '/$account/profile' - | '/$account/relay' - | '/$account/wallet' | '/columns/create-newsfeed' | '/columns/global' - | '/columns/newsfeed' - | '/columns/stories' - | '/$account/home' | '/columns/launchpad' - | '/columns/notification' | '/columns/onboarding' | '/columns/search' | '/columns/trending' @@ -890,38 +802,35 @@ export interface FileRouteTypes { | '/columns/create-newsfeed/users' | '/columns/groups/$id' | '/columns/interests/$id' + | '/columns/newsfeed/$id' + | '/columns/stories/$id' | '/columns/events/$id' + | '/columns/notification/$id' | '/columns/replies/$id' | '/columns/users/$id' fileRoutesByTo: FileRoutesByTo to: - | '/' | '/bootstrap-relays' - | '/loading' | '/set-group' | '/set-interest' + | '' | '/new' | '/reset' - | '/$account' - | '/$account/backup' + | '/bitcoin-connect' + | '/general' + | '/profile' + | '/relay' + | '/wallet' | '/columns' | '/zap/$id' | '/auth/connect' | '/auth/import' | '/auth/new' + | '/' | '/editor' - | '/$account/bitcoin-connect' - | '/$account/general' - | '/$account/profile' - | '/$account/relay' - | '/$account/wallet' | '/columns/create-newsfeed' | '/columns/global' - | '/columns/newsfeed' - | '/columns/stories' - | '/$account/home' | '/columns/launchpad' - | '/columns/notification' | '/columns/onboarding' | '/columns/search' | '/columns/trending' @@ -929,41 +838,37 @@ export interface FileRouteTypes { | '/columns/create-newsfeed/users' | '/columns/groups/$id' | '/columns/interests/$id' + | '/columns/newsfeed/$id' + | '/columns/stories/$id' | '/columns/events/$id' + | '/columns/notification/$id' | '/columns/replies/$id' | '/columns/users/$id' id: | '__root__' - | '/' + | '/_layout' | '/bootstrap-relays' - | '/loading' | '/set-group' | '/set-interest' + | '/_settings' | '/new' | '/reset' - | '/$account' - | '/$account/_app' - | '/$account/backup' + | '/_settings/bitcoin-connect' + | '/_settings/general' + | '/_settings/profile' + | '/_settings/relay' + | '/_settings/wallet' | '/columns' | '/columns/_layout' | '/zap/$id' - | '/$account/_settings' | '/auth/connect' | '/auth/import' | '/auth/new' + | '/_layout/' | '/editor/' - | '/$account/_settings/bitcoin-connect' - | '/$account/_settings/general' - | '/$account/_settings/profile' - | '/$account/_settings/relay' - | '/$account/_settings/wallet' | '/columns/_layout/create-newsfeed' | '/columns/_layout/global' - | '/columns/_layout/newsfeed' - | '/columns/_layout/stories' - | '/$account/_app/home' | '/columns/_layout/launchpad' - | '/columns/_layout/notification' | '/columns/_layout/onboarding' | '/columns/_layout/search' | '/columns/_layout/trending' @@ -971,21 +876,23 @@ export interface FileRouteTypes { | '/columns/_layout/create-newsfeed/users' | '/columns/_layout/groups/$id' | '/columns/_layout/interests/$id' + | '/columns/_layout/newsfeed/$id' + | '/columns/_layout/stories/$id' | '/columns/_layout/events/$id' + | '/columns/_layout/notification/$id' | '/columns/_layout/replies/$id' | '/columns/_layout/users/$id' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute + LayoutRoute: typeof LayoutRouteWithChildren BootstrapRelaysRoute: typeof BootstrapRelaysRoute - LoadingRoute: typeof LoadingRoute SetGroupRoute: typeof SetGroupRoute SetInterestRoute: typeof SetInterestRoute + SettingsLazyRoute: typeof SettingsLazyRouteWithChildren NewLazyRoute: typeof NewLazyRoute ResetLazyRoute: typeof ResetLazyRoute - AccountRoute: typeof AccountRouteWithChildren ColumnsRoute: typeof ColumnsRouteWithChildren ZapIdRoute: typeof ZapIdRoute AuthConnectLazyRoute: typeof AuthConnectLazyRoute @@ -995,14 +902,13 @@ export interface RootRouteChildren { } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, + LayoutRoute: LayoutRouteWithChildren, BootstrapRelaysRoute: BootstrapRelaysRoute, - LoadingRoute: LoadingRoute, SetGroupRoute: SetGroupRoute, SetInterestRoute: SetInterestRoute, + SettingsLazyRoute: SettingsLazyRouteWithChildren, NewLazyRoute: NewLazyRoute, ResetLazyRoute: ResetLazyRoute, - AccountRoute: AccountRouteWithChildren, ColumnsRoute: ColumnsRouteWithChildren, ZapIdRoute: ZapIdRoute, AuthConnectLazyRoute: AuthConnectLazyRoute, @@ -1023,14 +929,13 @@ export const routeTree = rootRoute "__root__": { "filePath": "__root.tsx", "children": [ - "/", + "/_layout", "/bootstrap-relays", - "/loading", "/set-group", "/set-interest", + "/_settings", "/new", "/reset", - "/$account", "/columns", "/zap/$id", "/auth/connect", @@ -1039,45 +944,56 @@ export const routeTree = rootRoute "/editor/" ] }, - "/": { - "filePath": "index.tsx" + "/_layout": { + "filePath": "_layout.tsx", + "children": [ + "/_layout/" + ] }, "/bootstrap-relays": { "filePath": "bootstrap-relays.tsx" }, - "/loading": { - "filePath": "loading.tsx" - }, "/set-group": { "filePath": "set-group.tsx" }, "/set-interest": { "filePath": "set-interest.tsx" }, + "/_settings": { + "filePath": "_settings.lazy.tsx", + "children": [ + "/_settings/bitcoin-connect", + "/_settings/general", + "/_settings/profile", + "/_settings/relay", + "/_settings/wallet" + ] + }, "/new": { "filePath": "new.lazy.tsx" }, "/reset": { "filePath": "reset.lazy.tsx" }, - "/$account": { - "filePath": "$account", - "children": [ - "/$account/_app", - "/$account/backup", - "/$account/_settings" - ] + "/_settings/bitcoin-connect": { + "filePath": "_settings/bitcoin-connect.tsx", + "parent": "/_settings" }, - "/$account/_app": { - "filePath": "$account/_app.tsx", - "parent": "/$account", - "children": [ - "/$account/_app/home" - ] + "/_settings/general": { + "filePath": "_settings/general.tsx", + "parent": "/_settings" }, - "/$account/backup": { - "filePath": "$account/backup.tsx", - "parent": "/$account" + "/_settings/profile": { + "filePath": "_settings/profile.tsx", + "parent": "/_settings" + }, + "/_settings/relay": { + "filePath": "_settings/relay.tsx", + "parent": "/_settings" + }, + "/_settings/wallet": { + "filePath": "_settings/wallet.tsx", + "parent": "/_settings" }, "/columns": { "filePath": "columns", @@ -1091,16 +1007,16 @@ export const routeTree = rootRoute "children": [ "/columns/_layout/create-newsfeed", "/columns/_layout/global", - "/columns/_layout/newsfeed", - "/columns/_layout/stories", "/columns/_layout/launchpad", - "/columns/_layout/notification", "/columns/_layout/onboarding", "/columns/_layout/search", "/columns/_layout/trending", "/columns/_layout/groups/$id", "/columns/_layout/interests/$id", + "/columns/_layout/newsfeed/$id", + "/columns/_layout/stories/$id", "/columns/_layout/events/$id", + "/columns/_layout/notification/$id", "/columns/_layout/replies/$id", "/columns/_layout/users/$id" ] @@ -1108,17 +1024,6 @@ export const routeTree = rootRoute "/zap/$id": { "filePath": "zap.$id.tsx" }, - "/$account/_settings": { - "filePath": "$account/_settings.lazy.tsx", - "parent": "/$account", - "children": [ - "/$account/_settings/bitcoin-connect", - "/$account/_settings/general", - "/$account/_settings/profile", - "/$account/_settings/relay", - "/$account/_settings/wallet" - ] - }, "/auth/connect": { "filePath": "auth/connect.lazy.tsx" }, @@ -1128,29 +1033,13 @@ export const routeTree = rootRoute "/auth/new": { "filePath": "auth/new.lazy.tsx" }, + "/_layout/": { + "filePath": "_layout/index.tsx", + "parent": "/_layout" + }, "/editor/": { "filePath": "editor/index.tsx" }, - "/$account/_settings/bitcoin-connect": { - "filePath": "$account/_settings/bitcoin-connect.tsx", - "parent": "/$account/_settings" - }, - "/$account/_settings/general": { - "filePath": "$account/_settings/general.tsx", - "parent": "/$account/_settings" - }, - "/$account/_settings/profile": { - "filePath": "$account/_settings/profile.tsx", - "parent": "/$account/_settings" - }, - "/$account/_settings/relay": { - "filePath": "$account/_settings/relay.tsx", - "parent": "/$account/_settings" - }, - "/$account/_settings/wallet": { - "filePath": "$account/_settings/wallet.tsx", - "parent": "/$account/_settings" - }, "/columns/_layout/create-newsfeed": { "filePath": "columns/_layout/create-newsfeed.tsx", "parent": "/columns/_layout", @@ -1163,26 +1052,10 @@ export const routeTree = rootRoute "filePath": "columns/_layout/global.tsx", "parent": "/columns/_layout" }, - "/columns/_layout/newsfeed": { - "filePath": "columns/_layout/newsfeed.tsx", - "parent": "/columns/_layout" - }, - "/columns/_layout/stories": { - "filePath": "columns/_layout/stories.tsx", - "parent": "/columns/_layout" - }, - "/$account/_app/home": { - "filePath": "$account/_app.home.lazy.tsx", - "parent": "/$account/_app" - }, "/columns/_layout/launchpad": { "filePath": "columns/_layout/launchpad.lazy.tsx", "parent": "/columns/_layout" }, - "/columns/_layout/notification": { - "filePath": "columns/_layout/notification.lazy.tsx", - "parent": "/columns/_layout" - }, "/columns/_layout/onboarding": { "filePath": "columns/_layout/onboarding.lazy.tsx", "parent": "/columns/_layout" @@ -1211,10 +1084,22 @@ export const routeTree = rootRoute "filePath": "columns/_layout/interests.$id.tsx", "parent": "/columns/_layout" }, + "/columns/_layout/newsfeed/$id": { + "filePath": "columns/_layout/newsfeed.$id.tsx", + "parent": "/columns/_layout" + }, + "/columns/_layout/stories/$id": { + "filePath": "columns/_layout/stories.$id.tsx", + "parent": "/columns/_layout" + }, "/columns/_layout/events/$id": { "filePath": "columns/_layout/events.$id.lazy.tsx", "parent": "/columns/_layout" }, + "/columns/_layout/notification/$id": { + "filePath": "columns/_layout/notification.$id.lazy.tsx", + "parent": "/columns/_layout" + }, "/columns/_layout/replies/$id": { "filePath": "columns/_layout/replies.$id.lazy.tsx", "parent": "/columns/_layout" diff --git a/src/routes/$account/_app.home.lazy.tsx b/src/routes/$account/_app.home.lazy.tsx deleted file mode 100644 index c565718a..00000000 --- a/src/routes/$account/_app.home.lazy.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import { appColumns } from "@/commons"; -import { Spinner } from "@/components"; -import { Column } from "@/components/column"; -import { LumeWindow } from "@/system"; -import type { ColumnEvent, LumeColumn } from "@/types"; -import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { useStore } from "@tanstack/react-store"; -import { listen } from "@tauri-apps/api/event"; -import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; -import { resolveResource } from "@tauri-apps/api/path"; -import { getCurrentWindow } from "@tauri-apps/api/window"; -import { readTextFile } from "@tauri-apps/plugin-fs"; -import useEmblaCarousel from "embla-carousel-react"; -import { nanoid } from "nanoid"; -import { - type ReactNode, - useCallback, - useEffect, - useLayoutEffect, - useState, -} from "react"; -import { createPortal } from "react-dom"; -import { useDebouncedCallback } from "use-debounce"; - -export const Route = createLazyFileRoute("/$account/_app/home")({ - component: Screen, -}); - -function Screen() { - const params = Route.useParams(); - const columns = useStore(appColumns, (state) => state); - - const [emblaRef, emblaApi] = useEmblaCarousel({ - watchDrag: false, - loop: false, - }); - - const scrollPrev = useCallback(() => { - if (emblaApi) emblaApi.scrollPrev(true); - }, [emblaApi]); - - const scrollNext = useCallback(() => { - if (emblaApi) emblaApi.scrollNext(true); - }, [emblaApi]); - - const emitScrollEvent = useCallback(() => { - getCurrentWindow().emit("column_scroll", {}); - }, []); - - const add = useDebouncedCallback((column: LumeColumn) => { - column.label = `${column.label}-${nanoid()}`; // update col label - appColumns.setState((prev) => [column, ...prev]); - - if (emblaApi) { - emblaApi.scrollTo(0, true); - } - }, 150); - - const remove = useDebouncedCallback((label: string) => { - appColumns.setState((prev) => prev.filter((t) => t.label !== label)); - }, 150); - - const move = useDebouncedCallback( - (label: string, direction: "left" | "right") => { - const newCols = [...columns]; - - const col = newCols.find((el) => el.label === label); - const colIndex = newCols.findIndex((el) => el.label === label); - - newCols.splice(colIndex, 1); - - if (direction === "left") newCols.splice(colIndex - 1, 0, col); - if (direction === "right") newCols.splice(colIndex + 1, 0, col); - - appColumns.setState(() => newCols); - }, - 150, - ); - - const update = useDebouncedCallback((label: string, title: string) => { - const currentColIndex = columns.findIndex((col) => col.label === label); - - const updatedCol = Object.assign({}, columns[currentColIndex]); - updatedCol.name = title; - - const newCols = columns.slice(); - newCols[currentColIndex] = updatedCol; - - appColumns.setState(() => newCols); - }, 150); - - const reset = useDebouncedCallback(() => appColumns.setState(() => []), 150); - - const handleKeyDown = useDebouncedCallback((event) => { - if (event.defaultPrevented) return; - - switch (event.code) { - case "ArrowLeft": - if (emblaApi) emblaApi.scrollPrev(); - break; - case "ArrowRight": - if (emblaApi) emblaApi.scrollNext(); - break; - default: - break; - } - - event.preventDefault(); - }, 150); - - useEffect(() => { - if (emblaApi) { - emblaApi.on("scroll", emitScrollEvent); - emblaApi.on("slidesChanged", emitScrollEvent); - } - - return () => { - emblaApi?.off("scroll", emitScrollEvent); - emblaApi?.off("slidesChanged", emitScrollEvent); - }; - }, [emblaApi, emitScrollEvent]); - - // Listen for keyboard event - useEffect(() => { - window.addEventListener("keydown", handleKeyDown); - - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - }, [handleKeyDown]); - - // Listen for columns event - useEffect(() => { - const unlisten = listen("columns", (data) => { - if (data.payload.type === "reset") reset(); - if (data.payload.type === "add") add(data.payload.column); - if (data.payload.type === "remove") remove(data.payload.label); - if (data.payload.type === "move") - move(data.payload.label, data.payload.direction); - if (data.payload.type === "set_title") - update(data.payload.label, data.payload.title); - }); - - return () => { - unlisten.then((f) => f()); - }; - }, []); - - useEffect(() => { - async function getSystemColumns() { - const systemPath = "resources/columns.json"; - const resourcePath = await resolveResource(systemPath); - const resourceFile = await readTextFile(resourcePath); - const cols: LumeColumn[] = JSON.parse(resourceFile); - - appColumns.setState(() => cols.filter((col) => col.default)); - } - - if (!columns.length) { - const prevColumns = window.localStorage.getItem( - `${params.account}_columns`, - ); - - if (!prevColumns) { - getSystemColumns(); - } else { - const parsed: LumeColumn[] = JSON.parse(prevColumns); - appColumns.setState(() => parsed); - } - } else { - window.localStorage.setItem( - `${params.account}_columns`, - JSON.stringify(columns), - ); - } - }, [columns.length]); - - return ( -
-
-
- {!columns ? ( -
- -
- ) : ( - columns.map((column) => ( - - )) - )} -
-
- -
-
-
-
- - - - - -
- ); -} - -function ManageButton() { - const showContextMenu = useCallback(async (e: React.MouseEvent) => { - e.preventDefault(); - - const menuItems = await Promise.all([ - MenuItem.new({ - text: "Open Launchpad", - action: () => LumeWindow.openColumnsGallery(), - }), - PredefinedMenuItem.new({ item: "Separator" }), - MenuItem.new({ - text: "Open Newsfeed", - action: () => LumeWindow.openLocalFeeds(), - }), - MenuItem.new({ - text: "Open Notification", - action: () => LumeWindow.openNotification(), - }), - ]); - - const menu = await Menu.new({ - items: menuItems, - }); - - await menu.popup().catch((e) => console.error(e)); - }, []); - - return ( - - ); -} - -function Toolbar({ children }: { children: ReactNode[] }) { - const [domReady, setDomReady] = useState(false); - - useLayoutEffect(() => { - setDomReady(true); - }, []); - - return domReady ? ( - // @ts-ignore, react bug ??? - createPortal(children, document.getElementById("toolbar")) - ) : ( - <> - ); -} diff --git a/src/routes/$account/_app.lazy.tsx b/src/routes/$account/_app.lazy.tsx deleted file mode 100644 index 0498f8d4..00000000 --- a/src/routes/$account/_app.lazy.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { cn } from "@/commons"; -import { User } from "@/components/user"; -import { LumeWindow } from "@/system"; -import { CaretDown, Feather, MagnifyingGlass } from "@phosphor-icons/react"; -import { Outlet, createLazyFileRoute } from "@tanstack/react-router"; -import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; -import { writeText } from "@tauri-apps/plugin-clipboard-manager"; -import { memo, useCallback } from "react"; - -export const Route = createLazyFileRoute("/$account/_app")({ - component: Screen, -}); - -function Screen() { - const context = Route.useRouteContext(); - - return ( -
-
-
- -
- - -
-
-
-
-
- -
-
- ); -} - -const Account = memo(function Account() { - const params = Route.useParams(); - const navigate = Route.useNavigate(); - - const showContextMenu = useCallback( - async (e: React.MouseEvent) => { - e.preventDefault(); - - const menuItems = await Promise.all([ - MenuItem.new({ - text: "New Post", - action: () => LumeWindow.openEditor(), - }), - MenuItem.new({ - text: "Profile", - action: () => LumeWindow.openProfile(params.account), - }), - MenuItem.new({ - text: "Settings", - action: () => LumeWindow.openSettings(params.account), - }), - PredefinedMenuItem.new({ item: "Separator" }), - MenuItem.new({ - text: "Copy Public Key", - action: async () => await writeText(params.account), - }), - MenuItem.new({ - text: "Logout", - action: () => navigate({ to: "/" }), - }), - ]); - - const menu = await Menu.new({ - items: menuItems, - }); - - await menu.popup().catch((e) => console.error(e)); - }, - [params.account], - ); - - return ( - - ); -}); diff --git a/src/routes/$account/_settings.lazy.tsx b/src/routes/$account/_settings.lazy.tsx deleted file mode 100644 index cb3d482b..00000000 --- a/src/routes/$account/_settings.lazy.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { cn } from "@/commons"; -import { CurrencyBtc, GearSix, HardDrives, User } from "@phosphor-icons/react"; -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import { Link } from "@tanstack/react-router"; -import { Outlet, createLazyFileRoute } from "@tanstack/react-router"; - -export const Route = createLazyFileRoute("/$account/_settings")({ - component: Screen, -}); - -function Screen() { - const { account } = Route.useParams(); - const { platform } = Route.useRouteContext(); - - return ( -
-
- - {({ isActive }) => { - return ( -
- -

General

-
- ); - }} - - - {({ isActive }) => { - return ( -
- -

Profile

-
- ); - }} - - - {({ isActive }) => { - return ( -
- -

Relay

-
- ); - }} - - - {({ isActive }) => { - return ( -
- -

Wallet

-
- ); - }} - -
- - - - - - - - - -
- ); -} diff --git a/src/routes/$account/_settings/bitcoin-connect.lazy.tsx b/src/routes/$account/_settings/bitcoin-connect.lazy.tsx deleted file mode 100644 index aa073fd5..00000000 --- a/src/routes/$account/_settings/bitcoin-connect.lazy.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { commands } from "@/commands.gen"; -import { NostrAccount } from "@/system"; -import { Button } from "@getalby/bitcoin-connect-react"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; - -export const Route = createLazyFileRoute("/$account/_settings/bitcoin-connect")( - { - component: Screen, - }, -); - -function Screen() { - const setNwcUri = async (uri: string) => { - const res = await commands.setWallet(uri); - - if (res.status === "ok") { - await getCurrentWebviewWindow().close(); - } else { - throw new Error(res.error); - } - }; - - return ( -
-
-
-

- Click to the button below to connect with your Bitcoin wallet. -

-
-
-
- ); -} diff --git a/src/routes/$account/_settings/bitcoin-connect.tsx b/src/routes/$account/_settings/bitcoin-connect.tsx deleted file mode 100644 index 1ddf1e1f..00000000 --- a/src/routes/$account/_settings/bitcoin-connect.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { init } from "@getalby/bitcoin-connect-react"; -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/_settings/bitcoin-connect")({ - beforeLoad: () => { - init({ - appName: "Lume", - filters: ["nwc"], - showBalance: true, - }); - }, -}); diff --git a/src/routes/$account/_settings/general.lazy.tsx b/src/routes/$account/_settings/general.lazy.tsx deleted file mode 100644 index 8f8e033c..00000000 --- a/src/routes/$account/_settings/general.lazy.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { commands } from "@/commands.gen"; -import { appSettings } from "@/commons"; -import { Spinner } from "@/components"; -import * as Switch from "@radix-ui/react-switch"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { useStore } from "@tanstack/react-store"; -import { invoke } from "@tauri-apps/api/core"; -import { message } from "@tauri-apps/plugin-dialog"; -import { useCallback, useEffect, useState, useTransition } from "react"; - -type Theme = "auto" | "light" | "dark"; - -export const Route = createLazyFileRoute("/$account/_settings/general")({ - component: Screen, -}); - -function Screen() { - const [theme, setTheme] = useState(null); - const [isPending, startTransition] = useTransition(); - - const changeTheme = useCallback(async (theme: string) => { - if (theme === "auto" || theme === "light" || theme === "dark") { - invoke("plugin:theme|set_theme", { - theme: theme, - }).then(() => setTheme(theme)); - } - }, []); - - const updateSettings = () => { - startTransition(async () => { - const newSettings = JSON.stringify(appSettings.state); - const res = await commands.setUserSettings(newSettings); - - if (res.status === "error") { - await message(res.error, { kind: "error" }); - } - - return; - }); - }; - - useEffect(() => { - invoke("plugin:theme|get_theme").then((data) => setTheme(data as Theme)); - }, []); - - return ( -
-
-
-

- General -

-
- - - -
-
-
-

- Appearance -

-
-
-
-

Appearance

-

- Change app theme -

-
-
- -
-
- - - -
-
-
-

- Privacy & Performance -

-
- - - -
-
-
-
-
- -
-
- ); -} - -function Setting({ - label, - name, - description, -}: { label: string; name: string; description: string }) { - const state = useStore(appSettings, (state) => state[label]); - - const toggle = useCallback(() => { - appSettings.setState((state) => { - return { - ...state, - [label]: !state[label], - }; - }); - }, []); - - return ( -
-
-

{name}

-

- {description} -

-
-
- toggle()} - className="relative h-7 w-12 shrink-0 cursor-default rounded-full bg-black/10 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/10" - > - - -
-
- ); -} diff --git a/src/routes/$account/_settings/general.tsx b/src/routes/$account/_settings/general.tsx deleted file mode 100644 index fbc2049f..00000000 --- a/src/routes/$account/_settings/general.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { commands } from "@/commands.gen"; -import { appSettings } from "@/commons"; -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/_settings/general")({ - beforeLoad: async () => { - const res = await commands.getUserSettings(); - - if (res.status === "ok") { - appSettings.setState((state) => { - return { ...state, ...res.data }; - }); - } else { - throw new Error(res.error); - } - }, -}); diff --git a/src/routes/$account/_settings/profile.lazy.tsx b/src/routes/$account/_settings/profile.lazy.tsx deleted file mode 100644 index 32e1d99b..00000000 --- a/src/routes/$account/_settings/profile.lazy.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { type Profile, commands } from "@/commands.gen"; -import { cn, upload } from "@/commons"; -import { Spinner } from "@/components"; -import { Plus } from "@phosphor-icons/react"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { writeText } from "@tauri-apps/plugin-clipboard-manager"; -import { message } from "@tauri-apps/plugin-dialog"; -import { - type Dispatch, - type ReactNode, - type SetStateAction, - useState, - useTransition, -} from "react"; -import { useForm } from "react-hook-form"; - -export const Route = createLazyFileRoute("/$account/_settings/profile")({ - component: Screen, -}); - -function Screen() { - const { profile } = Route.useRouteContext(); - const { register, handleSubmit } = useForm({ defaultValues: profile }); - - const [isPending, startTransition] = useTransition(); - const [picture, setPicture] = useState(""); - - const onSubmit = (data: Profile) => { - startTransition(async () => { - const newProfile: Profile = { ...profile, ...data, picture }; - const res = await commands.setProfile(newProfile); - - if (res.status === "error") { - await message(res.error, { title: "Profile", kind: "error" }); - } - - return; - }); - }; - - return ( -
-
-
- {profile.picture ? ( - avatar - ) : null} - - - -
-
-
-
{profile.display_name}
-
- {profile.nip05} -
-
- -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
- ); -} - -function PrivkeyButton() { - const { account } = Route.useParams(); - - const [isPending, startTransition] = useTransition(); - const [isCopy, setIsCopy] = useState(false); - - const copyPrivateKey = () => { - startTransition(async () => { - const res = await commands.getPrivateKey(account); - - 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/routes/$account/_settings/profile.tsx b/src/routes/$account/_settings/profile.tsx deleted file mode 100644 index e0e6d07e..00000000 --- a/src/routes/$account/_settings/profile.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { type Profile, commands } from "@/commands.gen"; -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/_settings/profile")({ - beforeLoad: async ({ params }) => { - const res = await commands.getProfile(params.account); - - if (res.status === "ok") { - const profile: Profile = JSON.parse(res.data); - return { profile }; - } else { - throw new Error(res.error); - } - }, -}); diff --git a/src/routes/$account/_settings/relay.lazy.tsx b/src/routes/$account/_settings/relay.lazy.tsx deleted file mode 100644 index d4df2190..00000000 --- a/src/routes/$account/_settings/relay.lazy.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { commands } from "@/commands.gen"; -import { Plus, X } from "@phosphor-icons/react"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { message } from "@tauri-apps/plugin-dialog"; -import { useEffect, useState, useTransition } from "react"; - -export const Route = createLazyFileRoute("/$account/_settings/relay")({ - component: Screen, -}); - -function Screen() { - const { relayList } = Route.useRouteContext(); - - const [relays, setRelays] = useState([]); - const [newRelay, setNewRelay] = useState(""); - const [isPending, startTransition] = useTransition(); - - const removeRelay = async (relay: string) => { - const res = await commands.removeRelay(relay); - - if (res.status === "ok") { - return res.data; - } else { - throw new Error(res.error); - } - }; - - const addNewRelay = () => { - startTransition(async () => { - try { - let url = newRelay; - - if (!url.startsWith("wss://")) { - url = `wss://${url}`; - } - - const relay = new URL(url); - const res = await commands.connectRelay(relay.toString()); - - if (res.status === "ok") { - setRelays((prev) => [...prev, newRelay]); - setNewRelay(""); - } else { - await message(res.error, { title: "Relay", kind: "error" }); - return; - } - } catch { - await message("URL is not valid.", { kind: "error" }); - return; - } - }); - }; - - useEffect(() => { - setRelays(relayList.connected); - }, [relayList]); - - return ( -
-
-
-

- Connected Relays -

-
- {relays.map((relay) => ( -
-
- - - - - {relay} -
-
- -
-
- ))} -
-
- setNewRelay(e.target.value)} - name="url" - placeholder="wss://..." - spellCheck={false} - className="flex-1 px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring-0 dark:border-neutral-700 dark:placeholder:text-neutral-400" - /> - -
-
-
-
-
-

- User Relays (NIP-65) -

-
-

- Lume will automatically connect to the user's relay list, but the - manager function (like adding, removing, changing relay purpose) - is not yet available. -

-
-
- {relayList.read?.map((relay) => ( -
-
{relay}
-
READ
-
- ))} - {relayList.write?.map((relay) => ( -
-
{relay}
-
WRITE
-
- ))} - {relayList.both?.map((relay) => ( -
-
{relay}
-
READ + WRITE
-
- ))} -
-
-
-
- ); -} diff --git a/src/routes/$account/_settings/relay.tsx b/src/routes/$account/_settings/relay.tsx deleted file mode 100644 index 89754b4d..00000000 --- a/src/routes/$account/_settings/relay.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { commands } from "@/commands.gen"; -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/_settings/relay")({ - beforeLoad: async () => { - const res = await commands.getRelays(); - - if (res.status === "ok") { - const relayList = res.data; - return { relayList }; - } else { - throw new Error(res.error); - } - }, -}); diff --git a/src/routes/$account/_settings/wallet.lazy.tsx b/src/routes/$account/_settings/wallet.lazy.tsx deleted file mode 100644 index d991fed7..00000000 --- a/src/routes/$account/_settings/wallet.lazy.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { commands } from "@/commands.gen"; -import { createLazyFileRoute, redirect } from "@tanstack/react-router"; - -export const Route = createLazyFileRoute("/$account/_settings/wallet")({ - component: Screen, -}); - -function Screen() { - const { account } = Route.useParams(); - const { balance } = Route.useRouteContext(); - - const disconnect = async () => { - const res = await commands.removeWallet(); - - if (res.status === "ok") { - window.localStorage.removeItem("bc:config"); - return redirect({ to: "/$account/bitcoin-connect", params: { account } }); - } else { - throw new Error(res.error); - } - }; - - return ( -
-
-
-
-
-

Connection

-
-
- -
-
-
-
-
-
-

Current Balance

-
-
- ₿ {balance.bitcoinFormatted} -
-
-
-
-
- ); -} diff --git a/src/routes/$account/_settings/wallet.tsx b/src/routes/$account/_settings/wallet.tsx deleted file mode 100644 index adf36b71..00000000 --- a/src/routes/$account/_settings/wallet.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { commands } from "@/commands.gen"; -import { getBitcoinDisplayValues } from "@/commons"; -import { createFileRoute, redirect } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/_settings/wallet")({ - beforeLoad: async ({ params }) => { - const query = await commands.loadWallet(); - - if (query.status === "ok") { - const wallet = Number.parseInt(query.data); - const balance = getBitcoinDisplayValues(wallet); - - return { balance }; - } else { - throw redirect({ - to: "/$account/bitcoin-connect", - params: { account: params.account }, - }); - } - }, -}); diff --git a/src/routes/$account/backup.tsx b/src/routes/$account/backup.tsx deleted file mode 100644 index 641c793d..00000000 --- a/src/routes/$account/backup.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { displayNsec } from "@/commons"; -import { Spinner } from "@/components"; -import { Check } from "@phosphor-icons/react"; -import * as Checkbox from "@radix-ui/react-checkbox"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { invoke } from "@tauri-apps/api/core"; -import { writeText } from "@tauri-apps/plugin-clipboard-manager"; -import { message } from "@tauri-apps/plugin-dialog"; -import { useState } from "react"; - -export const Route = createFileRoute("/$account/backup")({ - component: Screen, -}); - -function Screen() { - const { account } = Route.useParams(); - const navigate = useNavigate(); - - const [key, setKey] = useState(null); - const [passphase, setPassphase] = useState(""); - const [copied, setCopied] = useState(false); - const [loading, setLoading] = useState(false); - const [confirm, setConfirm] = useState({ c1: false, c2: false }); - - const submit = async () => { - try { - if (key) { - if (!confirm.c1 || !confirm.c2) { - return await message("You need to confirm before continue", { - title: "Backup", - kind: "info", - }); - } - - navigate({ to: "/", replace: true }); - } - - // start loading - setLoading(true); - - invoke("get_encrypted_key", { - npub: account, - password: passphase, - }).then((encrypted: string) => { - // update state - setKey(encrypted); - setLoading(false); - }); - } catch (e) { - setLoading(false); - await message(String(e), { - title: "Backup", - kind: "error", - }); - } - }; - - const copyKey = async () => { - try { - await writeText(key); - setCopied(true); - } catch (e) { - await message(String(e), { - title: "Backup", - kind: "error", - }); - } - }; - - return ( -
-
-

Backup your sign in keys

-

- It's use for login to Lume or other Nostr clients. You will lost - access to your account if you lose this key. -

-
-
-
- -
- setPassphase(e.target.value)} - className="w-full px-3 border-transparent rounded-lg h-11 bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:bg-white/10 dark:placeholder:text-neutral-400" - /> -
-
- {key ? ( - <> -
- -
- - -
-
-
-
Before you continue:
-
-
- - setConfirm((state) => ({ ...state, c1: !state.c1 })) - } - className="flex items-center justify-center rounded-md outline-none appearance-none size-6 bg-neutral-100 dark:bg-white/10 dark:hover:bg-white/20" - id="confirm1" - > - - - - - -
-
- - setConfirm((state) => ({ ...state, c2: !state.c2 })) - } - className="flex items-center justify-center rounded-md outline-none appearance-none size-6 bg-neutral-100 dark:bg-white/10 dark:hover:bg-white/20" - id="confirm2" - > - - - - - -
-
-
- - ) : null} -
- -
-
-
- ); -} diff --git a/src/routes/_layout.lazy.tsx b/src/routes/_layout.lazy.tsx new file mode 100644 index 00000000..83e8564a --- /dev/null +++ b/src/routes/_layout.lazy.tsx @@ -0,0 +1,165 @@ +import { type NegentropyEvent, commands } from "@/commands.gen"; +import { cn } from "@/commons"; +import { User } from "@/components/user"; +import { LumeWindow } from "@/system"; +import { Feather, MagnifyingGlass } from "@phosphor-icons/react"; +import { useQuery } from "@tanstack/react-query"; +import { Outlet, createLazyFileRoute } from "@tanstack/react-router"; +import { Channel } from "@tauri-apps/api/core"; +import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; +import { writeText } from "@tauri-apps/plugin-clipboard-manager"; +import { memo, useCallback, useEffect, useState } from "react"; + +export const Route = createLazyFileRoute("/_layout")({ + component: Layout, +}); + +function Layout() { + return ( +
+ +
+ +
+
+ ); +} + +function Topbar() { + const context = Route.useRouteContext(); + const { data: accounts } = useQuery({ + queryKey: ["accounts"], + queryFn: async () => { + return await commands.getAccounts(); + }, + }); + + return ( +
+
+ + {accounts?.map((account) => ( + + ))} +
+
+ {accounts?.length ? ( + <> + + + + ) : null} +
+
+
+ ); +} + +const NegentropyBadge = memo(function NegentropyBadge() { + const [process, setProcess] = useState(null); + + useEffect(() => { + const channel = new Channel(); + + channel.onmessage = (message) => { + if (message.Progress.message === "Ok") { + setProcess(null); + } else { + setProcess(message); + } + }; + + (async () => { + await commands.runSync(channel); + })(); + }, []); + + if (!process) { + return null; + } + + return ( +
+ {process ? ( + + {process.Progress.message} + {process.Progress.total_event > 0 + ? ` / ${process.Progress.total_event}` + : null} + + ) : ( + "Syncing" + )} +
+ ); +}); + +const Account = memo(function Account({ pubkey }: { pubkey: string }) { + const showContextMenu = useCallback( + async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "New Post", + action: () => LumeWindow.openEditor(), + }), + MenuItem.new({ + text: "Profile", + action: () => LumeWindow.openProfile(pubkey), + }), + MenuItem.new({ + text: "Settings", + action: () => LumeWindow.openSettings(pubkey), + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "Copy Public Key", + action: async () => await writeText(pubkey), + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, + [pubkey], + ); + + return ( + + ); +}); diff --git a/src/routes/$account/_app.tsx b/src/routes/_layout.tsx similarity index 50% rename from src/routes/$account/_app.tsx rename to src/routes/_layout.tsx index b7b988e9..a68a3390 100644 --- a/src/routes/$account/_app.tsx +++ b/src/routes/_layout.tsx @@ -1,3 +1,3 @@ import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/$account/_app")(); +export const Route = createFileRoute("/_layout")(); diff --git a/src/routes/_layout/index.lazy.tsx b/src/routes/_layout/index.lazy.tsx new file mode 100644 index 00000000..6d6999f3 --- /dev/null +++ b/src/routes/_layout/index.lazy.tsx @@ -0,0 +1,458 @@ +import { appColumns } from "@/commons"; +import { Column, Spinner } from "@/components"; +import { LumeWindow } from "@/system"; +import type { ColumnEvent, LumeColumn } from "@/types"; +import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { useStore } from "@tanstack/react-store"; +import { listen } from "@tauri-apps/api/event"; +import { resolveResource } from "@tauri-apps/api/path"; +import { getCurrentWindow } from "@tauri-apps/api/window"; +import { readTextFile } from "@tauri-apps/plugin-fs"; +import useEmblaCarousel from "embla-carousel-react"; +import { nanoid } from "nanoid"; +import { + type ReactNode, + useCallback, + useEffect, + useLayoutEffect, + useState, +} from "react"; +import { createPortal } from "react-dom"; +import { useDebouncedCallback } from "use-debounce"; + +export const Route = createLazyFileRoute("/_layout/")({ + component: Screen, +}); + +function Screen() { + const columns = useStore(appColumns, (state) => state); + + const [emblaRef, emblaApi] = useEmblaCarousel({ + watchDrag: false, + loop: false, + }); + + const scrollPrev = useCallback(() => { + if (emblaApi) emblaApi.scrollPrev(true); + }, [emblaApi]); + + const scrollNext = useCallback(() => { + if (emblaApi) emblaApi.scrollNext(true); + }, [emblaApi]); + + const emitScrollEvent = useCallback(() => { + getCurrentWindow().emit("column_scroll", {}); + }, []); + + const add = useDebouncedCallback((column: LumeColumn) => { + column.label = `${column.label}-${nanoid()}`; // update col label + appColumns.setState((prev) => [column, ...prev]); + + if (emblaApi) { + emblaApi.scrollTo(0, true); + } + }, 150); + + const remove = useDebouncedCallback((label: string) => { + appColumns.setState((prev) => prev.filter((t) => t.label !== label)); + }, 150); + + const move = useDebouncedCallback( + (label: string, direction: "left" | "right") => { + const newCols = [...columns]; + + const col = newCols.find((el) => el.label === label); + const colIndex = newCols.findIndex((el) => el.label === label); + + newCols.splice(colIndex, 1); + + if (direction === "left") newCols.splice(colIndex - 1, 0, col); + if (direction === "right") newCols.splice(colIndex + 1, 0, col); + + appColumns.setState(() => newCols); + }, + 150, + ); + + const update = useDebouncedCallback((label: string, title: string) => { + const currentColIndex = columns.findIndex((col) => col.label === label); + + const updatedCol = Object.assign({}, columns[currentColIndex]); + updatedCol.name = title; + + const newCols = columns.slice(); + newCols[currentColIndex] = updatedCol; + + appColumns.setState(() => newCols); + }, 150); + + const reset = useDebouncedCallback(() => appColumns.setState(() => []), 150); + + const handleKeyDown = useDebouncedCallback((event) => { + if (event.defaultPrevented) return; + + switch (event.code) { + case "ArrowLeft": + if (emblaApi) emblaApi.scrollPrev(); + break; + case "ArrowRight": + if (emblaApi) emblaApi.scrollNext(); + break; + default: + break; + } + + event.preventDefault(); + }, 150); + + useEffect(() => { + if (emblaApi) { + emblaApi.on("scroll", emitScrollEvent); + emblaApi.on("slidesChanged", emitScrollEvent); + } + + return () => { + emblaApi?.off("scroll", emitScrollEvent); + emblaApi?.off("slidesChanged", emitScrollEvent); + }; + }, [emblaApi, emitScrollEvent]); + + // Listen for keyboard event + useEffect(() => { + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [handleKeyDown]); + + // Listen for columns event + useEffect(() => { + const unlisten = listen("columns", (data) => { + if (data.payload.type === "reset") reset(); + if (data.payload.type === "add") add(data.payload.column); + if (data.payload.type === "remove") remove(data.payload.label); + if (data.payload.type === "move") + move(data.payload.label, data.payload.direction); + if (data.payload.type === "set_title") + update(data.payload.label, data.payload.title); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + + useEffect(() => { + async function getSystemColumns() { + const systemPath = "resources/columns.json"; + const resourcePath = await resolveResource(systemPath); + const resourceFile = await readTextFile(resourcePath); + const cols: LumeColumn[] = JSON.parse(resourceFile); + + appColumns.setState(() => cols.filter((col) => col.default)); + } + + if (!columns.length) { + const prevColumns = window.localStorage.getItem("columns"); + + if (!prevColumns) { + getSystemColumns(); + } else { + const parsed: LumeColumn[] = JSON.parse(prevColumns); + appColumns.setState(() => parsed); + } + } else { + window.localStorage.setItem("columns", JSON.stringify(columns)); + } + }, [columns.length]); + + return ( +
+
+
+ {!columns ? ( +
+ +
+ ) : ( + columns.map((column) => ( + + )) + )} +
+
+ +
+
+
+
+ + + + + +
+ ); +} + +function Toolbar({ children }: { children: ReactNode[] }) { + const [domReady, setDomReady] = useState(false); + + useLayoutEffect(() => { + setDomReady(true); + }, []); + + return domReady ? ( + // @ts-ignore, react bug ??? + createPortal(children, document.getElementById("toolbar")) + ) : ( + <> + ); +} + +/* +function Screen() { + const context = Route.useRouteContext() + const navigate = Route.useNavigate() + + const currentDate = useMemo( + () => + new Date().toLocaleString('default', { + weekday: 'long', + month: 'long', + day: 'numeric', + }), + [], + ) + + const [accounts, setAccounts] = useState([]) + const [value, setValue] = useState('') + const [autoLogin, setAutoLogin] = useState(false) + const [password, setPassword] = useState('') + const [isPending, startTransition] = useTransition() + + const showContextMenu = useCallback( + async (e: React.MouseEvent, account: string) => { + e.stopPropagation() + + const menuItems = await Promise.all([ + MenuItem.new({ + text: 'Reset password', + enabled: !account.includes('_nostrconnect'), + // @ts-ignore, this is tanstack router bug + action: () => navigate({ to: '/reset', search: { account } }), + }), + MenuItem.new({ + text: 'Delete account', + action: async () => await deleteAccount(account), + }), + ]) + + const menu = await Menu.new({ + items: menuItems, + }) + + await menu.popup().catch((e) => console.error(e)) + }, + [], + ) + + const deleteAccount = async (account: string) => { + const res = await commands.deleteAccount(account) + + if (res.status === 'ok') { + setAccounts((prev) => prev.filter((item) => item !== account)) + } + } + + const selectAccount = (account: string) => { + setValue(account) + + if (account.includes('_nostrconnect')) { + setAutoLogin(true) + } + } + + const loginWith = () => { + startTransition(async () => { + const res = await commands.login(value, password) + + if (res.status === 'ok') { + const settings = await commands.getUserSettings() + + if (settings.status === 'ok') { + appSettings.setState(() => settings.data) + } + + const status = await commands.isAccountSync(res.data) + + if (status) { + navigate({ + to: '/$account/home', + // @ts-ignore, this is tanstack router bug + params: { account: res.data }, + replace: true, + }) + } else { + navigate({ + to: '/loading', + // @ts-ignore, this is tanstack router bug + search: { account: res.data }, + replace: true, + }) + } + } else { + await message(res.error, { title: 'Login', kind: 'error' }) + return + } + }) + } + + useEffect(() => { + if (autoLogin) { + loginWith() + } + }, [autoLogin, value]) + + useEffect(() => { + setAccounts(context.accounts) + }, [context.accounts]) + + return ( +
+
+
+

+ {currentDate} +

+

Welcome back!

+
+ + {accounts.map((account) => ( +
selectAccount(account)} + onKeyDown={() => selectAccount(account)} + className="group flex items-center gap-2 hover:bg-black/5 dark:hover:bg-white/5 p-3" + > + + + + {value === account && !value.includes('_nostrconnect') ? ( +
+ setPassword(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') loginWith() + }} + disabled={isPending} + placeholder="Password" + className="px-3 rounded-full w-full h-10 bg-transparent border border-neutral-200 dark:border-neutral-500 focus:border-blue-500 focus:outline-none placeholder:text-neutral-400" + /> +
+ ) : ( +
+
+ + {account.includes('_nostrconnect') ? ( +
+ Nostr Connect +
+ ) : null} +
+ + {displayNpub(account.replace('_nostrconnect', ''), 16)} + +
+ )} +
+
+
+ {value === account ? ( + isPending ? ( + + ) : ( + + ) + ) : ( + + )} +
+
+ ))} + +
+
+ +
+ + New account + +
+
+ +
+ +
+ ) +} +*/ diff --git a/src/routes/_layout/index.tsx b/src/routes/_layout/index.tsx new file mode 100644 index 00000000..c67745be --- /dev/null +++ b/src/routes/_layout/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/')() diff --git a/src/routes/_settings.lazy.tsx b/src/routes/_settings.lazy.tsx new file mode 100644 index 00000000..b9144be6 --- /dev/null +++ b/src/routes/_settings.lazy.tsx @@ -0,0 +1,111 @@ +import { cn } from '@/commons' +import { CurrencyBtc, GearSix, HardDrives, User } from '@phosphor-icons/react' +import * as ScrollArea from '@radix-ui/react-scroll-area' +import { Link } from '@tanstack/react-router' +import { Outlet, createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/_settings')({ + component: Screen, +}) + +function Screen() { + const { account } = Route.useParams() + const { platform } = Route.useRouteContext() + + return ( +
+
+ + {({ isActive }) => { + return ( +
+ +

General

+
+ ) + }} + + + {({ isActive }) => { + return ( +
+ +

Profile

+
+ ) + }} + + + {({ isActive }) => { + return ( +
+ +

Relay

+
+ ) + }} + + + {({ isActive }) => { + return ( +
+ +

Wallet

+
+ ) + }} + +
+ + + + + + + + + +
+ ) +} diff --git a/src/routes/_settings/bitcoin-connect.lazy.tsx b/src/routes/_settings/bitcoin-connect.lazy.tsx new file mode 100644 index 00000000..014468c9 --- /dev/null +++ b/src/routes/_settings/bitcoin-connect.lazy.tsx @@ -0,0 +1,38 @@ +import { commands } from '@/commands.gen' +import { NostrAccount } from '@/system' +import { Button } from '@getalby/bitcoin-connect-react' +import { createLazyFileRoute } from '@tanstack/react-router' +import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' + +export const Route = createLazyFileRoute('/_settings/bitcoin-connect')({ + component: Screen, +}) + +function Screen() { + const setNwcUri = async (uri: string) => { + const res = await commands.setWallet(uri) + + if (res.status === 'ok') { + await getCurrentWebviewWindow().close() + } else { + throw new Error(res.error) + } + } + + return ( +
+
+
+

+ Click to the button below to connect with your Bitcoin wallet. +

+
+
+
+ ) +} diff --git a/src/routes/_settings/bitcoin-connect.tsx b/src/routes/_settings/bitcoin-connect.tsx new file mode 100644 index 00000000..077dd105 --- /dev/null +++ b/src/routes/_settings/bitcoin-connect.tsx @@ -0,0 +1,12 @@ +import { init } from '@getalby/bitcoin-connect-react' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_settings/bitcoin-connect')({ + beforeLoad: () => { + init({ + appName: 'Lume', + filters: ['nwc'], + showBalance: true, + }) + }, +}) diff --git a/src/routes/_settings/general.lazy.tsx b/src/routes/_settings/general.lazy.tsx new file mode 100644 index 00000000..83da3672 --- /dev/null +++ b/src/routes/_settings/general.lazy.tsx @@ -0,0 +1,189 @@ +import { commands } from '@/commands.gen' +import { appSettings } from '@/commons' +import { Spinner } from '@/components' +import * as Switch from '@radix-ui/react-switch' +import { createLazyFileRoute } from '@tanstack/react-router' +import { useStore } from '@tanstack/react-store' +import { invoke } from '@tauri-apps/api/core' +import { message } from '@tauri-apps/plugin-dialog' +import { useCallback, useEffect, useState, useTransition } from 'react' + +type Theme = 'auto' | 'light' | 'dark' + +export const Route = createLazyFileRoute('/_settings/general')({ + component: Screen, +}) + +function Screen() { + const [theme, setTheme] = useState(null) + const [isPending, startTransition] = useTransition() + + const changeTheme = useCallback(async (theme: string) => { + if (theme === 'auto' || theme === 'light' || theme === 'dark') { + invoke('plugin:theme|set_theme', { + theme: theme, + }).then(() => setTheme(theme)) + } + }, []) + + const updateSettings = () => { + startTransition(async () => { + const newSettings = JSON.stringify(appSettings.state) + const res = await commands.setUserSettings(newSettings) + + if (res.status === 'error') { + await message(res.error, { kind: 'error' }) + } + + return + }) + } + + useEffect(() => { + invoke('plugin:theme|get_theme').then((data) => setTheme(data as Theme)) + }, []) + + return ( +
+
+
+

+ General +

+
+ + + +
+
+
+

+ Appearance +

+
+
+
+

Appearance

+

+ Change app theme +

+
+
+ +
+
+ + + +
+
+
+

+ Privacy & Performance +

+
+ + + +
+
+
+
+
+ +
+
+ ) +} + +function Setting({ + label, + name, + description, +}: { + label: string + name: string + description: string +}) { + const state = useStore(appSettings, (state) => state[label]) + + const toggle = useCallback(() => { + appSettings.setState((state) => { + return { + ...state, + [label]: !state[label], + } + }) + }, []) + + return ( +
+
+

{name}

+

+ {description} +

+
+
+ toggle()} + className="relative h-7 w-12 shrink-0 cursor-default rounded-full bg-black/10 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/10" + > + + +
+
+ ) +} diff --git a/src/routes/_settings/general.tsx b/src/routes/_settings/general.tsx new file mode 100644 index 00000000..5daf2fe7 --- /dev/null +++ b/src/routes/_settings/general.tsx @@ -0,0 +1,17 @@ +import { commands } from '@/commands.gen' +import { appSettings } from '@/commons' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_settings/general')({ + beforeLoad: async () => { + const res = await commands.getUserSettings() + + if (res.status === 'ok') { + appSettings.setState((state) => { + return { ...state, ...res.data } + }) + } else { + throw new Error(res.error) + } + }, +}) diff --git a/src/routes/_settings/profile.lazy.tsx b/src/routes/_settings/profile.lazy.tsx new file mode 100644 index 00000000..91f037e2 --- /dev/null +++ b/src/routes/_settings/profile.lazy.tsx @@ -0,0 +1,245 @@ +import { type Profile, commands } from '@/commands.gen' +import { cn, upload } from '@/commons' +import { Spinner } from '@/components' +import { Plus } from '@phosphor-icons/react' +import { createLazyFileRoute } from '@tanstack/react-router' +import { writeText } from '@tauri-apps/plugin-clipboard-manager' +import { message } from '@tauri-apps/plugin-dialog' +import { + type Dispatch, + type ReactNode, + type SetStateAction, + useState, + useTransition, +} from 'react' +import { useForm } from 'react-hook-form' + +export const Route = createLazyFileRoute('/_settings/profile')({ + component: Screen, +}) + +function Screen() { + const { profile } = Route.useRouteContext() + const { register, handleSubmit } = useForm({ defaultValues: profile }) + + const [isPending, startTransition] = useTransition() + const [picture, setPicture] = useState('') + + const onSubmit = (data: Profile) => { + startTransition(async () => { + const newProfile: Profile = { ...profile, ...data, picture } + const res = await commands.setProfile(newProfile) + + if (res.status === 'error') { + await message(res.error, { title: 'Profile', kind: 'error' }) + } + + return + }) + } + + return ( +
+
+
+ {profile.picture ? ( + avatar + ) : null} + + + +
+
+
+
{profile.display_name}
+
+ {profile.nip05} +
+
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ ) +} + +function PrivkeyButton() { + const { account } = Route.useParams() + + const [isPending, startTransition] = useTransition() + const [isCopy, setIsCopy] = useState(false) + + const copyPrivateKey = () => { + startTransition(async () => { + const res = await commands.getPrivateKey(account) + + 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/routes/_settings/profile.tsx b/src/routes/_settings/profile.tsx new file mode 100644 index 00000000..80a80e6a --- /dev/null +++ b/src/routes/_settings/profile.tsx @@ -0,0 +1,15 @@ +import { type Profile, commands } from '@/commands.gen' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_settings/profile')({ + beforeLoad: async ({ params }) => { + const res = await commands.getProfile(params.account) + + if (res.status === 'ok') { + const profile: Profile = JSON.parse(res.data) + return { profile } + } else { + throw new Error(res.error) + } + }, +}) diff --git a/src/routes/_settings/relay.lazy.tsx b/src/routes/_settings/relay.lazy.tsx new file mode 100644 index 00000000..c21ff89d --- /dev/null +++ b/src/routes/_settings/relay.lazy.tsx @@ -0,0 +1,155 @@ +import { commands } from '@/commands.gen' +import { Plus, X } from '@phosphor-icons/react' +import { createLazyFileRoute } from '@tanstack/react-router' +import { message } from '@tauri-apps/plugin-dialog' +import { useEffect, useState, useTransition } from 'react' + +export const Route = createLazyFileRoute('/_settings/relay')({ + component: Screen, +}) + +function Screen() { + const { relayList } = Route.useRouteContext() + + const [relays, setRelays] = useState([]) + const [newRelay, setNewRelay] = useState('') + const [isPending, startTransition] = useTransition() + + const removeRelay = async (relay: string) => { + const res = await commands.removeRelay(relay) + + if (res.status === 'ok') { + return res.data + } else { + throw new Error(res.error) + } + } + + const addNewRelay = () => { + startTransition(async () => { + try { + let url = newRelay + + if (!url.startsWith('wss://')) { + url = `wss://${url}` + } + + const relay = new URL(url) + const res = await commands.connectRelay(relay.toString()) + + if (res.status === 'ok') { + setRelays((prev) => [...prev, newRelay]) + setNewRelay('') + } else { + await message(res.error, { title: 'Relay', kind: 'error' }) + return + } + } catch { + await message('URL is not valid.', { kind: 'error' }) + return + } + }) + } + + useEffect(() => { + setRelays(relayList.connected) + }, [relayList]) + + return ( +
+
+
+

+ Connected Relays +

+
+ {relays.map((relay) => ( +
+
+ + + + + {relay} +
+
+ +
+
+ ))} +
+
+ setNewRelay(e.target.value)} + name="url" + placeholder="wss://..." + spellCheck={false} + className="flex-1 px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring-0 dark:border-neutral-700 dark:placeholder:text-neutral-400" + /> + +
+
+
+
+
+

+ User Relays (NIP-65) +

+
+

+ Lume will automatically connect to the user's relay list, but the + manager function (like adding, removing, changing relay purpose) + is not yet available. +

+
+
+ {relayList.read?.map((relay) => ( +
+
{relay}
+
READ
+
+ ))} + {relayList.write?.map((relay) => ( +
+
{relay}
+
WRITE
+
+ ))} + {relayList.both?.map((relay) => ( +
+
{relay}
+
READ + WRITE
+
+ ))} +
+
+
+
+ ) +} diff --git a/src/routes/_settings/relay.tsx b/src/routes/_settings/relay.tsx new file mode 100644 index 00000000..a71d8d2e --- /dev/null +++ b/src/routes/_settings/relay.tsx @@ -0,0 +1,15 @@ +import { commands } from '@/commands.gen' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_settings/relay')({ + beforeLoad: async () => { + const res = await commands.getRelays() + + if (res.status === 'ok') { + const relayList = res.data + return { relayList } + } else { + throw new Error(res.error) + } + }, +}) diff --git a/src/routes/_settings/wallet.lazy.tsx b/src/routes/_settings/wallet.lazy.tsx new file mode 100644 index 00000000..1cbeea01 --- /dev/null +++ b/src/routes/_settings/wallet.lazy.tsx @@ -0,0 +1,55 @@ +import { commands } from '@/commands.gen' +import { createLazyFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/_settings/wallet')({ + component: Screen, +}) + +function Screen() { + const { account } = Route.useParams() + const { balance } = Route.useRouteContext() + + const disconnect = async () => { + const res = await commands.removeWallet() + + if (res.status === 'ok') { + window.localStorage.removeItem('bc:config') + return redirect({ to: '/$account/bitcoin-connect', params: { account } }) + } else { + throw new Error(res.error) + } + } + + return ( +
+
+
+
+
+

Connection

+
+
+ +
+
+
+
+
+
+

Current Balance

+
+
+ ₿ {balance.bitcoinFormatted} +
+
+
+
+
+ ) +} diff --git a/src/routes/_settings/wallet.tsx b/src/routes/_settings/wallet.tsx new file mode 100644 index 00000000..db49b8a2 --- /dev/null +++ b/src/routes/_settings/wallet.tsx @@ -0,0 +1,21 @@ +import { commands } from '@/commands.gen' +import { getBitcoinDisplayValues } from '@/commons' +import { createFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createFileRoute('/_settings/wallet')({ + beforeLoad: async ({ params }) => { + const query = await commands.loadWallet() + + if (query.status === 'ok') { + const wallet = Number.parseInt(query.data) + const balance = getBitcoinDisplayValues(wallet) + + return { balance } + } else { + throw redirect({ + to: '/$account/bitcoin-connect', + params: { account: params.account }, + }) + } + }, +}) diff --git a/src/routes/columns/_layout.tsx b/src/routes/columns/_layout.tsx index f1980c45..2f2a24bc 100644 --- a/src/routes/columns/_layout.tsx +++ b/src/routes/columns/_layout.tsx @@ -1,12 +1,16 @@ import { commands } from "@/commands.gen"; import { appSettings } from "@/commons"; -import type { ColumnRouteSearch } from "@/types"; import { Outlet, createFileRoute } from "@tanstack/react-router"; +export interface RouteSearch { + label?: string; + name?: string; + redirect?: string; +} + export const Route = createFileRoute("/columns/_layout")({ - validateSearch: (search: Record): ColumnRouteSearch => { + validateSearch: (search: Record): RouteSearch => { return { - account: search.account, label: search.label, name: search.name, }; diff --git a/src/routes/columns/_layout/global.tsx b/src/routes/columns/_layout/global.tsx index 456b9bde..154da3e4 100644 --- a/src/routes/columns/_layout/global.tsx +++ b/src/routes/columns/_layout/global.tsx @@ -15,7 +15,7 @@ export const Route = createFileRoute("/columns/_layout/global")({ }); export function Screen() { - const { label, account } = Route.useSearch(); + const { label } = Route.useSearch(); const { data, isLoading, @@ -24,7 +24,7 @@ export function Screen() { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - queryKey: [label, account], + queryKey: [label], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { const until = pageParam > 0 ? pageParam.toString() : undefined; diff --git a/src/routes/columns/_layout/launchpad.lazy.tsx b/src/routes/columns/_layout/launchpad.lazy.tsx index 3f531bec..e12986e1 100644 --- a/src/routes/columns/_layout/launchpad.lazy.tsx +++ b/src/routes/columns/_layout/launchpad.lazy.tsx @@ -26,6 +26,7 @@ function Screen() { + { - const systemPath = "resources/columns.json"; - const resourcePath = await resolveResource(systemPath); - const resourceFile = await readTextFile(resourcePath); - - const systemColumns: LumeColumn[] = JSON.parse(resourceFile); - const columns = systemColumns.filter((col) => !col.default); - - return columns; - }, - refetchOnWindowFocus: false, - }); - - return ( -
-
-

Core

-
-
- {isLoading ? ( -
- - Loading... -
- ) : ( - data.map((column) => ( -
-
-
- {column.name} -
-
- {column.description} -
-
- -
- )) - )} -
-
- ); -} - function Groups() { - const { account } = Route.useSearch(); const { isLoading, data, refetch, isRefetching } = useQuery({ - queryKey: ["groups", account], + queryKey: ["groups"], queryFn: async () => { const res = await commands.getAllGroups(); @@ -125,23 +69,32 @@ function Groups() { return (
-
- {item.tags - .filter((tag) => tag[0] === "p") - .map((tag) => ( -
- - - - - -
- ))} +
+
+ {item.tags + .filter((tag) => tag[0] === "p") + .map((tag) => ( +
+ + + + + +
+ ))} +
-
-
{name}
+
+
+ + + + + +
{name}
+
); } + +function Accounts() { + const { isLoading, data: accounts } = useQuery({ + queryKey: ["accounts"], + queryFn: async () => { + const res = await commands.getAccounts(); + return res; + }, + refetchOnWindowFocus: false, + }); + + return ( +
+
+

Accounts

+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : ( + accounts.map((account) => ( +
+
+ + + + + + +
+
+
+
Newsfeed
+ +
+
+
Stories
+ +
+
+
Notification
+ +
+
+
+ )) + )} +
+
+ ); +} + +function Core() { + const { isLoading, data } = useQuery({ + queryKey: ["other-columns"], + queryFn: async () => { + const systemPath = "resources/columns.json"; + const resourcePath = await resolveResource(systemPath); + const resourceFile = await readTextFile(resourcePath); + + const systemColumns: LumeColumn[] = JSON.parse(resourceFile); + const columns = systemColumns.filter((col) => !col.default); + + return columns; + }, + refetchOnWindowFocus: false, + }); + + return ( +
+
+

Others

+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : ( + data.map((column) => ( +
+
+
+ {column.name} +
+
+ {column.description} +
+
+ +
+ )) + )} +
+
+ ); +} diff --git a/src/routes/columns/_layout/newsfeed.$id.lazy.tsx b/src/routes/columns/_layout/newsfeed.$id.lazy.tsx new file mode 100644 index 00000000..b6657600 --- /dev/null +++ b/src/routes/columns/_layout/newsfeed.$id.lazy.tsx @@ -0,0 +1,229 @@ +import { events, commands } from '@/commands.gen' +import { toLumeEvents } from '@/commons' +import { RepostNote, Spinner, TextNote } from '@/components' +import { LumeEvent } from '@/system' +import { Kind, type Meta } from '@/types' +import { ArrowDown, ArrowUp } from '@phosphor-icons/react' +import * as ScrollArea from '@radix-ui/react-scroll-area' +import { type InfiniteData, useInfiniteQuery } from '@tanstack/react-query' +import { createLazyFileRoute } from '@tanstack/react-router' +import { getCurrentWindow } from '@tauri-apps/api/window' +import { + memo, + useCallback, + useEffect, + useRef, + useState, + useTransition, +} from 'react' +import { Virtualizer } from 'virtua' + +type Payload = { + raw: string + parsed: Meta +} + +export const Route = createLazyFileRoute('/columns/_layout/newsfeed/$id')({ + component: Screen, +}) + +export function Screen() { + const contacts = Route.useLoaderData() + const { label } = Route.useSearch() + const { + data, + isLoading, + isFetching, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: [label], + initialPageParam: 0, + queryFn: async ({ pageParam }: { pageParam: number }) => { + const until = pageParam > 0 ? pageParam.toString() : undefined + const res = await commands.getAllEventsByAuthors(contacts, until) + + if (res.status === 'error') { + throw new Error(res.error) + } + + return toLumeEvents(res.data) + }, + getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1, + select: (data) => data?.pages.flat(), + enabled: contacts?.length > 0, + }) + + const ref = useRef(null) + + const renderItem = useCallback( + (event: LumeEvent) => { + if (!event) return + switch (event.kind) { + case Kind.Repost: + return ( + + ) + default: + return ( + + ) + } + }, + [data], + ) + + return ( + + + + + {isFetching && !isLoading && !isFetchingNextPage ? ( +
+
+ + Getting new notes... +
+
+ ) : null} + {isLoading ? ( +
+ + Loading... +
+ ) : !data.length ? ( +
+ 🎉 Yo. You're catching up on all latest notes. +
+ ) : ( + data.map((item) => renderItem(item)) + )} + {hasNextPage ? ( +
+ +
+ ) : null} +
+
+ + + + +
+ ) +} + +const Listener = memo(function Listerner() { + const { queryClient } = Route.useRouteContext() + const { label } = Route.useSearch() + + const [lumeEvents, setLumeEvents] = useState([]) + const [isPending, startTransition] = useTransition() + + const queryStatus = queryClient.getQueryState([label]) + + const pushNewEvents = () => { + startTransition(() => { + queryClient.setQueryData( + [label], + (oldData: InfiniteData | undefined) => { + if (oldData) { + const firstPage = oldData.pages[0] + const newPage = [...lumeEvents, ...firstPage] + + return { + ...oldData, + pages: [newPage, ...oldData.pages.slice(1)], + } + } + }, + ) + + // Reset array + setLumeEvents([]) + + return + }) + } + + useEffect(() => { + events.subscription + .emit({ label, kind: 'Subscribe', event_id: undefined }) + .then(() => console.log('Subscribe: ', label)) + + return () => { + events.subscription + .emit({ + label, + kind: 'Unsubscribe', + event_id: undefined, + }) + .then(() => console.log('Unsubscribe: ', label)) + } + }, []) + + useEffect(() => { + const unlisten = getCurrentWindow().listen('event', (data) => { + const event = LumeEvent.from(data.payload.raw, data.payload.parsed) + setLumeEvents((prev) => [event, ...prev]) + }) + + return () => { + unlisten.then((f) => f()) + } + }, []) + + if (lumeEvents.length && queryStatus.fetchStatus !== 'fetching') { + return ( +
+ +
+ ) + } + + return null +}) diff --git a/src/routes/columns/_layout/newsfeed.tsx b/src/routes/columns/_layout/newsfeed.$id.tsx similarity index 56% rename from src/routes/columns/_layout/newsfeed.tsx rename to src/routes/columns/_layout/newsfeed.$id.tsx index b45eafa9..ba4422c4 100644 --- a/src/routes/columns/_layout/newsfeed.tsx +++ b/src/routes/columns/_layout/newsfeed.$id.tsx @@ -1,9 +1,9 @@ import { commands } from "@/commands.gen"; import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/columns/_layout/newsfeed")({ - loader: async () => { - const res = await commands.getContactList(); +export const Route = createFileRoute("/columns/_layout/newsfeed/$id")({ + loader: async ({ params }) => { + const res = await commands.getContactList(params.id); if (res.status === "ok") { return res.data; diff --git a/src/routes/columns/_layout/newsfeed.lazy.tsx b/src/routes/columns/_layout/newsfeed.lazy.tsx deleted file mode 100644 index 94b8ae56..00000000 --- a/src/routes/columns/_layout/newsfeed.lazy.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { events, commands } from "@/commands.gen"; -import { toLumeEvents } from "@/commons"; -import { RepostNote, Spinner, TextNote } from "@/components"; -import { LumeEvent } from "@/system"; -import { Kind, type Meta } from "@/types"; -import { ArrowDown, ArrowUp } from "@phosphor-icons/react"; -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { getCurrentWindow } from "@tauri-apps/api/window"; -import { - memo, - useCallback, - useEffect, - useRef, - useState, - useTransition, -} from "react"; -import { Virtualizer } from "virtua"; - -type Payload = { - raw: string; - parsed: Meta; -}; - -export const Route = createLazyFileRoute("/columns/_layout/newsfeed")({ - component: Screen, -}); - -export function Screen() { - const contacts = Route.useLoaderData(); - const { label, account } = Route.useSearch(); - const { - data, - isLoading, - isFetching, - isFetchingNextPage, - hasNextPage, - fetchNextPage, - } = useInfiniteQuery({ - queryKey: [label, account], - initialPageParam: 0, - queryFn: async ({ pageParam }: { pageParam: number }) => { - const until = pageParam > 0 ? pageParam.toString() : undefined; - const res = await commands.getAllEventsByAuthors(contacts, until); - - if (res.status === "error") { - throw new Error(res.error); - } - - return toLumeEvents(res.data); - }, - getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1, - select: (data) => data?.pages.flat(), - enabled: contacts?.length > 0, - }); - - const ref = useRef(null); - - const renderItem = useCallback( - (event: LumeEvent) => { - if (!event) return; - switch (event.kind) { - case Kind.Repost: - return ( - - ); - default: - return ( - - ); - } - }, - [data], - ); - - return ( - - - - - {isFetching && !isLoading && !isFetchingNextPage ? ( -
-
- - Getting new notes... -
-
- ) : null} - {isLoading ? ( -
- - Loading... -
- ) : !data.length ? ( -
- 🎉 Yo. You're catching up on all latest notes. -
- ) : ( - data.map((item) => renderItem(item)) - )} - {hasNextPage ? ( -
- -
- ) : null} -
-
- - - - -
- ); -} - -const Listener = memo(function Listerner() { - const { queryClient } = Route.useRouteContext(); - const { label, account } = Route.useSearch(); - - const [lumeEvents, setLumeEvents] = useState([]); - const [isPending, startTransition] = useTransition(); - - const queryStatus = queryClient.getQueryState([label, account]); - - const pushNewEvents = () => { - startTransition(() => { - queryClient.setQueryData( - [label, account], - (oldData: InfiniteData | undefined) => { - if (oldData) { - const firstPage = oldData.pages[0]; - const newPage = [...lumeEvents, ...firstPage]; - - return { - ...oldData, - pages: [newPage, ...oldData.pages.slice(1)], - }; - } - }, - ); - - // Reset array - setLumeEvents([]); - - return; - }); - }; - - useEffect(() => { - events.subscription - .emit({ label, kind: "Subscribe", event_id: undefined }) - .then(() => console.log("Subscribe: ", label)); - - return () => { - events.subscription - .emit({ - label, - kind: "Unsubscribe", - event_id: undefined, - }) - .then(() => console.log("Unsubscribe: ", label)); - }; - }, []); - - useEffect(() => { - const unlisten = getCurrentWindow().listen("event", (data) => { - const event = LumeEvent.from(data.payload.raw, data.payload.parsed); - setLumeEvents((prev) => [event, ...prev]); - }); - - return () => { - unlisten.then((f) => f()); - }; - }, []); - - if (lumeEvents.length && queryStatus.fetchStatus !== "fetching") { - return ( -
- -
- ); - } - - return null; -}); diff --git a/src/routes/columns/_layout/notification.$id.lazy.tsx b/src/routes/columns/_layout/notification.$id.lazy.tsx new file mode 100644 index 00000000..65ea2011 --- /dev/null +++ b/src/routes/columns/_layout/notification.$id.lazy.tsx @@ -0,0 +1,331 @@ +import { commands } from '@/commands.gen' +import { decodeZapInvoice, formatCreatedAt } from '@/commons' +import { Note, RepostIcon, Spinner, User } from '@/components' +import { LumeEvent, LumeWindow, useEvent } from '@/system' +import { Kind, type NostrEvent } from '@/types' +import { Info } from '@phosphor-icons/react' +import * as ScrollArea from '@radix-ui/react-scroll-area' +import * as Tabs from '@radix-ui/react-tabs' +import { useQuery } from '@tanstack/react-query' +import { createLazyFileRoute } from '@tanstack/react-router' +import { getCurrentWindow } from '@tauri-apps/api/window' +import { type ReactNode, useEffect, useMemo, useRef } from 'react' +import { Virtualizer } from 'virtua' + +export const Route = createLazyFileRoute('/columns/_layout/notification/$id')({ + component: Screen, +}) + +function Screen() { + const { queryClient } = Route.useRouteContext() + const { isLoading, data } = useQuery({ + queryKey: ['notification'], + queryFn: async () => { + const res = await commands.getNotifications() + + if (res.status === 'error') { + throw new Error(res.error) + } + + const data: NostrEvent[] = res.data.map((item) => JSON.parse(item)) + const events = data.map((ev) => new LumeEvent(ev)) + + return events + }, + select: (events) => { + const zaps = new Map() + const reactions = new Map() + + const texts = events.filter((ev) => ev.kind === Kind.Text) + const zapEvents = events.filter((ev) => ev.kind === Kind.ZapReceipt) + const reactEvents = events.filter( + (ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction, + ) + + for (const event of reactEvents) { + const rootId = event.tags.filter((tag) => tag[0] === 'e')[0]?.[1] + + if (rootId) { + if (reactions.has(rootId)) { + reactions.get(rootId).push(event) + } else { + reactions.set(rootId, [event]) + } + } + } + + for (const event of zapEvents) { + const rootId = event.tags.filter((tag) => tag[0] === 'e')[0]?.[1] + + if (rootId) { + if (zaps.has(rootId)) { + zaps.get(rootId).push(event) + } else { + zaps.set(rootId, [event]) + } + } + } + + return { texts, zaps, reactions } + }, + refetchOnWindowFocus: false, + }) + + useEffect(() => { + const unlisten = getCurrentWindow().listen('event', async (data) => { + const event: LumeEvent = JSON.parse(data.payload as string) + await queryClient.setQueryData(['notification'], (data: LumeEvent[]) => [ + event, + ...data, + ]) + }) + + return () => { + unlisten.then((f) => f()) + } + }, []) + + if (isLoading) { + return ( +
+ +
+ ) + } + + return ( +
+ + + + Replies + + + Reactions + + + Zaps + + + + + {data.texts.map((event) => ( + + ))} + + + {[...data.reactions.entries()].map(([root, events]) => ( +
+
+
+ +
+
+ {events.map((event) => ( + + + +
+ {event.kind === Kind.Reaction ? ( + event.content === '+' ? ( + '👍' + ) : ( + event.content + ) + ) : ( + + )} +
+
+
+ ))} +
+
+
+ ))} +
+ + {[...data.zaps.entries()].map(([root, events]) => ( +
+
+
+ +
+
+ {events.map((event) => ( + + ))} +
+
+
+ ))} +
+ + + + +
+
+
+ ) +} + +function Tab({ value, children }: { value: string; children: ReactNode[] }) { + const ref = useRef(null) + + return ( + + + {children} + + + ) +} + +function RootNote({ id }: { id: string }) { + const { isLoading, isError, data } = useEvent(id) + + if (isLoading) { + return ( +
+
+
+
+ ) + } + + if (isError || !data) { + return ( +
+
+ +
+

+ Event not found with your current relay set +

+
+ ) + } + + return ( + + + + + + + +
{data.content}
+
+
+ ) +} + +function TextNote({ event }: { event: LumeEvent }) { + const pTags = event.tags + .filter((tag) => tag[0] === 'p') + .map((tag) => tag[1]) + .slice(0, 3) + + return ( + + ) +} + +function ZapReceipt({ event }: { event: LumeEvent }) { + const amount = useMemo( + () => decodeZapInvoice(event.tags).bitcoinFormatted ?? '0', + [event.id], + ) + const sender = useMemo( + () => event.tags.find((tag) => tag[0] === 'P')?.[1], + [event.id], + ) + + if (!sender) { + return ( +
+
+
+ ₿ {amount} +
+
+ ) + } + + return ( + + + +
+ ₿ {amount} +
+
+
+ ) +} diff --git a/src/routes/columns/_layout/notification.lazy.tsx b/src/routes/columns/_layout/notification.lazy.tsx deleted file mode 100644 index da12877b..00000000 --- a/src/routes/columns/_layout/notification.lazy.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import { commands } from "@/commands.gen"; -import { decodeZapInvoice, formatCreatedAt } from "@/commons"; -import { Note, RepostIcon, Spinner, User } from "@/components"; -import { LumeEvent, LumeWindow, useEvent } from "@/system"; -import { Kind, type NostrEvent } from "@/types"; -import { Info } from "@phosphor-icons/react"; -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import * as Tabs from "@radix-ui/react-tabs"; -import { useQuery } from "@tanstack/react-query"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { getCurrentWindow } from "@tauri-apps/api/window"; -import { nip19 } from "nostr-tools"; -import { type ReactNode, useEffect, useMemo, useRef } from "react"; -import { Virtualizer } from "virtua"; - -export const Route = createLazyFileRoute("/columns/_layout/notification")({ - component: Screen, -}); - -function Screen() { - const { account } = Route.useSearch(); - const { queryClient } = Route.useRouteContext(); - const { isLoading, data } = useQuery({ - queryKey: ["notification", account], - queryFn: async () => { - const res = await commands.getNotifications(); - - if (res.status === "error") { - throw new Error(res.error); - } - - const data: NostrEvent[] = res.data.map((item) => JSON.parse(item)); - const events = data.map((ev) => new LumeEvent(ev)); - - return events; - }, - select: (events) => { - const zaps = new Map(); - const reactions = new Map(); - const hex = nip19.decode(account).data; - - const texts = events.filter( - (ev) => ev.kind === Kind.Text && ev.pubkey !== hex, - ); - const zapEvents = events.filter((ev) => ev.kind === Kind.ZapReceipt); - const reactEvents = events.filter( - (ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction, - ); - - for (const event of reactEvents) { - const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1]; - - if (rootId) { - if (reactions.has(rootId)) { - reactions.get(rootId).push(event); - } else { - reactions.set(rootId, [event]); - } - } - } - - for (const event of zapEvents) { - const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1]; - - if (rootId) { - if (zaps.has(rootId)) { - zaps.get(rootId).push(event); - } else { - zaps.set(rootId, [event]); - } - } - } - - return { texts, zaps, reactions }; - }, - refetchOnWindowFocus: false, - }); - - useEffect(() => { - const unlisten = getCurrentWindow().listen("event", async (data) => { - const event: LumeEvent = JSON.parse(data.payload as string); - await queryClient.setQueryData( - ["notification", account], - (data: LumeEvent[]) => [event, ...data], - ); - }); - - return () => { - unlisten.then((f) => f()); - }; - }, [account]); - - if (isLoading) { - return ( -
- -
- ); - } - - return ( -
- - - - Replies - - - Reactions - - - Zaps - - - - - {data.texts.map((event, index) => ( - - ))} - - - {[...data.reactions.entries()].map(([root, events]) => ( -
-
-
- -
-
- {events.map((event) => ( - - - -
- {event.kind === Kind.Reaction ? ( - event.content === "+" ? ( - "👍" - ) : ( - event.content - ) - ) : ( - - )} -
-
-
- ))} -
-
-
- ))} -
- - {[...data.zaps.entries()].map(([root, events]) => ( -
-
-
- -
-
- {events.map((event) => ( - - ))} -
-
-
- ))} -
- - - - -
-
-
- ); -} - -function Tab({ value, children }: { value: string; children: ReactNode[] }) { - const ref = useRef(null); - - return ( - - - {children} - - - ); -} - -function RootNote({ id }: { id: string }) { - const { isLoading, isError, data } = useEvent(id); - - if (isLoading) { - return ( -
-
-
-
- ); - } - - if (isError || !data) { - return ( -
-
- -
-

- Event not found with your current relay set -

-
- ); - } - - return ( - - - - - - - -
{data.content}
-
-
- ); -} - -function TextNote({ event }: { event: LumeEvent }) { - const pTags = event.tags - .filter((tag) => tag[0] === "p") - .map((tag) => tag[1]) - .slice(0, 3); - - return ( - - ); -} - -function ZapReceipt({ event }: { event: LumeEvent }) { - const amount = useMemo( - () => decodeZapInvoice(event.tags).bitcoinFormatted ?? "0", - [event.id], - ); - const sender = useMemo( - () => event.tags.find((tag) => tag[0] === "P")?.[1], - [event.id], - ); - - if (!sender) { - return ( -
-
-
- ₿ {amount} -
-
- ); - } - - return ( - - - -
- ₿ {amount} -
-
-
- ); -} diff --git a/src/routes/columns/_layout/stories.$id.lazy.tsx b/src/routes/columns/_layout/stories.$id.lazy.tsx new file mode 100644 index 00000000..c933cd65 --- /dev/null +++ b/src/routes/columns/_layout/stories.$id.lazy.tsx @@ -0,0 +1,240 @@ +import { commands } from '@/commands.gen' +import { replyTime, toLumeEvents } from '@/commons' +import { Note, Spinner, User } from '@/components' +import { Hashtag } from '@/components/note/mentions/hashtag' +import { MentionUser } from '@/components/note/mentions/user' +import { type LumeEvent, LumeWindow } from '@/system' +import { Kind } from '@/types' +import { ArrowRight } from '@phosphor-icons/react' +import * as ScrollArea from '@radix-ui/react-scroll-area' +import { useQuery } from '@tanstack/react-query' +import { createLazyFileRoute } from '@tanstack/react-router' +import { nip19 } from 'nostr-tools' +import { type ReactNode, memo, useMemo, useRef } from 'react' +import reactStringReplace from 'react-string-replace' +import { Virtualizer } from 'virtua' + +export const Route = createLazyFileRoute('/columns/_layout/stories/$id')({ + component: Screen, +}) + +function Screen() { + const contacts = Route.useLoaderData() + const ref = useRef(null) + + return ( + + + + {!contacts ? ( +
+ +
+ ) : ( + contacts.map((contact) => ( + + )) + )} +
+
+ + + + +
+ ) +} + +function StoryItem({ contact }: { contact: string }) { + const { + isLoading, + isError, + error, + data: events, + } = useQuery({ + queryKey: ['stories', contact], + queryFn: async () => { + const res = await commands.getAllEventsByAuthor(contact, 10) + + if (res.status === 'ok') { + const data = toLumeEvents(res.data) + return data + } else { + throw new Error(res.error) + } + }, + select: (data) => data.filter((ev) => ev.kind === Kind.Text), + refetchOnWindowFocus: false, + }) + + const ref = useRef(null) + + return ( +
+
+ + + + + + +
+ +
+
+ + + + {isLoading ? ( +
+ +
+ ) : isError ? ( +
+ {error.message} +
+ ) : !events.length ? ( +
+ This user didn't have any new notes. +
+ ) : ( + events.map((event) => ) + )} +
+
+ + + + +
+
+ ) +} + +const StoryEvent = memo(function StoryEvent({ event }: { event: LumeEvent }) { + return ( + + + +
+ + +
+
+ + {replyTime(event.created_at)} + +
+ + + +
+
+
+
+
+ ) +}) + +function Content({ text, className }: { text: string; className?: string }) { + const content = useMemo(() => { + let replacedText: ReactNode[] | string = text.trim() + + const nostr = replacedText + .split(/\s+/) + .filter((w) => w.startsWith('nostr:')) + + replacedText = reactStringReplace(text, /(https?:\/\/\S+)/g, (match, i) => ( + + {match} + + )) + + replacedText = reactStringReplace(replacedText, /#(\w+)/g, (match, i) => ( + + )) + + for (const word of nostr) { + const bech32 = word.replace('nostr:', '').replace(/[^\w\s]/gi, '') + + try { + const data = nip19.decode(bech32) + + switch (data.type) { + case 'npub': + replacedText = reactStringReplace( + replacedText, + word, + (match, i) => , + ) + break + case 'nprofile': + replacedText = reactStringReplace( + replacedText, + word, + (match, i) => ( + + ), + ) + break + default: + replacedText = reactStringReplace( + replacedText, + word, + (match, i) => ( + + {match} + + ), + ) + break + } + } catch { + console.log(word) + } + } + + return replacedText + }, [text]) + + return
{content}
+} diff --git a/src/routes/columns/_layout/stories.$id.tsx b/src/routes/columns/_layout/stories.$id.tsx new file mode 100644 index 00000000..841afec3 --- /dev/null +++ b/src/routes/columns/_layout/stories.$id.tsx @@ -0,0 +1,14 @@ +import { commands } from '@/commands.gen' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/columns/_layout/stories/$id')({ + loader: async () => { + const res = await commands.getContactList() + + if (res.status === 'ok') { + return res.data + } else { + throw new Error(res.error) + } + }, +}) diff --git a/src/routes/columns/_layout/stories.lazy.tsx b/src/routes/columns/_layout/stories.lazy.tsx deleted file mode 100644 index 29e3f53b..00000000 --- a/src/routes/columns/_layout/stories.lazy.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { commands } from "@/commands.gen"; -import { replyTime, toLumeEvents } from "@/commons"; -import { Note, Spinner, User } from "@/components"; -import { Hashtag } from "@/components/note/mentions/hashtag"; -import { MentionUser } from "@/components/note/mentions/user"; -import { type LumeEvent, LumeWindow } from "@/system"; -import { Kind } from "@/types"; -import { ArrowRight } from "@phosphor-icons/react"; -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import { useQuery } from "@tanstack/react-query"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { nip19 } from "nostr-tools"; -import { type ReactNode, memo, useMemo, useRef } from "react"; -import reactStringReplace from "react-string-replace"; -import { Virtualizer } from "virtua"; - -export const Route = createLazyFileRoute("/columns/_layout/stories")({ - component: Screen, -}); - -function Screen() { - const contacts = Route.useLoaderData(); - const ref = useRef(null); - - return ( - - - - {!contacts ? ( -
- -
- ) : ( - contacts.map((contact) => ( - - )) - )} -
-
- - - - -
- ); -} - -function StoryItem({ contact }: { contact: string }) { - const { - isLoading, - isError, - error, - data: events, - } = useQuery({ - queryKey: ["stories", contact], - queryFn: async () => { - const res = await commands.getAllEventsByAuthor(contact, 10); - - if (res.status === "ok") { - const data = toLumeEvents(res.data); - return data; - } else { - throw new Error(res.error); - } - }, - select: (data) => data.filter((ev) => ev.kind === Kind.Text), - refetchOnWindowFocus: false, - }); - - const ref = useRef(null); - - return ( -
-
- - - - - - -
- -
-
- - - - {isLoading ? ( -
- -
- ) : isError ? ( -
- {error.message} -
- ) : !events.length ? ( -
- This user didn't have any new notes. -
- ) : ( - events.map((event) => ) - )} -
-
- - - - -
-
- ); -} - -const StoryEvent = memo(function StoryEvent({ event }: { event: LumeEvent }) { - return ( - - - -
- - -
-
- - {replyTime(event.created_at)} - -
- - - -
-
-
-
-
- ); -}); - -function Content({ text, className }: { text: string; className?: string }) { - const content = useMemo(() => { - let replacedText: ReactNode[] | string = text.trim(); - - const nostr = replacedText - .split(/\s+/) - .filter((w) => w.startsWith("nostr:")); - - replacedText = reactStringReplace(text, /(https?:\/\/\S+)/g, (match, i) => ( - - {match} - - )); - - replacedText = reactStringReplace(replacedText, /#(\w+)/g, (match, i) => ( - - )); - - for (const word of nostr) { - const bech32 = word.replace("nostr:", "").replace(/[^\w\s]/gi, ""); - - try { - const data = nip19.decode(bech32); - - switch (data.type) { - case "npub": - replacedText = reactStringReplace( - replacedText, - word, - (match, i) => , - ); - break; - case "nprofile": - replacedText = reactStringReplace( - replacedText, - word, - (match, i) => ( - - ), - ); - break; - default: - replacedText = reactStringReplace( - replacedText, - word, - (match, i) => ( - - {match} - - ), - ); - break; - } - } catch { - console.log(word); - } - } - - return replacedText; - }, [text]); - - return
{content}
; -} diff --git a/src/routes/columns/_layout/stories.tsx b/src/routes/columns/_layout/stories.tsx deleted file mode 100644 index 5c28cdff..00000000 --- a/src/routes/columns/_layout/stories.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { commands } from "@/commands.gen"; -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/columns/_layout/stories")({ - loader: async () => { - const res = await commands.getContactList(); - - if (res.status === "ok") { - return res.data; - } else { - throw new Error(res.error); - } - }, -}); diff --git a/src/routes/columns/_layout/trending.lazy.tsx b/src/routes/columns/_layout/trending.lazy.tsx index 9777e9f9..06a6b83c 100644 --- a/src/routes/columns/_layout/trending.lazy.tsx +++ b/src/routes/columns/_layout/trending.lazy.tsx @@ -13,7 +13,7 @@ export const Route = createLazyFileRoute("/columns/_layout/trending")({ function Screen() { const { isLoading, data } = useQuery({ - queryKey: ["trending-notes"], + queryKey: ["trending"], queryFn: async ({ signal }) => { const res = await fetch("https://api.nostr.band/v0/trending/notes", { signal, diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx deleted file mode 100644 index d1f3af4f..00000000 --- a/src/routes/index.lazy.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { commands } from "@/commands.gen"; -import { appSettings, displayNpub } from "@/commons"; -import { Frame, Spinner, User } from "@/components"; -import { ArrowRight, DotsThree, GearSix, Plus } from "@phosphor-icons/react"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { Menu, MenuItem } from "@tauri-apps/api/menu"; -import { message } from "@tauri-apps/plugin-dialog"; -import { - useCallback, - useEffect, - useMemo, - useState, - useTransition, -} from "react"; - -export const Route = createLazyFileRoute("/")({ - component: Screen, -}); - -function Screen() { - const context = Route.useRouteContext(); - const navigate = Route.useNavigate(); - - const currentDate = useMemo( - () => - new Date().toLocaleString("default", { - weekday: "long", - month: "long", - day: "numeric", - }), - [], - ); - - const [accounts, setAccounts] = useState([]); - const [value, setValue] = useState(""); - const [autoLogin, setAutoLogin] = useState(false); - const [password, setPassword] = useState(""); - const [isPending, startTransition] = useTransition(); - - const showContextMenu = useCallback( - async (e: React.MouseEvent, account: string) => { - e.stopPropagation(); - - const menuItems = await Promise.all([ - MenuItem.new({ - text: "Reset password", - enabled: !account.includes("_nostrconnect"), - // @ts-ignore, this is tanstack router bug - action: () => navigate({ to: "/reset", search: { account } }), - }), - MenuItem.new({ - text: "Delete account", - action: async () => await deleteAccount(account), - }), - ]); - - const menu = await Menu.new({ - items: menuItems, - }); - - await menu.popup().catch((e) => console.error(e)); - }, - [], - ); - - const deleteAccount = async (account: string) => { - const res = await commands.deleteAccount(account); - - if (res.status === "ok") { - setAccounts((prev) => prev.filter((item) => item !== account)); - } - }; - - const selectAccount = (account: string) => { - setValue(account); - - if (account.includes("_nostrconnect")) { - setAutoLogin(true); - } - }; - - const loginWith = () => { - startTransition(async () => { - const res = await commands.login(value, password); - - if (res.status === "ok") { - const settings = await commands.getUserSettings(); - - if (settings.status === "ok") { - appSettings.setState(() => settings.data); - } - - const status = await commands.isAccountSync(res.data); - - if (status) { - navigate({ - to: "/$account/home", - // @ts-ignore, this is tanstack router bug - params: { account: res.data }, - replace: true, - }); - } else { - navigate({ - to: "/loading", - // @ts-ignore, this is tanstack router bug - search: { account: res.data }, - replace: true, - }); - } - } else { - await message(res.error, { title: "Login", kind: "error" }); - return; - } - }); - }; - - useEffect(() => { - if (autoLogin) { - loginWith(); - } - }, [autoLogin, value]); - - useEffect(() => { - setAccounts(context.accounts); - }, [context.accounts]); - - return ( -
-
-
-

- {currentDate} -

-

Welcome back!

-
- - {accounts.map((account) => ( -
selectAccount(account)} - onKeyDown={() => selectAccount(account)} - className="group flex items-center gap-2 hover:bg-black/5 dark:hover:bg-white/5 p-3" - > - - - - {value === account && !value.includes("_nostrconnect") ? ( -
- setPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") loginWith(); - }} - disabled={isPending} - placeholder="Password" - className="px-3 rounded-full w-full h-10 bg-transparent border border-neutral-200 dark:border-neutral-500 focus:border-blue-500 focus:outline-none placeholder:text-neutral-400" - /> -
- ) : ( -
-
- - {account.includes("_nostrconnect") ? ( -
- Nostr Connect -
- ) : null} -
- - {displayNpub(account.replace("_nostrconnect", ""), 16)} - -
- )} -
-
-
- {value === account ? ( - isPending ? ( - - ) : ( - - ) - ) : ( - - )} -
-
- ))} - -
-
- -
- - New account - -
-
- -
- -
- ); -} diff --git a/src/routes/index.tsx b/src/routes/index.tsx deleted file mode 100644 index 3fdd825c..00000000 --- a/src/routes/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { commands } from "@/commands.gen"; -import { checkForAppUpdates } from "@/commons"; -import { createFileRoute, redirect } from "@tanstack/react-router"; - -export const Route = createFileRoute("/")({ - beforeLoad: async () => { - // Check for app updates - await checkForAppUpdates(true); - - // Get all accounts - const accounts = await commands.getAccounts(); - - if (accounts.length < 1) { - throw redirect({ - to: "/new", - replace: true, - }); - } - - return { - accounts: accounts.filter((account) => !account.endsWith("Lume")), - }; - }, -}); diff --git a/src/routes/loading.tsx b/src/routes/loading.tsx deleted file mode 100644 index 9376570b..00000000 --- a/src/routes/loading.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { commands } from "@/commands.gen"; -import { Frame, Spinner } from "@/components"; -import { createFileRoute } from "@tanstack/react-router"; -import { listen } from "@tauri-apps/api/event"; -import { useEffect } from "react"; - -type RouteSearch = { - account: string; -}; - -export const Route = createFileRoute("/loading")({ - validateSearch: (search: Record): RouteSearch => { - return { - account: search.account, - }; - }, - component: Screen, -}); - -function Screen() { - const navigate = Route.useNavigate(); - const search = Route.useSearch(); - - useEffect(() => { - const unlisten = listen("neg_synchronized", async () => { - const status = await commands.createSyncFile(search.account); - - if (status) { - navigate({ - to: "/$account/home", - // @ts-ignore, this is tanstack router bug - params: { account: search.account }, - replace: true, - }); - } else { - throw new Error("System error."); - } - }); - - return () => { - unlisten.then((f) => f()); - }; - }, []); - - return ( -
- - -

- Syncing all necessary data for the first time login... -

- -
- ); -} diff --git a/src/system/window.ts b/src/system/window.ts index cc62cbd7..a1406dfc 100644 --- a/src/system/window.ts +++ b/src/system/window.ts @@ -11,7 +11,7 @@ export const LumeWindow = { column, }); }, - openColumnsGallery: async () => { + openLaunchpad: async () => { await getCurrentWindow().emit("columns", { type: "add", column: { @@ -21,23 +21,33 @@ export const LumeWindow = { }, }); }, - openLocalFeeds: async () => { + openNewsfeed: async (account: string) => { await getCurrentWindow().emit("columns", { type: "add", column: { label: "newsfeed", name: "Newsfeed", - url: "/columns/newsfeed", + url: `/columns/newsfeed/${account}`, }, }); }, - openNotification: async () => { + openStory: async (account: string) => { + await getCurrentWindow().emit("columns", { + type: "add", + column: { + label: "newsfeed", + name: "Newsfeed", + url: `/columns/stories/${account}`, + }, + }); + }, + openNotification: async (account: string) => { await getCurrentWindow().emit("columns", { type: "add", column: { label: "notification", name: "Notification", - url: "/columns/notification", + url: `/columns/notification/${account}`, }, }); }, diff --git a/src/types.ts b/src/types.ts index ec360564..78917765 100644 --- a/src/types.ts +++ b/src/types.ts @@ -50,13 +50,6 @@ export interface Metadata { lud16?: string; } -export interface ColumnRouteSearch { - account?: string; - label?: string; - name?: string; - redirect?: string; -} - export interface LumeColumn { label: string; name: string;