diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index 1e8eb716..adadd22c 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -14,7 +14,8 @@ "zap-*", "event-*", "user-*", - "editor-*" + "editor-*", + "popup-*" ], "permissions": [ "core:path:default", diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index 6b1763d4..1c5a26cc 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"column":{"identifier":"column","description":"Capability for the column","local":true,"windows":["column-*"],"permissions":["core:resources:default","core:tray:default","os:allow-locale","os:allow-os-type","clipboard-manager:allow-write-text","dialog:allow-open","dialog:allow-ask","dialog:allow-message","fs:allow-read-file","core:menu:default","core:menu:allow-new","core:menu:allow-popup","http:default","shell:allow-open","store:allow-get","store:allow-set","store:allow-delete",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]},"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["main","panel","settings","search-*","zap-*","event-*","user-*","editor-*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","core:menu:allow-new","core:menu:allow-popup","shell:allow-open","store:default","prevent-default:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}} \ No newline at end of file +{"column":{"identifier":"column","description":"Capability for the column","local":true,"windows":["column-*"],"permissions":["core:resources:default","core:tray:default","os:allow-locale","os:allow-os-type","clipboard-manager:allow-write-text","dialog:allow-open","dialog:allow-ask","dialog:allow-message","fs:allow-read-file","core:menu:default","core:menu:allow-new","core:menu:allow-popup","http:default","shell:allow-open","store:allow-get","store:allow-set","store:allow-delete",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]},"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["main","panel","settings","search-*","zap-*","event-*","user-*","editor-*","popup-*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","core:menu:allow-new","core:menu:allow-popup","shell:allow-open","store:default","prevent-default:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}} \ No newline at end of file diff --git a/src-tauri/resources/columns.json b/src-tauri/resources/columns.json index d0f44f64..ca69000b 100644 --- a/src-tauri/resources/columns.json +++ b/src-tauri/resources/columns.json @@ -20,9 +20,9 @@ { "default": false, "official": true, - "label": "local_feeds", - "name": "Local Feeds", - "description": "All notes from your follows.", + "label": "newsfeed", + "name": "Newsfeed", + "description": "All notes from you're following.", "url": "/columns/newsfeed", "picture": "" }, @@ -58,19 +58,10 @@ "official": true, "label": "global_feeds", "name": "Global Feeds", - "description": "Discover all global notes.", + "description": "All global notes from all connected relays.", "url": "/columns/global", "picture": "" }, - { - "default": false, - "official": true, - "label": "group_feeds", - "name": "Group", - "description": "Custom feeds for group of people.", - "url": "/columns/group", - "picture": "" - }, { "default": false, "official": true, diff --git a/src-tauri/src/commands/account.rs b/src-tauri/src/commands/account.rs index f6f4d1a7..40dd3516 100644 --- a/src-tauri/src/commands/account.rs +++ b/src-tauri/src/commands/account.rs @@ -271,6 +271,15 @@ pub async fn login( // NIP-65: Connect to user's relay list init_nip65(client, &public_key).await; + // NIP-03: Get user's contact list + let contact_list = { + let contacts = client.get_contact_list(None).await.unwrap(); + // Update app's state + state.contact_list.lock().await.clone_from(&contacts); + // Return + contacts + }; + // Run seperate thread for syncing data let pk = public_key.clone(); tauri::async_runtime::spawn(async move { @@ -292,7 +301,10 @@ pub async fn login( Kind::MuteList, Kind::Bookmarks, Kind::Interests, + Kind::InterestSet, + Kind::FollowSet, Kind::PinList, + Kind::EventDeletion, ]) .limit(1000), NegentropyOptions::default(), @@ -355,15 +367,6 @@ pub async fn login( println!("Subscribed: {}", e.success.len()) } - // Get user's contact list - let contact_list = { - let contacts = client.get_contact_list(None).await.unwrap(); - // Update app's state - state.contact_list.lock().await.clone_from(&contacts); - // Return - contacts - }; - // Get events from contact list if !contact_list.is_empty() { let authors: Vec = contact_list.iter().map(|f| f.public_key).collect(); diff --git a/src-tauri/src/commands/event.rs b/src-tauri/src/commands/event.rs index 444a3d1c..555f4897 100644 --- a/src-tauri/src/commands/event.rs +++ b/src-tauri/src/commands/event.rs @@ -198,7 +198,7 @@ pub async fn subscribe_to(id: String, state: State<'_, Nostr>) -> Result<(), Str #[tauri::command] #[specta::specta] -pub async fn get_events_by( +pub async fn get_all_events_by_author( public_key: String, limit: i32, state: State<'_, Nostr>, @@ -237,14 +237,111 @@ pub async fn get_events_by( #[tauri::command] #[specta::specta] -pub async fn get_local_events( - until: Option<&str>, +pub async fn get_all_events_by_authors( + public_keys: Vec, + until: Option, state: State<'_, Nostr>, ) -> Result, String> { let client = &state.client; let as_of = match until { - Some(until) => Timestamp::from_str(until).map_err(|err| err.to_string())?, + Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?, + None => Timestamp::now(), + }; + + let authors: Vec = public_keys + .iter() + .map(|pk| PublicKey::from_str(pk).map_err(|err| err.to_string())) + .collect::, _>>()?; + + let filter = Filter::new() + .kinds(vec![Kind::TextNote, Kind::Repost]) + .limit(FETCH_LIMIT) + .until(as_of) + .authors(authors); + + match client + .get_events_of(vec![filter], EventSource::Database) + .await + { + Ok(events) => { + let fils = filter_converstation(events); + let futures = fils.iter().map(|ev| async move { + let raw = ev.as_json(); + let parsed = if ev.kind == Kind::TextNote { + Some(parse_event(&ev.content).await) + } else { + None + }; + + RichEvent { raw, parsed } + }); + + let rich_events = join_all(futures).await; + + Ok(rich_events) + } + Err(err) => Err(err.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_all_events_by_hashtags( + hashtags: Vec, + until: Option, + state: State<'_, Nostr>, +) -> Result, String> { + let client = &state.client; + + let as_of = match until { + Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?, + None => Timestamp::now(), + }; + + let filter = Filter::new() + .kinds(vec![Kind::TextNote, Kind::Repost]) + .limit(FETCH_LIMIT) + .until(as_of) + .hashtags(hashtags); + + match client + .get_events_of( + vec![filter], + EventSource::both(Some(Duration::from_secs(5))), + ) + .await + { + Ok(events) => { + let fils = filter_converstation(events); + let futures = fils.iter().map(|ev| async move { + let raw = ev.as_json(); + let parsed = if ev.kind == Kind::TextNote { + Some(parse_event(&ev.content).await) + } else { + None + }; + + RichEvent { raw, parsed } + }); + let rich_events = join_all(futures).await; + + Ok(rich_events) + } + Err(err) => Err(err.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_local_events( + until: Option, + state: State<'_, Nostr>, +) -> Result, String> { + let client = &state.client; + + let as_of = match until { + Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?, None => Timestamp::now(), }; @@ -274,75 +371,22 @@ pub async fn get_local_events( } } -#[tauri::command] -#[specta::specta] -pub async fn get_group_events( - public_keys: Vec<&str>, - until: Option<&str>, - state: State<'_, Nostr>, -) -> Result, String> { - let client = &state.client; - - let as_of = match until { - Some(until) => Timestamp::from_str(until).map_err(|err| err.to_string())?, - None => Timestamp::now(), - }; - - let authors: Vec = public_keys - .iter() - .map(|p| PublicKey::from_str(p).map_err(|err| err.to_string())) - .collect::, _>>()?; - - let filter = Filter::new() - .kinds(vec![Kind::TextNote, Kind::Repost]) - .limit(20) - .until(as_of) - .authors(authors); - - match client - .get_events_of( - vec![filter], - EventSource::both(Some(Duration::from_secs(5))), - ) - .await - { - Ok(events) => { - let fils = filter_converstation(events); - let futures = fils.iter().map(|ev| async move { - let raw = ev.as_json(); - let parsed = if ev.kind == Kind::TextNote { - Some(parse_event(&ev.content).await) - } else { - None - }; - - RichEvent { raw, parsed } - }); - - let rich_events = join_all(futures).await; - - Ok(rich_events) - } - Err(err) => Err(err.to_string()), - } -} - #[tauri::command] #[specta::specta] pub async fn get_global_events( - until: Option<&str>, + until: Option, state: State<'_, Nostr>, ) -> Result, String> { let client = &state.client; let as_of = match until { - Some(until) => Timestamp::from_str(until).map_err(|err| err.to_string())?, + Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?, None => Timestamp::now(), }; let filter = Filter::new() .kinds(vec![Kind::TextNote, Kind::Repost]) - .limit(20) + .limit(FETCH_LIMIT) .until(as_of); match client @@ -372,51 +416,6 @@ pub async fn get_global_events( } } -#[tauri::command] -#[specta::specta] -pub async fn get_hashtag_events( - hashtags: Vec<&str>, - until: Option<&str>, - state: State<'_, Nostr>, -) -> Result, String> { - let client = &state.client; - let as_of = match until { - Some(until) => Timestamp::from_str(until).map_err(|err| err.to_string())?, - None => Timestamp::now(), - }; - let filter = Filter::new() - .kinds(vec![Kind::TextNote, Kind::Repost]) - .limit(20) - .until(as_of) - .hashtags(hashtags); - - match client - .get_events_of( - vec![filter], - EventSource::both(Some(Duration::from_secs(5))), - ) - .await - { - Ok(events) => { - let fils = filter_converstation(events); - let futures = fils.iter().map(|ev| async move { - let raw = ev.as_json(); - let parsed = if ev.kind == Kind::TextNote { - Some(parse_event(&ev.content).await) - } else { - None - }; - - RichEvent { raw, parsed } - }); - let rich_events = join_all(futures).await; - - Ok(rich_events) - } - Err(err) => Err(err.to_string()), - } -} - #[tauri::command] #[specta::specta] pub async fn publish( @@ -534,7 +533,7 @@ pub async fn reply( #[tauri::command] #[specta::specta] -pub async fn repost(raw: &str, state: State<'_, Nostr>) -> Result { +pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result { let client = &state.client; let event = Event::from_json(raw).map_err(|err| err.to_string())?; @@ -587,7 +586,7 @@ pub async fn event_to_bech32(id: String, state: State<'_, Nostr>) -> Result) -> Result { +pub async fn user_to_bech32(user: String, state: State<'_, Nostr>) -> Result { let client = &state.client; let public_key = PublicKey::parse(user).map_err(|err| err.to_string())?; @@ -672,3 +671,21 @@ pub async fn search( Err(e) => Err(e.to_string()), } } + +#[tauri::command] +#[specta::specta] +pub async fn is_deleted_event(id: String, state: State<'_, Nostr>) -> Result { + let client = &state.client; + let signer = client.signer().await.map_err(|err| err.to_string())?; + let public_key = signer.public_key().await.map_err(|err| err.to_string())?; + let event_id = EventId::from_str(&id).map_err(|err| err.to_string())?; + let filter = Filter::new() + .author(public_key) + .event(event_id) + .kind(Kind::EventDeletion); + + match client.database().query(vec![filter]).await { + Ok(events) => Ok(!events.is_empty()), + Err(e) => Err(e.to_string()), + } +} diff --git a/src-tauri/src/commands/metadata.rs b/src-tauri/src/commands/metadata.rs index 1152c826..394859df 100644 --- a/src-tauri/src/commands/metadata.rs +++ b/src-tauri/src/commands/metadata.rs @@ -3,7 +3,7 @@ use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use specta::Type; use std::{str::FromStr, time::Duration}; -use tauri::State; +use tauri::{Emitter, Manager, State}; use tauri_specta::Event; use crate::{common::get_latest_event, NewSettings, Nostr, Settings}; @@ -105,18 +105,14 @@ pub async fn set_contact_list( #[tauri::command] #[specta::specta] pub async fn get_contact_list(state: State<'_, Nostr>) -> Result, String> { - let client = &state.client; + let contact_list = state.contact_list.lock().await.clone(); + println!("Total contacts: {}", contact_list.len()); + let vec: Vec = contact_list + .into_iter() + .map(|f| f.public_key.to_hex()) + .collect(); - match client.get_contact_list(Some(Duration::from_secs(5))).await { - Ok(contact_list) => { - let list = contact_list - .into_iter() - .map(|f| f.public_key.to_hex()) - .collect(); - Ok(list) - } - Err(err) => Err(err.to_string()), - } + Ok(vec) } #[tauri::command] @@ -200,6 +196,194 @@ pub async fn toggle_contact( } } +#[tauri::command] +#[specta::specta] +pub async fn set_group( + title: String, + description: Option, + image: Option, + users: Vec, + state: State<'_, Nostr>, + handle: tauri::AppHandle, +) -> Result { + let client = &state.client; + let public_keys: Vec = users + .iter() + .map(|u| PublicKey::from_str(u).unwrap()) + .collect(); + let label = title.to_lowercase().replace(" ", "-"); + let mut tags: Vec = vec![Tag::title(title)]; + + if let Some(desc) = description { + tags.push(Tag::description(desc)) + }; + + if let Some(img) = image { + let url = UncheckedUrl::new(img); + tags.push(Tag::image(url, None)); + } + + let builder = EventBuilder::follow_set(label, public_keys.clone()).add_tags(tags); + + match client.send_event_builder(builder).await { + Ok(report) => { + tauri::async_runtime::spawn(async move { + let state = handle.state::(); + let client = &state.client; + + let filter = Filter::new() + .kinds(vec![Kind::TextNote, Kind::Repost]) + .authors(public_keys) + .limit(500); + + if let Ok(report) = client.reconcile(filter, NegentropyOptions::default()).await { + println!("Received: {}", report.received.len()); + handle.emit("synchronized", ()).unwrap(); + }; + }); + + Ok(report.id().to_hex()) + } + Err(e) => Err(e.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_group(id: String, state: State<'_, Nostr>) -> Result { + let client = &state.client; + let event_id = EventId::from_str(&id).map_err(|e| e.to_string())?; + let filter = Filter::new().kind(Kind::FollowSet).id(event_id); + + match client + .get_events_of( + vec![filter], + EventSource::both(Some(Duration::from_secs(5))), + ) + .await + { + Ok(events) => match get_latest_event(&events) { + Some(ev) => Ok(ev.as_json()), + None => Err("Not found.".to_string()), + }, + Err(e) => Err(e.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_all_groups(state: State<'_, Nostr>) -> 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); + + match client + .get_events_of(vec![filter], EventSource::Database) + .await + { + Ok(events) => { + let data: Vec = events.iter().map(|ev| ev.as_json()).collect(); + Ok(data) + } + Err(e) => Err(e.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn set_interest( + title: String, + description: Option, + image: Option, + hashtags: Vec, + state: State<'_, Nostr>, + handle: tauri::AppHandle, +) -> Result { + let client = &state.client; + let label = title.to_lowercase().replace(" ", "-"); + let mut tags: Vec = vec![Tag::title(title)]; + + if let Some(desc) = description { + tags.push(Tag::description(desc)) + }; + + if let Some(img) = image { + let url = UncheckedUrl::new(img); + tags.push(Tag::image(url, None)); + } + + let builder = EventBuilder::interest_set(label, hashtags.clone()).add_tags(tags); + + match client.send_event_builder(builder).await { + Ok(report) => { + tauri::async_runtime::spawn(async move { + let state = handle.state::(); + let client = &state.client; + + let filter = Filter::new() + .kinds(vec![Kind::TextNote, Kind::Repost]) + .hashtags(hashtags) + .limit(500); + + if let Ok(report) = client.reconcile(filter, NegentropyOptions::default()).await { + println!("Received: {}", report.received.len()); + handle.emit("synchronized", ()).unwrap(); + }; + }); + + Ok(report.id().to_hex()) + } + Err(e) => Err(e.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_interest(id: String, state: State<'_, Nostr>) -> Result { + let client = &state.client; + let event_id = EventId::from_str(&id).map_err(|e| e.to_string())?; + let filter = Filter::new() + .kinds(vec![Kind::Interests, Kind::InterestSet]) + .id(event_id); + + match client + .get_events_of( + vec![filter], + EventSource::both(Some(Duration::from_secs(5))), + ) + .await + { + Ok(events) => match get_latest_event(&events) { + Some(ev) => Ok(ev.as_json()), + None => Err("Not found.".to_string()), + }, + Err(e) => Err(e.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_all_interests(state: State<'_, Nostr>) -> 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() + .kinds(vec![Kind::InterestSet, Kind::Interests]) + .author(public_key); + + match client + .get_events_of(vec![filter], EventSource::Database) + .await + { + Ok(events) => { + let data: Vec = events.iter().map(|ev| ev.as_json()).collect(); + Ok(data) + } + Err(e) => Err(e.to_string()), + } +} + #[tauri::command] #[specta::specta] pub async fn get_mention_list(state: State<'_, Nostr>) -> Result, String> { @@ -230,61 +414,6 @@ pub async fn get_mention_list(state: State<'_, Nostr>) -> Result, S Ok(data) } -#[tauri::command] -#[specta::specta] -pub async fn set_lume_store( - key: String, - content: String, - state: State<'_, Nostr>, -) -> Result { - 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 encrypted = signer - .nip44_encrypt(&public_key, content) - .await - .map_err(|e| e.to_string())?; - let tag = Tag::identifier(key); - let builder = EventBuilder::new(Kind::ApplicationSpecificData, encrypted, vec![tag]); - - match client.send_event_builder(builder).await { - Ok(event_id) => Ok(event_id.to_string()), - Err(err) => Err(err.to_string()), - } -} - -#[tauri::command] -#[specta::specta] -pub async fn get_lume_store(key: String, state: State<'_, Nostr>) -> Result { - 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() - .author(public_key) - .kind(Kind::ApplicationSpecificData) - .identifier(key) - .limit(10); - - match client - .get_events_of(vec![filter], EventSource::Database) - .await - { - Ok(events) => { - if let Some(event) = get_latest_event(&events) { - match signer.nip44_decrypt(&public_key, &event.content).await { - Ok(decrypted) => Ok(decrypted), - Err(_) => Err(event.content.to_string()), - } - } else { - Err("Not found".into()) - } - } - Err(err) => Err(err.to_string()), - } -} - #[tauri::command] #[specta::specta] pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result { diff --git a/src-tauri/src/commands/window.rs b/src-tauri/src/commands/window.rs index 4994aa92..ca075eab 100644 --- a/src-tauri/src/commands/window.rs +++ b/src-tauri/src/commands/window.rs @@ -113,15 +113,15 @@ pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result Result<(), String> { - if let Some(window) = app_handle.get_window(&window.label) { - if window.is_visible().unwrap_or_default() { - let _ = window.set_focus(); + if let Some(current_window) = app_handle.get_window(&window.label) { + if current_window.is_visible().unwrap_or_default() { + let _ = current_window.set_focus(); } else { - let _ = window.show(); - let _ = window.set_focus(); + let _ = current_window.show(); + let _ = current_window.set_focus(); }; } else { - let window = WebviewWindowBuilder::new( + let new_window = WebviewWindowBuilder::new( &app_handle, &window.label, WebviewUrl::App(PathBuf::from(window.url)), @@ -129,7 +129,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S .title(&window.title) .min_inner_size(window.width, window.height) .inner_size(window.width, window.height) - .hidden_title(true) + .hidden_title(window.hidden_title) .title_bar_style(TitleBarStyle::Overlay) .minimizable(window.minimizable) .maximizable(window.maximizable) @@ -144,7 +144,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S .unwrap(); // Restore native border - window.add_border(None); + new_window.add_border(None); } Ok(()) @@ -217,7 +217,7 @@ pub fn reopen_lume(app: tauri::AppHandle) { // Set a custom inset to the traffic lights #[cfg(target_os = "macos")] - window.set_traffic_lights_inset(7.0, 13.0).unwrap(); + window.set_traffic_lights_inset(7.0, 10.0).unwrap(); #[cfg(target_os = "macos")] let win = window.clone(); @@ -225,7 +225,7 @@ pub fn reopen_lume(app: tauri::AppHandle) { #[cfg(target_os = "macos")] window.on_window_event(move |event| { if let tauri::WindowEvent::ThemeChanged(_) = event { - win.set_traffic_lights_inset(7.0, 13.0).unwrap(); + win.set_traffic_lights_inset(7.0, 10.0).unwrap(); } }); } diff --git a/src-tauri/src/common.rs b/src-tauri/src/common.rs index 267dd1cc..1ffb7d6e 100644 --- a/src-tauri/src/common.rs +++ b/src-tauri/src/common.rs @@ -52,14 +52,23 @@ pub fn filter_converstation(events: Vec) -> Vec { events .into_iter() .filter_map(|ev| { - let tags = ev.get_tags_content(TagKind::SingleLetter(SingleLetterTag::lowercase( - Alphabet::E, - ))); + if ev.kind == Kind::TextNote { + let tags: Vec<&str> = ev + .tags + .iter() + .filter(|t| { + t.kind() == TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::E)) + }) + .filter_map(|t| t.content()) + .collect(); - if tags.is_empty() { - Some(ev) + if tags.is_empty() { + Some(ev) + } else { + None + } } else { - None + Some(ev) } }) .collect::>() diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 249953c7..fd528e5b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -116,8 +116,12 @@ fn main() { check_contact, toggle_contact, get_mention_list, - get_lume_store, - set_lume_store, + set_group, + get_group, + get_all_groups, + set_interest, + get_interest, + get_all_interests, set_wallet, load_wallet, remove_wallet, @@ -134,11 +138,12 @@ fn main() { get_event_from, get_replies, subscribe_to, - get_events_by, + get_all_events_by_author, + get_all_events_by_authors, + get_all_events_by_hashtags, get_local_events, - get_group_events, get_global_events, - get_hashtag_events, + is_deleted_event, search, publish, reply, diff --git a/src/app.tsx b/src/app.tsx index 0013a428..00b61914 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -13,8 +13,8 @@ import { createStore } from "@tauri-apps/plugin-store"; import { routeTree } from "./routes.gen"; // auto generated file const platform = type(); -// @ts-ignore, https://github.com/tauri-apps/plugins-workspace/pull/1860 -const store = await createStore(".cache", { autoSave: 100 }); +// @ts-expect-error, required: https://github.com/tauri-apps/plugins-workspace/pull/1860 +const store = await createStore(".cache", { autoSave: 250 }); const queryClient = new QueryClient({ defaultOptions: { queries: { diff --git a/src/commands.gen.ts b/src/commands.gen.ts index 605affc6..97edf48d 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -163,17 +163,49 @@ async getMentionList() : Promise> { else return { status: "error", error: e as any }; } }, -async getLumeStore(key: string) : Promise> { +async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_lume_store", { key }) }; + return { status: "ok", data: await TAURI_INVOKE("set_group", { title, description, image, users }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, -async setLumeStore(key: string, content: string) : Promise> { +async getGroup(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("set_lume_store", { key, content }) }; + return { status: "ok", data: await TAURI_INVOKE("get_group", { id }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getAllGroups() : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_groups") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setInterest(title: string, description: string | null, image: string | null, hashtags: string[]) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("set_interest", { title, description, image, hashtags }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getInterest(id: string) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_interest", { id }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getAllInterests() : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_interests") }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -307,9 +339,25 @@ async subscribeTo(id: string) : Promise> { else return { status: "error", error: e as any }; } }, -async getEventsBy(publicKey: string, limit: number) : Promise> { +async getAllEventsByAuthor(publicKey: string, limit: number) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, limit }) }; + return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_author", { publicKey, limit }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getAllEventsByAuthors(publicKeys: string[], until: string | null) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_authors", { publicKeys, until }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getAllEventsByHashtags(hashtags: string[], until: string | null) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_hashtags", { hashtags, until }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -323,14 +371,6 @@ async getLocalEvents(until: string | null) : Promise else return { status: "error", error: e as any }; } }, -async getGroupEvents(publicKeys: string[], until: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_group_events", { publicKeys, until }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, async getGlobalEvents(until: string | null) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) }; @@ -339,9 +379,9 @@ async getGlobalEvents(until: string | null) : Promise> { +async isDeletedEvent(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }) }; + return { status: "ok", data: await TAURI_INVOKE("is_deleted_event", { id }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; diff --git a/src/commons.ts b/src/commons.ts index 61e2df16..163c5cf0 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -18,7 +18,7 @@ import { decode } from "light-bolt11-decoder"; import { twMerge } from "tailwind-merge"; import type { RichEvent, Settings } from "./commands.gen"; import { LumeEvent } from "./system"; -import type { NostrEvent } from "./types"; +import type { LumeColumn, NostrEvent } from "./types"; dayjs.extend(relativeTime); dayjs.extend(updateLocale); @@ -154,6 +154,7 @@ export function decodeZapInvoice(tags?: string[][]) { (s: { name: string }) => s.name === "amount", ); + // @ts-ignore, its fine. const amount = Number.parseInt(amountSection.value); const displayValue = getBitcoinDisplayValues(amount); @@ -289,3 +290,5 @@ export const appSettings = new Store({ display_media: true, transparent: true, }); + +export const appColumns = new Store([]); diff --git a/src/components/column.tsx b/src/components/column.tsx index 01cf368f..a51598f7 100644 --- a/src/components/column.tsx +++ b/src/components/column.tsx @@ -1,7 +1,9 @@ import { commands } from "@/commands.gen"; +import { appColumns } from "@/commons"; 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 { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; @@ -88,7 +90,7 @@ export const Column = memo(function Column({ column }: { column: LumeColumn }) { return (
-
+
{!isCreated ? (
@@ -101,10 +103,14 @@ export const Column = memo(function Column({ column }: { column: LumeColumn }) { ); }); -function Header({ label, name }: { label: string; name: string }) { +function Header({ label }: { label: string }) { const [title, setTitle] = useState(""); const [isChanged, setIsChanged] = useState(false); + const column = useStore(appColumns, (state) => + state.find((col) => col.label === label), + ); + const saveNewTitle = async () => { const mainWindow = getCurrentWindow(); await mainWindow.emit("columns", { type: "set_title", label, title }); @@ -186,7 +192,7 @@ function Header({ label, name }: { label: string; name: string }) { onBlur={(e) => setTitle(e.currentTarget.textContent)} className="text-[12px] font-semibold focus:outline-none" > - {name} + {column.name}
{isChanged ? ( + ); } diff --git a/src/routes.gen.ts b/src/routes.gen.ts index 93c3a81f..4bb1622d 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -13,6 +13,8 @@ import { createFileRoute } from '@tanstack/react-router' // Import Routes 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' @@ -22,17 +24,17 @@ 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 ColumnsLayoutGroupImport } from './routes/columns/_layout/group' +import { Route as ColumnsLayoutNewsfeedImport } from './routes/columns/_layout/newsfeed' import { Route as ColumnsLayoutGlobalImport } from './routes/columns/_layout/global' -import { Route as ColumnsLayoutGalleryImport } from './routes/columns/_layout/gallery' import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed' -import { Route as ColumnsLayoutCreateGroupImport } from './routes/columns/_layout/create-group' 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 AccountAppHomeImport } from './routes/$account/_app/home' +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' import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/_layout/create-newsfeed.f2f' @@ -58,8 +60,8 @@ const ColumnsLayoutOnboardingLazyImport = createFileRoute( const ColumnsLayoutNotificationLazyImport = createFileRoute( '/columns/_layout/notification', )() -const ColumnsLayoutNewsfeedLazyImport = createFileRoute( - '/columns/_layout/newsfeed', +const ColumnsLayoutGalleryLazyImport = createFileRoute( + '/columns/_layout/gallery', )() const ColumnsLayoutUsersIdLazyImport = createFileRoute( '/columns/_layout/users/$id', @@ -67,9 +69,6 @@ const ColumnsLayoutUsersIdLazyImport = createFileRoute( const ColumnsLayoutRepliesIdLazyImport = createFileRoute( '/columns/_layout/replies/$id', )() -const ColumnsLayoutHashtagsContentLazyImport = createFileRoute( - '/columns/_layout/hashtags/$content', -)() const ColumnsLayoutEventsIdLazyImport = createFileRoute( '/columns/_layout/events/$id', )() @@ -96,6 +95,16 @@ const NewLazyRoute = NewLazyImport.update({ getParentRoute: () => rootRoute, } as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route)) +const SetInterestRoute = SetInterestImport.update({ + path: '/set-interest', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/set-interest.lazy').then((d) => d.Route)) + +const SetGroupRoute = SetGroupImport.update({ + path: '/set-group', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/set-group.lazy').then((d) => d.Route)) + const LoadingRoute = LoadingImport.update({ path: '/loading', getParentRoute: () => rootRoute, @@ -190,11 +199,11 @@ const ColumnsLayoutNotificationLazyRoute = import('./routes/columns/_layout/notification.lazy').then((d) => d.Route), ) -const ColumnsLayoutNewsfeedLazyRoute = ColumnsLayoutNewsfeedLazyImport.update({ - path: '/newsfeed', +const ColumnsLayoutGalleryLazyRoute = ColumnsLayoutGalleryLazyImport.update({ + path: '/gallery', getParentRoute: () => ColumnsLayoutRoute, } as any).lazy(() => - import('./routes/columns/_layout/newsfeed.lazy').then((d) => d.Route), + import('./routes/columns/_layout/gallery.lazy').then((d) => d.Route), ) const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({ @@ -204,11 +213,11 @@ const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({ import('./routes/columns/_layout/stories.lazy').then((d) => d.Route), ) -const ColumnsLayoutGroupRoute = ColumnsLayoutGroupImport.update({ - path: '/group', +const ColumnsLayoutNewsfeedRoute = ColumnsLayoutNewsfeedImport.update({ + path: '/newsfeed', getParentRoute: () => ColumnsLayoutRoute, } as any).lazy(() => - import('./routes/columns/_layout/group.lazy').then((d) => d.Route), + import('./routes/columns/_layout/newsfeed.lazy').then((d) => d.Route), ) const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({ @@ -216,26 +225,12 @@ const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({ getParentRoute: () => ColumnsLayoutRoute, } as any) -const ColumnsLayoutGalleryRoute = ColumnsLayoutGalleryImport.update({ - path: '/gallery', - getParentRoute: () => ColumnsLayoutRoute, -} as any).lazy(() => - import('./routes/columns/_layout/gallery.lazy').then((d) => d.Route), -) - const ColumnsLayoutCreateNewsfeedRoute = ColumnsLayoutCreateNewsfeedImport.update({ path: '/create-newsfeed', getParentRoute: () => ColumnsLayoutRoute, } as any) -const ColumnsLayoutCreateGroupRoute = ColumnsLayoutCreateGroupImport.update({ - path: '/create-group', - getParentRoute: () => ColumnsLayoutRoute, -} as any).lazy(() => - import('./routes/columns/_layout/create-group.lazy').then((d) => d.Route), -) - const AccountSettingsWalletRoute = AccountSettingsWalletImport.update({ path: '/wallet', getParentRoute: () => AccountSettingsLazyRoute, @@ -297,16 +292,6 @@ const ColumnsLayoutRepliesIdLazyRoute = ColumnsLayoutRepliesIdLazyImport.update( import('./routes/columns/_layout/replies.$id.lazy').then((d) => d.Route), ) -const ColumnsLayoutHashtagsContentLazyRoute = - ColumnsLayoutHashtagsContentLazyImport.update({ - path: '/hashtags/$content', - getParentRoute: () => ColumnsLayoutRoute, - } as any).lazy(() => - import('./routes/columns/_layout/hashtags.$content.lazy').then( - (d) => d.Route, - ), - ) - const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({ path: '/events/$id', getParentRoute: () => ColumnsLayoutRoute, @@ -314,6 +299,20 @@ const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({ import('./routes/columns/_layout/events.$id.lazy').then((d) => d.Route), ) +const ColumnsLayoutInterestsIdRoute = ColumnsLayoutInterestsIdImport.update({ + path: '/interests/$id', + getParentRoute: () => ColumnsLayoutRoute, +} as any).lazy(() => + import('./routes/columns/_layout/interests.$id.lazy').then((d) => d.Route), +) + +const ColumnsLayoutGroupsIdRoute = ColumnsLayoutGroupsIdImport.update({ + path: '/groups/$id', + getParentRoute: () => ColumnsLayoutRoute, +} as any).lazy(() => + import('./routes/columns/_layout/groups.$id.lazy').then((d) => d.Route), +) + const ColumnsLayoutCreateNewsfeedUsersRoute = ColumnsLayoutCreateNewsfeedUsersImport.update({ path: '/users', @@ -351,6 +350,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoadingImport parentRoute: typeof rootRoute } + '/set-group': { + id: '/set-group' + path: '/set-group' + fullPath: '/set-group' + preLoaderRoute: typeof SetGroupImport + parentRoute: typeof rootRoute + } + '/set-interest': { + id: '/set-interest' + path: '/set-interest' + fullPath: '/set-interest' + preLoaderRoute: typeof SetInterestImport + parentRoute: typeof rootRoute + } '/new': { id: '/new' path: '/new' @@ -484,13 +497,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AccountSettingsWalletImport parentRoute: typeof AccountSettingsLazyImport } - '/columns/_layout/create-group': { - id: '/columns/_layout/create-group' - path: '/create-group' - fullPath: '/columns/create-group' - preLoaderRoute: typeof ColumnsLayoutCreateGroupImport - parentRoute: typeof ColumnsLayoutImport - } '/columns/_layout/create-newsfeed': { id: '/columns/_layout/create-newsfeed' path: '/create-newsfeed' @@ -498,13 +504,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedImport parentRoute: typeof ColumnsLayoutImport } - '/columns/_layout/gallery': { - id: '/columns/_layout/gallery' - path: '/gallery' - fullPath: '/columns/gallery' - preLoaderRoute: typeof ColumnsLayoutGalleryImport - parentRoute: typeof ColumnsLayoutImport - } '/columns/_layout/global': { id: '/columns/_layout/global' path: '/global' @@ -512,11 +511,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutGlobalImport parentRoute: typeof ColumnsLayoutImport } - '/columns/_layout/group': { - id: '/columns/_layout/group' - path: '/group' - fullPath: '/columns/group' - preLoaderRoute: typeof ColumnsLayoutGroupImport + '/columns/_layout/newsfeed': { + id: '/columns/_layout/newsfeed' + path: '/newsfeed' + fullPath: '/columns/newsfeed' + preLoaderRoute: typeof ColumnsLayoutNewsfeedImport parentRoute: typeof ColumnsLayoutImport } '/columns/_layout/stories': { @@ -526,11 +525,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutStoriesImport parentRoute: typeof ColumnsLayoutImport } - '/columns/_layout/newsfeed': { - id: '/columns/_layout/newsfeed' - path: '/newsfeed' - fullPath: '/columns/newsfeed' - preLoaderRoute: typeof ColumnsLayoutNewsfeedLazyImport + '/columns/_layout/gallery': { + id: '/columns/_layout/gallery' + path: '/gallery' + fullPath: '/columns/gallery' + preLoaderRoute: typeof ColumnsLayoutGalleryLazyImport parentRoute: typeof ColumnsLayoutImport } '/columns/_layout/notification': { @@ -575,6 +574,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedUsersImport parentRoute: typeof ColumnsLayoutCreateNewsfeedImport } + '/columns/_layout/groups/$id': { + id: '/columns/_layout/groups/$id' + path: '/groups/$id' + fullPath: '/columns/groups/$id' + preLoaderRoute: typeof ColumnsLayoutGroupsIdImport + parentRoute: typeof ColumnsLayoutImport + } + '/columns/_layout/interests/$id': { + id: '/columns/_layout/interests/$id' + path: '/interests/$id' + fullPath: '/columns/interests/$id' + preLoaderRoute: typeof ColumnsLayoutInterestsIdImport + parentRoute: typeof ColumnsLayoutImport + } '/columns/_layout/events/$id': { id: '/columns/_layout/events/$id' path: '/events/$id' @@ -582,13 +595,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport parentRoute: typeof ColumnsLayoutImport } - '/columns/_layout/hashtags/$content': { - id: '/columns/_layout/hashtags/$content' - path: '/hashtags/$content' - fullPath: '/columns/hashtags/$content' - preLoaderRoute: typeof ColumnsLayoutHashtagsContentLazyImport - parentRoute: typeof ColumnsLayoutImport - } '/columns/_layout/replies/$id': { id: '/columns/_layout/replies/$id' path: '/replies/$id' @@ -672,38 +678,36 @@ const ColumnsLayoutCreateNewsfeedRouteWithChildren = ) interface ColumnsLayoutRouteChildren { - ColumnsLayoutCreateGroupRoute: typeof ColumnsLayoutCreateGroupRoute ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren - ColumnsLayoutGalleryRoute: typeof ColumnsLayoutGalleryRoute ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute - ColumnsLayoutGroupRoute: typeof ColumnsLayoutGroupRoute + ColumnsLayoutNewsfeedRoute: typeof ColumnsLayoutNewsfeedRoute ColumnsLayoutStoriesRoute: typeof ColumnsLayoutStoriesRoute - ColumnsLayoutNewsfeedLazyRoute: typeof ColumnsLayoutNewsfeedLazyRoute + ColumnsLayoutGalleryLazyRoute: typeof ColumnsLayoutGalleryLazyRoute ColumnsLayoutNotificationLazyRoute: typeof ColumnsLayoutNotificationLazyRoute ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute + ColumnsLayoutGroupsIdRoute: typeof ColumnsLayoutGroupsIdRoute + ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute - ColumnsLayoutHashtagsContentLazyRoute: typeof ColumnsLayoutHashtagsContentLazyRoute ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute } const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = { - ColumnsLayoutCreateGroupRoute: ColumnsLayoutCreateGroupRoute, ColumnsLayoutCreateNewsfeedRoute: ColumnsLayoutCreateNewsfeedRouteWithChildren, - ColumnsLayoutGalleryRoute: ColumnsLayoutGalleryRoute, ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute, - ColumnsLayoutGroupRoute: ColumnsLayoutGroupRoute, + ColumnsLayoutNewsfeedRoute: ColumnsLayoutNewsfeedRoute, ColumnsLayoutStoriesRoute: ColumnsLayoutStoriesRoute, - ColumnsLayoutNewsfeedLazyRoute: ColumnsLayoutNewsfeedLazyRoute, + ColumnsLayoutGalleryLazyRoute: ColumnsLayoutGalleryLazyRoute, ColumnsLayoutNotificationLazyRoute: ColumnsLayoutNotificationLazyRoute, ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute, ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute, ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute, + ColumnsLayoutGroupsIdRoute: ColumnsLayoutGroupsIdRoute, + ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute, ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute, - ColumnsLayoutHashtagsContentLazyRoute: ColumnsLayoutHashtagsContentLazyRoute, ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute, ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute, } @@ -727,6 +731,8 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/bootstrap-relays': typeof BootstrapRelaysRoute '/loading': typeof LoadingRoute + '/set-group': typeof SetGroupRoute + '/set-interest': typeof SetInterestRoute '/new': typeof NewLazyRoute '/reset': typeof ResetLazyRoute '/$account': typeof AccountSettingsLazyRouteWithChildren @@ -743,21 +749,20 @@ export interface FileRoutesByFullPath { '/$account/profile': typeof AccountSettingsProfileRoute '/$account/relay': typeof AccountSettingsRelayRoute '/$account/wallet': typeof AccountSettingsWalletRoute - '/columns/create-group': typeof ColumnsLayoutCreateGroupRoute '/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren - '/columns/gallery': typeof ColumnsLayoutGalleryRoute '/columns/global': typeof ColumnsLayoutGlobalRoute - '/columns/group': typeof ColumnsLayoutGroupRoute + '/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute '/columns/stories': typeof ColumnsLayoutStoriesRoute - '/columns/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute + '/columns/gallery': typeof ColumnsLayoutGalleryLazyRoute '/columns/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/search': typeof ColumnsLayoutSearchLazyRoute '/columns/trending': typeof ColumnsLayoutTrendingLazyRoute '/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute '/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute + '/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute + '/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute '/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute - '/columns/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute '/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -766,6 +771,8 @@ export interface FileRoutesByTo { '/': typeof IndexRoute '/bootstrap-relays': typeof BootstrapRelaysRoute '/loading': typeof LoadingRoute + '/set-group': typeof SetGroupRoute + '/set-interest': typeof SetInterestRoute '/new': typeof NewLazyRoute '/reset': typeof ResetLazyRoute '/$account': typeof AccountSettingsLazyRouteWithChildren @@ -782,21 +789,20 @@ export interface FileRoutesByTo { '/$account/profile': typeof AccountSettingsProfileRoute '/$account/relay': typeof AccountSettingsRelayRoute '/$account/wallet': typeof AccountSettingsWalletRoute - '/columns/create-group': typeof ColumnsLayoutCreateGroupRoute '/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren - '/columns/gallery': typeof ColumnsLayoutGalleryRoute '/columns/global': typeof ColumnsLayoutGlobalRoute - '/columns/group': typeof ColumnsLayoutGroupRoute + '/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute '/columns/stories': typeof ColumnsLayoutStoriesRoute - '/columns/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute + '/columns/gallery': typeof ColumnsLayoutGalleryLazyRoute '/columns/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/search': typeof ColumnsLayoutSearchLazyRoute '/columns/trending': typeof ColumnsLayoutTrendingLazyRoute '/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute '/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute + '/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute + '/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute '/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute - '/columns/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute '/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -806,6 +812,8 @@ export interface FileRoutesById { '/': typeof IndexRoute '/bootstrap-relays': typeof BootstrapRelaysRoute '/loading': typeof LoadingRoute + '/set-group': typeof SetGroupRoute + '/set-interest': typeof SetInterestRoute '/new': typeof NewLazyRoute '/reset': typeof ResetLazyRoute '/$account': typeof AccountRouteWithChildren @@ -825,21 +833,20 @@ export interface FileRoutesById { '/$account/_settings/profile': typeof AccountSettingsProfileRoute '/$account/_settings/relay': typeof AccountSettingsRelayRoute '/$account/_settings/wallet': typeof AccountSettingsWalletRoute - '/columns/_layout/create-group': typeof ColumnsLayoutCreateGroupRoute '/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren - '/columns/_layout/gallery': typeof ColumnsLayoutGalleryRoute '/columns/_layout/global': typeof ColumnsLayoutGlobalRoute - '/columns/_layout/group': typeof ColumnsLayoutGroupRoute + '/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedRoute '/columns/_layout/stories': typeof ColumnsLayoutStoriesRoute - '/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute + '/columns/_layout/gallery': typeof ColumnsLayoutGalleryLazyRoute '/columns/_layout/notification': typeof ColumnsLayoutNotificationLazyRoute '/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute '/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute '/columns/_layout/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute '/columns/_layout/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute + '/columns/_layout/groups/$id': typeof ColumnsLayoutGroupsIdRoute + '/columns/_layout/interests/$id': typeof ColumnsLayoutInterestsIdRoute '/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute - '/columns/_layout/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute '/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -850,6 +857,8 @@ export interface FileRouteTypes { | '/' | '/bootstrap-relays' | '/loading' + | '/set-group' + | '/set-interest' | '/new' | '/reset' | '/$account' @@ -866,21 +875,20 @@ export interface FileRouteTypes { | '/$account/profile' | '/$account/relay' | '/$account/wallet' - | '/columns/create-group' | '/columns/create-newsfeed' - | '/columns/gallery' | '/columns/global' - | '/columns/group' - | '/columns/stories' | '/columns/newsfeed' + | '/columns/stories' + | '/columns/gallery' | '/columns/notification' | '/columns/onboarding' | '/columns/search' | '/columns/trending' | '/columns/create-newsfeed/f2f' | '/columns/create-newsfeed/users' + | '/columns/groups/$id' + | '/columns/interests/$id' | '/columns/events/$id' - | '/columns/hashtags/$content' | '/columns/replies/$id' | '/columns/users/$id' fileRoutesByTo: FileRoutesByTo @@ -888,6 +896,8 @@ export interface FileRouteTypes { | '/' | '/bootstrap-relays' | '/loading' + | '/set-group' + | '/set-interest' | '/new' | '/reset' | '/$account' @@ -904,21 +914,20 @@ export interface FileRouteTypes { | '/$account/profile' | '/$account/relay' | '/$account/wallet' - | '/columns/create-group' | '/columns/create-newsfeed' - | '/columns/gallery' | '/columns/global' - | '/columns/group' - | '/columns/stories' | '/columns/newsfeed' + | '/columns/stories' + | '/columns/gallery' | '/columns/notification' | '/columns/onboarding' | '/columns/search' | '/columns/trending' | '/columns/create-newsfeed/f2f' | '/columns/create-newsfeed/users' + | '/columns/groups/$id' + | '/columns/interests/$id' | '/columns/events/$id' - | '/columns/hashtags/$content' | '/columns/replies/$id' | '/columns/users/$id' id: @@ -926,6 +935,8 @@ export interface FileRouteTypes { | '/' | '/bootstrap-relays' | '/loading' + | '/set-group' + | '/set-interest' | '/new' | '/reset' | '/$account' @@ -945,21 +956,20 @@ export interface FileRouteTypes { | '/$account/_settings/profile' | '/$account/_settings/relay' | '/$account/_settings/wallet' - | '/columns/_layout/create-group' | '/columns/_layout/create-newsfeed' - | '/columns/_layout/gallery' | '/columns/_layout/global' - | '/columns/_layout/group' - | '/columns/_layout/stories' | '/columns/_layout/newsfeed' + | '/columns/_layout/stories' + | '/columns/_layout/gallery' | '/columns/_layout/notification' | '/columns/_layout/onboarding' | '/columns/_layout/search' | '/columns/_layout/trending' | '/columns/_layout/create-newsfeed/f2f' | '/columns/_layout/create-newsfeed/users' + | '/columns/_layout/groups/$id' + | '/columns/_layout/interests/$id' | '/columns/_layout/events/$id' - | '/columns/_layout/hashtags/$content' | '/columns/_layout/replies/$id' | '/columns/_layout/users/$id' fileRoutesById: FileRoutesById @@ -969,6 +979,8 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute BootstrapRelaysRoute: typeof BootstrapRelaysRoute LoadingRoute: typeof LoadingRoute + SetGroupRoute: typeof SetGroupRoute + SetInterestRoute: typeof SetInterestRoute NewLazyRoute: typeof NewLazyRoute ResetLazyRoute: typeof ResetLazyRoute AccountRoute: typeof AccountRouteWithChildren @@ -984,6 +996,8 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, BootstrapRelaysRoute: BootstrapRelaysRoute, LoadingRoute: LoadingRoute, + SetGroupRoute: SetGroupRoute, + SetInterestRoute: SetInterestRoute, NewLazyRoute: NewLazyRoute, ResetLazyRoute: ResetLazyRoute, AccountRoute: AccountRouteWithChildren, @@ -1010,6 +1024,8 @@ export const routeTree = rootRoute "/", "/bootstrap-relays", "/loading", + "/set-group", + "/set-interest", "/new", "/reset", "/$account", @@ -1030,6 +1046,12 @@ export const routeTree = rootRoute "/loading": { "filePath": "loading.tsx" }, + "/set-group": { + "filePath": "set-group.tsx" + }, + "/set-interest": { + "filePath": "set-interest.tsx" + }, "/new": { "filePath": "new.lazy.tsx" }, @@ -1065,19 +1087,18 @@ export const routeTree = rootRoute "filePath": "columns/_layout.tsx", "parent": "/columns", "children": [ - "/columns/_layout/create-group", "/columns/_layout/create-newsfeed", - "/columns/_layout/gallery", "/columns/_layout/global", - "/columns/_layout/group", - "/columns/_layout/stories", "/columns/_layout/newsfeed", + "/columns/_layout/stories", + "/columns/_layout/gallery", "/columns/_layout/notification", "/columns/_layout/onboarding", "/columns/_layout/search", "/columns/_layout/trending", + "/columns/_layout/groups/$id", + "/columns/_layout/interests/$id", "/columns/_layout/events/$id", - "/columns/_layout/hashtags/$content", "/columns/_layout/replies/$id", "/columns/_layout/users/$id" ] @@ -1132,10 +1153,6 @@ export const routeTree = rootRoute "filePath": "$account/_settings/wallet.tsx", "parent": "/$account/_settings" }, - "/columns/_layout/create-group": { - "filePath": "columns/_layout/create-group.tsx", - "parent": "/columns/_layout" - }, "/columns/_layout/create-newsfeed": { "filePath": "columns/_layout/create-newsfeed.tsx", "parent": "/columns/_layout", @@ -1144,24 +1161,20 @@ export const routeTree = rootRoute "/columns/_layout/create-newsfeed/users" ] }, - "/columns/_layout/gallery": { - "filePath": "columns/_layout/gallery.tsx", - "parent": "/columns/_layout" - }, "/columns/_layout/global": { "filePath": "columns/_layout/global.tsx", "parent": "/columns/_layout" }, - "/columns/_layout/group": { - "filePath": "columns/_layout/group.tsx", + "/columns/_layout/newsfeed": { + "filePath": "columns/_layout/newsfeed.tsx", "parent": "/columns/_layout" }, "/columns/_layout/stories": { "filePath": "columns/_layout/stories.tsx", "parent": "/columns/_layout" }, - "/columns/_layout/newsfeed": { - "filePath": "columns/_layout/newsfeed.lazy.tsx", + "/columns/_layout/gallery": { + "filePath": "columns/_layout/gallery.lazy.tsx", "parent": "/columns/_layout" }, "/columns/_layout/notification": { @@ -1188,12 +1201,16 @@ export const routeTree = rootRoute "filePath": "columns/_layout/create-newsfeed.users.tsx", "parent": "/columns/_layout/create-newsfeed" }, - "/columns/_layout/events/$id": { - "filePath": "columns/_layout/events.$id.lazy.tsx", + "/columns/_layout/groups/$id": { + "filePath": "columns/_layout/groups.$id.tsx", "parent": "/columns/_layout" }, - "/columns/_layout/hashtags/$content": { - "filePath": "columns/_layout/hashtags.$content.lazy.tsx", + "/columns/_layout/interests/$id": { + "filePath": "columns/_layout/interests.$id.tsx", + "parent": "/columns/_layout" + }, + "/columns/_layout/events/$id": { + "filePath": "columns/_layout/events.$id.lazy.tsx", "parent": "/columns/_layout" }, "/columns/_layout/replies/$id": { diff --git a/src/routes/$account/_app.tsx b/src/routes/$account/_app.tsx index 8ae1fa1d..b7b988e9 100644 --- a/src/routes/$account/_app.tsx +++ b/src/routes/$account/_app.tsx @@ -1,15 +1,3 @@ -import type { LumeColumn } from "@/types"; import { createFileRoute } from "@tanstack/react-router"; -import { resolveResource } from "@tauri-apps/api/path"; -import { readTextFile } from "@tauri-apps/plugin-fs"; -export const Route = createFileRoute("/$account/_app")({ - beforeLoad: async () => { - const systemPath = "resources/columns.json"; - const resourcePath = await resolveResource(systemPath); - const resourceFile = await readTextFile(resourcePath); - const systemColumns: LumeColumn[] = JSON.parse(resourceFile); - - return { systemColumns }; - }, -}); +export const Route = createFileRoute("/$account/_app")(); diff --git a/src/routes/$account/_app/home.lazy.tsx b/src/routes/$account/_app/home.lazy.tsx index a2b07b02..211b24dc 100644 --- a/src/routes/$account/_app/home.lazy.tsx +++ b/src/routes/$account/_app/home.lazy.tsx @@ -1,13 +1,16 @@ -import { commands } from "@/commands.gen"; +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 { @@ -25,9 +28,8 @@ export const Route = createLazyFileRoute("/$account/_app/home")({ }); function Screen() { - const { initialColumns } = Route.useRouteContext(); + const columns = useStore(appColumns, (state) => state); - const [columns, setColumns] = useState([]); const [emblaRef, emblaApi] = useEmblaCarousel({ watchDrag: false, loop: false, @@ -51,11 +53,11 @@ function Screen() { const add = useDebouncedCallback((column: LumeColumn) => { column.label = `${column.label}-${nanoid()}`; // update col label - setColumns((prev) => [column, ...prev]); + appColumns.setState((prev) => [column, ...prev]); }, 150); const remove = useDebouncedCallback((label: string) => { - setColumns((prev) => prev.filter((t) => t.label !== label)); + appColumns.setState((prev) => prev.filter((t) => t.label !== label)); }, 150); const move = useDebouncedCallback( @@ -70,12 +72,12 @@ function Screen() { if (direction === "left") newCols.splice(colIndex - 1, 0, col); if (direction === "right") newCols.splice(colIndex + 1, 0, col); - setColumns(newCols); + appColumns.setState(() => newCols); }, 150, ); - const updateName = useDebouncedCallback((label: string, title: string) => { + const update = useDebouncedCallback((label: string, title: string) => { const currentColIndex = columns.findIndex((col) => col.label === label); const updatedCol = Object.assign({}, columns[currentColIndex]); @@ -84,10 +86,10 @@ function Screen() { const newCols = columns.slice(); newCols[currentColIndex] = updatedCol; - setColumns(newCols); + appColumns.setState(() => newCols); }, 150); - const reset = useDebouncedCallback(() => setColumns([]), 150); + const reset = useDebouncedCallback(() => appColumns.setState(() => []), 150); const handleKeyDown = useDebouncedCallback((event) => { if (event.defaultPrevented) return; @@ -106,18 +108,6 @@ function Screen() { event.preventDefault(); }, 150); - const saveAllColumns = useDebouncedCallback(async () => { - const key = "lume_v4:columns"; - const content = JSON.stringify(columns); - const res = await commands.setLumeStore(key, content); - - if (res.status === "ok") { - return res.data; - } else { - console.log(res.error); - } - }, 200); - useEffect(() => { if (emblaApi) { emblaApi.on("scroll", emitScrollEvent); @@ -132,14 +122,6 @@ function Screen() { }; }, [emblaApi, emitScrollEvent, emitResizeEvent]); - useEffect(() => { - if (columns) saveAllColumns(); - }, [columns]); - - useEffect(() => { - setColumns(initialColumns); - }, [initialColumns]); - // Listen for keyboard event useEffect(() => { window.addEventListener("keydown", handleKeyDown); @@ -158,7 +140,7 @@ function Screen() { if (data.payload.type === "move") move(data.payload.label, data.payload.direction); if (data.payload.type === "set_title") - updateName(data.payload.label, data.payload.title); + update(data.payload.label, data.payload.title); }); return () => { @@ -166,6 +148,21 @@ function Screen() { }; }, []); + 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) { + getSystemColumns(); + } + }, [columns.length]); + return (
diff --git a/src/routes/$account/_app/home.tsx b/src/routes/$account/_app/home.tsx index 9d6a29ff..bb6f89e9 100644 --- a/src/routes/$account/_app/home.tsx +++ b/src/routes/$account/_app/home.tsx @@ -1,20 +1,3 @@ -import { commands } from "@/commands.gen"; -import type { LumeColumn } from "@/types"; import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/$account/_app/home")({ - beforeLoad: async ({ context }) => { - const key = "lume_v4:columns"; - const defaultColumns = context.systemColumns.filter((col) => col.default); - const query = await commands.getLumeStore(key); - - let initialColumns: LumeColumn[] = defaultColumns; - - if (query.status === "ok") { - initialColumns = JSON.parse(query.data); - return { initialColumns }; - } - - return { initialColumns }; - }, -}); +export const Route = createFileRoute("/$account/_app/home")(); diff --git a/src/routes/columns/_layout/create-group.lazy.tsx b/src/routes/columns/_layout/create-group.lazy.tsx deleted file mode 100644 index 7c0c7953..00000000 --- a/src/routes/columns/_layout/create-group.lazy.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { commands } from "@/commands.gen"; -import { Spinner } from "@/components"; -import { User } from "@/components/user"; -import { Plus, X } from "@phosphor-icons/react"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { message } from "@tauri-apps/plugin-dialog"; -import { useState, useTransition } from "react"; - -export const Route = createLazyFileRoute("/columns/_layout/create-group")({ - component: Screen, -}); - -const REYA_NPUB = - "npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"; - -function Screen() { - const contacts = Route.useLoaderData(); - const search = Route.useSearch(); - const navigate = Route.useNavigate(); - const { queryClient } = Route.useRouteContext(); - - const [title, setTitle] = useState(""); - const [npub, setNpub] = useState(""); - const [users, setUsers] = useState([REYA_NPUB]); - const [isPending, startTransition] = useTransition(); - - const toggleUser = (pubkey: string) => { - setUsers((prev) => - prev.includes(pubkey) - ? prev.filter((i) => i !== pubkey) - : [...prev, pubkey], - ); - }; - - const addUser = () => { - if (!npub.startsWith("npub1")) return; - if (users.includes(npub)) return; - - setUsers((prev) => [...prev, npub]); - setNpub(""); - }; - - const submit = () => { - startTransition(async () => { - const key = `lume_v4:group:${search.label}`; - const res = await commands.setLumeStore(key, JSON.stringify(users)); - - if (res.status === "ok") { - await queryClient.invalidateQueries({ - queryKey: [search.label, search.account], - }); - // @ts-ignore, tanstack router bug. - navigate({ to: search.redirect, search: { ...search, name: title } }); - } else { - await message(res.error, { - title: "Create Group", - kind: "error", - }); - return; - } - }); - }; - - return ( -
-
-

Create a group

-

- For the people that you want to keep up. -

-
-
-
- - setTitle(e.target.value)} - placeholder="Enter a name for this group" - className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" - /> -
-
-
-
- setNpub(e.target.value)} - placeholder="npub1..." - className="w-full px-3 text-sm border-none rounded-lg h-9 bg-neutral-300 dark:bg-neutral-700 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" - /> - -
-
- Added -
- {users.length ? ( - users.map((item: string) => ( - - )) - ) : ( -
- Empty. -
- )} -
-
-
- Contacts -
- {contacts.length ? ( - contacts.map((item: string) => ( - - )) - ) : ( -
-

- Find more user at{" "} - - Nostr Directory - -

-
- )} -
-
-
- -
-
-
- ); -} diff --git a/src/routes/columns/_layout/gallery.lazy.tsx b/src/routes/columns/_layout/gallery.lazy.tsx index 1839b1ff..ec1a66b2 100644 --- a/src/routes/columns/_layout/gallery.lazy.tsx +++ b/src/routes/columns/_layout/gallery.lazy.tsx @@ -1,49 +1,30 @@ -import type { LumeColumn } from "@/types"; +import { commands } from "@/commands.gen"; +import { Spinner, User } from "@/components"; +import { LumeWindow } from "@/system"; +import type { LumeColumn, NostrEvent } from "@/types"; +import { ArrowClockwise, Plus } 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 { getCurrentWindow } from "@tauri-apps/api/window"; +import { resolveResource } from "@tauri-apps/api/path"; +import { readTextFile } from "@tauri-apps/plugin-fs"; +import { useCallback } from "react"; export const Route = createLazyFileRoute("/columns/_layout/gallery")({ component: Screen, }); function Screen() { - const { columns } = Route.useRouteContext(); - - const install = async (column: LumeColumn) => { - const mainWindow = getCurrentWindow(); - await mainWindow.emit("columns", { type: "add", column }); - }; - return ( - - {columns.map((column) => ( -
-
-
- {column.name} -
-
- {column.description} -
-
- -
- ))} + + + + ); } + +function Core() { + const { isLoading, data } = useQuery({ + queryKey: ["core"], + 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 ( +
+
+

Core

+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : ( + data.map((column) => ( +
+
+
+ {column.name} +
+
+ {column.description} +
+
+ +
+ )) + )} +
+
+ ); +} + +function MyGroups() { + const { account } = Route.useSearch(); + const { isLoading, data, refetch } = useQuery({ + queryKey: ["mygroups", account], + queryFn: async () => { + const res = await commands.getAllGroups(); + + if (res.status === "ok") { + const data = res.data.map((item) => JSON.parse(item) as NostrEvent); + return data; + } else { + throw new Error(res.error); + } + }, + select: (data) => + data.filter( + (item) => item.tags.filter((tag) => tag[0] === "p")?.length > 0, + ), + refetchOnWindowFocus: false, + }); + + const renderItem = useCallback( + (item: NostrEvent) => { + const name = item.tags.filter((tag) => tag[0] === "d")[0][1] ?? "unnamed"; + + return ( +
+
+ {item.tags + .filter((tag) => tag[0] === "p") + .map((tag) => ( +
+ + + + + +
+ ))} +
+
+
{name}
+
+ +
+
+
+ ); + }, + [data], + ); + + return ( +
+
+

My groups

+
+ + +
+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : !data.length ? ( +
+

You don't have any groups yet.

+
+ ) : ( + data.map((item) => renderItem(item)) + )} +
+
+ ); +} + +function MyInterests() { + const { account } = Route.useSearch(); + const { isLoading, data, refetch } = useQuery({ + queryKey: ["myinterests", account], + queryFn: async () => { + const res = await commands.getAllInterests(); + + if (res.status === "ok") { + const data = res.data.map((item) => JSON.parse(item) as NostrEvent); + return data; + } else { + throw new Error(res.error); + } + }, + select: (data) => + data.filter( + (item) => item.tags.filter((tag) => tag[0] === "t")?.length > 0, + ), + refetchOnWindowFocus: false, + }); + + const renderItem = useCallback( + (item: NostrEvent) => { + const name = item.tags.filter((tag) => tag[0] === "d")[0][1] ?? "unnamed"; + + return ( +
+
+ {item.tags + .filter((tag) => tag[0] === "t") + .map((tag) => ( +
+ {tag[1]} +
+ ))} +
+
+
{name}
+
+ +
+
+
+ ); + }, + [data], + ); + + return ( +
+
+

My interests

+
+ + +
+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : !data.length ? ( +
+

You don't have any interests yet.

+
+ ) : ( + data.map((item) => renderItem(item)) + )} +
+
+ ); +} diff --git a/src/routes/columns/_layout/gallery.tsx b/src/routes/columns/_layout/gallery.tsx deleted file mode 100644 index c9b7ad4d..00000000 --- a/src/routes/columns/_layout/gallery.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { LumeColumn } from "@/types"; -import { createFileRoute } from "@tanstack/react-router"; -import { resolveResource } from "@tauri-apps/api/path"; -import { readTextFile } from "@tauri-apps/plugin-fs"; - -export const Route = createFileRoute("/columns/_layout/gallery")({ - beforeLoad: 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, - }; - }, -}); diff --git a/src/routes/columns/_layout/group.tsx b/src/routes/columns/_layout/group.tsx deleted file mode 100644 index b0d29a7c..00000000 --- a/src/routes/columns/_layout/group.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { commands } from "@/commands.gen"; -import { createFileRoute, redirect } from "@tanstack/react-router"; - -export const Route = createFileRoute("/columns/_layout/group")({ - beforeLoad: async ({ search }) => { - const key = `lume_v4:group:${search.label}`; - const res = await commands.getLumeStore(key); - - if (res.status === "ok") { - const groups: string[] = JSON.parse(res.data); - - if (groups.length) { - return { groups }; - } - } - - throw redirect({ - to: "/columns/create-group", - search: { - ...search, - redirect: "/columns/group", - }, - }); - }, -}); diff --git a/src/routes/columns/_layout/group.lazy.tsx b/src/routes/columns/_layout/groups.$id.lazy.tsx similarity index 87% rename from src/routes/columns/_layout/group.lazy.tsx rename to src/routes/columns/_layout/groups.$id.lazy.tsx index 44eae315..3582ea6a 100644 --- a/src/routes/columns/_layout/group.lazy.tsx +++ b/src/routes/columns/_layout/groups.$id.lazy.tsx @@ -7,16 +7,19 @@ import { ArrowDown } from "@phosphor-icons/react"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { useInfiniteQuery } from "@tanstack/react-query"; import { createLazyFileRoute } from "@tanstack/react-router"; -import { useCallback, useRef } from "react"; +import { listen } from "@tauri-apps/api/event"; +import { useCallback, useEffect, useRef } from "react"; import { Virtualizer } from "virtua"; -export const Route = createLazyFileRoute("/columns/_layout/group")({ +export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({ component: Screen, }); export function Screen() { - const { label, account } = Route.useSearch(); - const { groups } = Route.useRouteContext(); + const group = Route.useLoaderData(); + const params = Route.useParams(); + const { queryClient } = Route.useRouteContext(); + const { data, isLoading, @@ -25,11 +28,11 @@ export function Screen() { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - queryKey: [label, account], + queryKey: ["groups", params.id], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { const until = pageParam > 0 ? pageParam.toString() : undefined; - const res = await commands.getGroupEvents(groups, until); + const res = await commands.getAllEventsByAuthors(group, until); if (res.status === "error") { throw new Error(res.error); @@ -39,6 +42,7 @@ export function Screen() { }, getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1, select: (data) => data?.pages.flat(), + enabled: group?.length > 0, refetchOnWindowFocus: false, }); @@ -80,6 +84,16 @@ export function Screen() { [data], ); + useEffect(() => { + const unlisten = listen("synchronized", async () => { + await queryClient.invalidateQueries({ queryKey: ["groups", params.id] }); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + return ( { + const res = await commands.getGroup(params.id); + + if (res.status === "ok") { + const event: NostrEvent = JSON.parse(res.data); + const tag = event.tags + .filter((tag) => tag[0] === "p") + .map((tag) => tag[1]); + + return tag; + } else { + throw new Error(res.error); + } + }, +}); diff --git a/src/routes/columns/_layout/hashtags.$content.lazy.tsx b/src/routes/columns/_layout/interests.$id.lazy.tsx similarity index 85% rename from src/routes/columns/_layout/hashtags.$content.lazy.tsx rename to src/routes/columns/_layout/interests.$id.lazy.tsx index 10a7f06b..61d82bd5 100644 --- a/src/routes/columns/_layout/hashtags.$content.lazy.tsx +++ b/src/routes/columns/_layout/interests.$id.lazy.tsx @@ -7,16 +7,19 @@ import { ArrowDown } from "@phosphor-icons/react"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { useInfiniteQuery } from "@tanstack/react-query"; import { createLazyFileRoute } from "@tanstack/react-router"; -import { useCallback, useRef } from "react"; +import { listen } from "@tauri-apps/api/event"; +import { useCallback, useEffect, useRef } from "react"; import { Virtualizer } from "virtua"; -export const Route = createLazyFileRoute("/columns/_layout/hashtags/$content")({ +export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({ component: Screen, }); export function Screen() { - const { label, account } = Route.useSearch(); - const { content } = Route.useParams(); + const hashtags = Route.useLoaderData(); + const params = Route.useParams(); + const { queryClient } = Route.useRouteContext(); + const { data, isLoading, @@ -25,12 +28,12 @@ export function Screen() { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - queryKey: [label, account], + queryKey: ["hashtags", params.id], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { - const hashtags = content.split("_"); + const tags = hashtags.map((tag) => tag.toLowerCase().replace("#", "")); const until = pageParam > 0 ? pageParam.toString() : undefined; - const res = await commands.getHashtagEvents(hashtags, until); + const res = await commands.getAllEventsByHashtags(tags, until); if (res.status === "error") { throw new Error(res.error); @@ -81,6 +84,18 @@ export function Screen() { [data], ); + useEffect(() => { + const unlisten = listen("synchronized", async () => { + await queryClient.invalidateQueries({ + queryKey: ["hashtags", params.id], + }); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + return ( { + const res = await commands.getInterest(params.id); + + if (res.status === "ok") { + const event: NostrEvent = JSON.parse(res.data); + const tag = event.tags + .filter((tag) => tag[0] === "t") + .map((tag) => tag[1]); + + return tag; + } else { + throw new Error(res.error); + } + }, +}); diff --git a/src/routes/columns/_layout/newsfeed.lazy.tsx b/src/routes/columns/_layout/newsfeed.lazy.tsx index e6bd62a0..875edd1a 100644 --- a/src/routes/columns/_layout/newsfeed.lazy.tsx +++ b/src/routes/columns/_layout/newsfeed.lazy.tsx @@ -6,11 +6,7 @@ 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 { - Navigate, - createLazyFileRoute, - useLocation, -} from "@tanstack/react-router"; +import { createLazyFileRoute } from "@tanstack/react-router"; import { listen } from "@tauri-apps/api/event"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { @@ -33,12 +29,12 @@ export const Route = createLazyFileRoute("/columns/_layout/newsfeed")({ }); export function Screen() { + const contacts = Route.useLoaderData(); const { queryClient } = Route.useRouteContext(); const { label, account } = Route.useSearch(); const { data, isLoading, - isError, isFetching, isFetchingNextPage, hasNextPage, @@ -48,7 +44,7 @@ export function Screen() { initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { const until = pageParam > 0 ? pageParam.toString() : undefined; - const res = await commands.getLocalEvents(until); + const res = await commands.getAllEventsByAuthors(contacts, until); if (res.status === "error") { throw new Error(res.error); @@ -58,9 +54,9 @@ export function Screen() { }, getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1, select: (data) => data?.pages.flat(), + enabled: contacts?.length > 0, }); - const location = useLocation(); const ref = useRef(null); const renderItem = useCallback( @@ -109,16 +105,6 @@ export function Screen() { }; }, []); - if (isError) { - return ( - - ); - } - return ( { const res = await commands.getContactList(); diff --git a/src/routes/columns/_layout/notification.lazy.tsx b/src/routes/columns/_layout/notification.lazy.tsx index 0731917e..da12877b 100644 --- a/src/routes/columns/_layout/notification.lazy.tsx +++ b/src/routes/columns/_layout/notification.lazy.tsx @@ -10,7 +10,7 @@ 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, useRef } from "react"; +import { type ReactNode, useEffect, useMemo, useRef } from "react"; import { Virtualizer } from "virtua"; export const Route = createLazyFileRoute("/columns/_layout/notification")({ @@ -180,17 +180,7 @@ function Screen() {
{events.map((event) => ( - tag[0] === "P")[1]} - > - - -
- ₿ {decodeZapInvoice(event.tags).bitcoinFormatted} -
-
-
+ ))}
@@ -311,3 +301,36 @@ function TextNote({ event }: { event: LumeEvent }) { ); } + +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.lazy.tsx b/src/routes/columns/_layout/stories.lazy.tsx index 1167c68b..4b75a462 100644 --- a/src/routes/columns/_layout/stories.lazy.tsx +++ b/src/routes/columns/_layout/stories.lazy.tsx @@ -50,7 +50,7 @@ function StoryItem({ contact }: { contact: string }) { } = useQuery({ queryKey: ["stories", contact], queryFn: async () => { - const res = await commands.getEventsBy(contact, 10); + const res = await commands.getAllEventsByAuthor(contact, 10); if (res.status === "ok") { const data = toLumeEvents(res.data); diff --git a/src/routes/columns/_layout/users.$id.lazy.tsx b/src/routes/columns/_layout/users.$id.lazy.tsx index 81900986..c3e850cf 100644 --- a/src/routes/columns/_layout/users.$id.lazy.tsx +++ b/src/routes/columns/_layout/users.$id.lazy.tsx @@ -22,7 +22,7 @@ function Screen() { const { isLoading, data: events } = useQuery({ queryKey: ["stories", id], queryFn: async () => { - const res = await commands.getEventsBy(id, 100); + const res = await commands.getAllEventsByAuthor(id, 100); if (res.status === "ok") { const data = toLumeEvents(res.data); diff --git a/src/routes/new.lazy.tsx b/src/routes/new.lazy.tsx index 209c5526..f1c8287d 100644 --- a/src/routes/new.lazy.tsx +++ b/src/routes/new.lazy.tsx @@ -21,25 +21,21 @@ function Screen() { href="/auth/connect" className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-900 ring-1 ring-black/5 dark:ring-white/5" > -

Continue with Nostr Connect

-
-

- Your account will be handled by a remote signer. Lume will not - store your account keys. -

-
+

Continue with Nostr Connect

+

+ Your account will be handled by a remote signer. Lume will not + store your account keys. +

-

Continue with Secret Key

-
-

- Lume will store your keys in secure storage. You can provide a - password to add extra security. -

-
+

Continue with Secret Key

+

+ Lume will store your keys in secure storage. You can provide a + password to add extra security. +

diff --git a/src/routes/set-group.lazy.tsx b/src/routes/set-group.lazy.tsx new file mode 100644 index 00000000..a632e1ba --- /dev/null +++ b/src/routes/set-group.lazy.tsx @@ -0,0 +1,205 @@ +import { commands } from "@/commands.gen"; +import { Spinner } from "@/components"; +import { User } from "@/components/user"; +import { Plus, X } from "@phosphor-icons/react"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { getCurrentWindow } from "@tauri-apps/api/window"; +import { message } from "@tauri-apps/plugin-dialog"; +import { useState, useTransition } from "react"; + +export const Route = createLazyFileRoute("/set-group")({ + component: Screen, +}); + +function Screen() { + const contacts = Route.useLoaderData(); + const { account } = Route.useSearch(); + const { queryClient } = Route.useRouteContext(); + + const [title, setTitle] = useState(""); + const [npub, setNpub] = useState(""); + const [users, setUsers] = useState([]); + const [isPending, startTransition] = useTransition(); + + const toggleUser = (pubkey: string) => { + setUsers((prev) => + prev.includes(pubkey) + ? prev.filter((i) => i !== pubkey) + : [...prev, pubkey], + ); + }; + + const addUser = () => { + if (!npub.startsWith("npub1")) return; + if (users.includes(npub)) return; + + setUsers((prev) => [...prev, npub]); + setNpub(""); + }; + + const submit = () => { + startTransition(async () => { + const res = await commands.setGroup(title, null, null, users); + + if (res.status === "ok") { + const window = getCurrentWindow(); + + // Invalidate cache + await queryClient.invalidateQueries({ + queryKey: ["mygroups", account], + }); + + // Create column in the main window + await window.emitTo("main", "columns", { + type: "add", + column: { + label: res.data, + name: title, + url: `/columns/groups/${res.data}`, + }, + }); + + // Close current popup + await window.close(); + } else { + await message(res.error, { kind: "error" }); + return; + } + }); + }; + + return ( +
+
+
+
+ + setTitle(e.target.value)} + placeholder="family, bff, devs,..." + className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" + /> +
+ +
+ + +
+

Added

+
+ setNpub(e.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") addUser(); + }} + placeholder="npub1..." + className="w-full px-3 text-sm border-none rounded-lg h-9 bg-neutral-100 dark:bg-neutral-900 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" + /> + +
+
+ {users.length ? ( + users.map((item: string) => ( + + )) + ) : ( +
+ Please add some user to your group. +
+ )} +
+
+
+

Contacts

+
+ {contacts.length ? ( + contacts + .filter((c) => !users.includes(c)) + .map((item: string) => ( + + )) + ) : ( +
+

+ Find more user at{" "} + + Nostr Directory + +

+
+ )} +
+
+
+ + + + +
+
+ ); +} diff --git a/src/routes/set-group.tsx b/src/routes/set-group.tsx new file mode 100644 index 00000000..73719832 --- /dev/null +++ b/src/routes/set-group.tsx @@ -0,0 +1,23 @@ +import { commands } from "@/commands.gen"; +import { createFileRoute } from "@tanstack/react-router"; + +type RouteSearch = { + account: string; +}; + +export const Route = createFileRoute("/set-group")({ + validateSearch: (search: Record): RouteSearch => { + return { + account: search.account, + }; + }, + 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/set-interest.lazy.tsx b/src/routes/set-interest.lazy.tsx new file mode 100644 index 00000000..377a0889 --- /dev/null +++ b/src/routes/set-interest.lazy.tsx @@ -0,0 +1,198 @@ +import { commands } from "@/commands.gen"; +import { cn } from "@/commons"; +import { Spinner } from "@/components"; +import { Plus, X } from "@phosphor-icons/react"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { getCurrentWindow } from "@tauri-apps/api/window"; +import { message } from "@tauri-apps/plugin-dialog"; +import { useState, useTransition } from "react"; + +const TOPICS = [ + { + title: "Popular", + content: [ + "#nostr", + "#introductions", + "#grownostr", + "#zap", + "#meme", + "#asknostr", + "#bitcoin", + ], + }, +]; + +export const Route = createLazyFileRoute("/set-interest")({ + component: Screen, +}); + +function Screen() { + const [title, setTitle] = useState(""); + const [hashtag, setHashtag] = useState(""); + const [hashtags, setHashtags] = useState([]); + const [isPending, startTransition] = useTransition(); + + const { account } = Route.useSearch(); + const { queryClient } = Route.useRouteContext(); + + const toggleHashtag = (tag: string) => { + setHashtags((prev) => + prev.includes(tag) ? prev.filter((i) => i !== tag) : [...prev, tag], + ); + }; + + const addHashtag = () => { + if (!hashtag.startsWith("#")) return; + if (hashtags.includes(hashtag)) return; + + setHashtags((prev) => [...prev, hashtag]); + setHashtag(""); + }; + + const submit = () => { + startTransition(async () => { + const content = hashtags.map((tag) => + tag.toLowerCase().replace(" ", "-").replace("#", ""), + ); + const res = await commands.setInterest(title, null, null, content); + + if (res.status === "ok") { + const window = getCurrentWindow(); + + // Invalidate cache + await queryClient.invalidateQueries({ + queryKey: ["myinterests", account], + }); + + // Create column in the main window + await window.emitTo("main", "columns", { + type: "add", + column: { + label: res.data, + name: title, + url: `/columns/interests/${res.data}`, + }, + }); + + // Close current popup + await window.close(); + } else { + await message(res.error, { kind: "error" }); + return; + } + }); + }; + + return ( +
+
+
+
+ + setTitle(e.target.value)} + placeholder="anime, sport, art,..." + className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" + /> +
+ +
+ + +
+ Added +
+ {hashtags.length ? ( + hashtags.map((item: string) => ( + + )) + ) : ( +
+ Please add some hashtag. +
+ )} +
+
+
+ Hashtags +
+ setHashtag(e.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") addHashtag(); + }} + className="w-full px-3 text-sm border-none rounded-lg h-9 bg-neutral-100 dark:bg-neutral-900 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" + /> + +
+
+ {TOPICS.map((topic) => ( +
+
{topic.title}
+
+ {topic.content.map((item) => ( + + ))} +
+
+ ))} +
+
+
+ + + + +
+
+ ); +} diff --git a/src/routes/set-interest.tsx b/src/routes/set-interest.tsx new file mode 100644 index 00000000..a8e13fce --- /dev/null +++ b/src/routes/set-interest.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from "@tanstack/react-router"; + +type RouteSearch = { + account: string; +}; + +export const Route = createFileRoute("/set-interest")({ + validateSearch: (search: Record): RouteSearch => { + return { + account: search.account, + }; + }, +}); diff --git a/src/system/window.ts b/src/system/window.ts index ed058c8f..672ec97a 100644 --- a/src/system/window.ts +++ b/src/system/window.ts @@ -1,6 +1,7 @@ import { commands } from "@/commands.gen"; import type { LumeColumn, NostrEvent } from "@/types"; import { getCurrentWindow } from "@tauri-apps/api/window"; +import { nanoid } from "nanoid"; import type { LumeEvent } from "./event"; export const LumeWindow = { @@ -150,8 +151,22 @@ export const LumeWindow = { throw new Error(query.error); } }, - openMainWindow: async () => { - const query = await commands.reopenLume(); - return query; + openPopup: async (title: string, url: string) => { + const query = await commands.openWindow({ + label: `popup-${nanoid()}`, + url, + title, + width: 400, + height: 500, + maximizable: false, + minimizable: false, + hidden_title: false, + }); + + if (query.status === "ok") { + return query.data; + } else { + throw new Error(query.error); + } }, };