feat: Add support for NIP-51 (#236)
* feat: and follow and interest sets * feat: improve query * feat: improve
This commit is contained in:
@@ -14,7 +14,8 @@
|
|||||||
"zap-*",
|
"zap-*",
|
||||||
"event-*",
|
"event-*",
|
||||||
"user-*",
|
"user-*",
|
||||||
"editor-*"
|
"editor-*",
|
||||||
|
"popup-*"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:path:default",
|
"core:path:default",
|
||||||
|
|||||||
@@ -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"]}}
|
{"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"]}}
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
{
|
{
|
||||||
"default": false,
|
"default": false,
|
||||||
"official": true,
|
"official": true,
|
||||||
"label": "local_feeds",
|
"label": "newsfeed",
|
||||||
"name": "Local Feeds",
|
"name": "Newsfeed",
|
||||||
"description": "All notes from your follows.",
|
"description": "All notes from you're following.",
|
||||||
"url": "/columns/newsfeed",
|
"url": "/columns/newsfeed",
|
||||||
"picture": ""
|
"picture": ""
|
||||||
},
|
},
|
||||||
@@ -58,19 +58,10 @@
|
|||||||
"official": true,
|
"official": true,
|
||||||
"label": "global_feeds",
|
"label": "global_feeds",
|
||||||
"name": "Global Feeds",
|
"name": "Global Feeds",
|
||||||
"description": "Discover all global notes.",
|
"description": "All global notes from all connected relays.",
|
||||||
"url": "/columns/global",
|
"url": "/columns/global",
|
||||||
"picture": ""
|
"picture": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": false,
|
|
||||||
"official": true,
|
|
||||||
"label": "group_feeds",
|
|
||||||
"name": "Group",
|
|
||||||
"description": "Custom feeds for group of people.",
|
|
||||||
"url": "/columns/group",
|
|
||||||
"picture": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": false,
|
"default": false,
|
||||||
"official": true,
|
"official": true,
|
||||||
|
|||||||
@@ -271,6 +271,15 @@ pub async fn login(
|
|||||||
// NIP-65: Connect to user's relay list
|
// NIP-65: Connect to user's relay list
|
||||||
init_nip65(client, &public_key).await;
|
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
|
// Run seperate thread for syncing data
|
||||||
let pk = public_key.clone();
|
let pk = public_key.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
@@ -292,7 +301,10 @@ pub async fn login(
|
|||||||
Kind::MuteList,
|
Kind::MuteList,
|
||||||
Kind::Bookmarks,
|
Kind::Bookmarks,
|
||||||
Kind::Interests,
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
Kind::FollowSet,
|
||||||
Kind::PinList,
|
Kind::PinList,
|
||||||
|
Kind::EventDeletion,
|
||||||
])
|
])
|
||||||
.limit(1000),
|
.limit(1000),
|
||||||
NegentropyOptions::default(),
|
NegentropyOptions::default(),
|
||||||
@@ -355,15 +367,6 @@ pub async fn login(
|
|||||||
println!("Subscribed: {}", e.success.len())
|
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
|
// Get events from contact list
|
||||||
if !contact_list.is_empty() {
|
if !contact_list.is_empty() {
|
||||||
let authors: Vec<PublicKey> = contact_list.iter().map(|f| f.public_key).collect();
|
let authors: Vec<PublicKey> = contact_list.iter().map(|f| f.public_key).collect();
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ pub async fn subscribe_to(id: String, state: State<'_, Nostr>) -> Result<(), Str
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_events_by(
|
pub async fn get_all_events_by_author(
|
||||||
public_key: String,
|
public_key: String,
|
||||||
limit: i32,
|
limit: i32,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
@@ -237,14 +237,111 @@ pub async fn get_events_by(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_local_events(
|
pub async fn get_all_events_by_authors(
|
||||||
until: Option<&str>,
|
public_keys: Vec<String>,
|
||||||
|
until: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<Vec<RichEvent>, String> {
|
) -> Result<Vec<RichEvent>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
let as_of = match until {
|
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<PublicKey> = public_keys
|
||||||
|
.iter()
|
||||||
|
.map(|pk| PublicKey::from_str(pk).map_err(|err| err.to_string()))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
until: Option<String>,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
) -> Result<Vec<RichEvent>, 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<String>,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
) -> Result<Vec<RichEvent>, 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(),
|
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<Vec<RichEvent>, 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<PublicKey> = public_keys
|
|
||||||
.iter()
|
|
||||||
.map(|p| PublicKey::from_str(p).map_err(|err| err.to_string()))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
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]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_global_events(
|
pub async fn get_global_events(
|
||||||
until: Option<&str>,
|
until: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<Vec<RichEvent>, String> {
|
) -> Result<Vec<RichEvent>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
let as_of = match until {
|
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(),
|
None => Timestamp::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(20)
|
.limit(FETCH_LIMIT)
|
||||||
.until(as_of);
|
.until(as_of);
|
||||||
|
|
||||||
match client
|
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<Vec<RichEvent>, 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]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn publish(
|
pub async fn publish(
|
||||||
@@ -534,7 +533,7 @@ pub async fn reply(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn repost(raw: &str, state: State<'_, Nostr>) -> Result<String, String> {
|
pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event = Event::from_json(raw).map_err(|err| err.to_string())?;
|
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<Stri
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn user_to_bech32(user: &str, state: State<'_, Nostr>) -> Result<String, String> {
|
pub async fn user_to_bech32(user: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key = PublicKey::parse(user).map_err(|err| err.to_string())?;
|
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()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn is_deleted_event(id: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use nostr_sdk::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::{str::FromStr, time::Duration};
|
use std::{str::FromStr, time::Duration};
|
||||||
use tauri::State;
|
use tauri::{Emitter, Manager, State};
|
||||||
use tauri_specta::Event;
|
use tauri_specta::Event;
|
||||||
|
|
||||||
use crate::{common::get_latest_event, NewSettings, Nostr, Settings};
|
use crate::{common::get_latest_event, NewSettings, Nostr, Settings};
|
||||||
@@ -105,18 +105,14 @@ pub async fn set_contact_list(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_contact_list(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
pub async fn get_contact_list(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
||||||
let client = &state.client;
|
let contact_list = state.contact_list.lock().await.clone();
|
||||||
|
println!("Total contacts: {}", contact_list.len());
|
||||||
|
let vec: Vec<String> = contact_list
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| f.public_key.to_hex())
|
||||||
|
.collect();
|
||||||
|
|
||||||
match client.get_contact_list(Some(Duration::from_secs(5))).await {
|
Ok(vec)
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -200,6 +196,194 @@ pub async fn toggle_contact(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn set_group(
|
||||||
|
title: String,
|
||||||
|
description: Option<String>,
|
||||||
|
image: Option<String>,
|
||||||
|
users: Vec<String>,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
handle: tauri::AppHandle,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let public_keys: Vec<PublicKey> = users
|
||||||
|
.iter()
|
||||||
|
.map(|u| PublicKey::from_str(u).unwrap())
|
||||||
|
.collect();
|
||||||
|
let label = title.to_lowercase().replace(" ", "-");
|
||||||
|
let mut tags: Vec<Tag> = 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::<Nostr>();
|
||||||
|
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<String, String> {
|
||||||
|
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<Vec<String>, 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<String> = 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<String>,
|
||||||
|
image: Option<String>,
|
||||||
|
hashtags: Vec<String>,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
handle: tauri::AppHandle,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let label = title.to_lowercase().replace(" ", "-");
|
||||||
|
let mut tags: Vec<Tag> = 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::<Nostr>();
|
||||||
|
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<String, String> {
|
||||||
|
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<Vec<String>, 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<String> = events.iter().map(|ev| ev.as_json()).collect();
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_mention_list(state: State<'_, Nostr>) -> Result<Vec<Mention>, String> {
|
pub async fn get_mention_list(state: State<'_, Nostr>) -> Result<Vec<Mention>, String> {
|
||||||
@@ -230,61 +414,6 @@ pub async fn get_mention_list(state: State<'_, Nostr>) -> Result<Vec<Mention>, S
|
|||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub async fn set_lume_store(
|
|
||||||
key: String,
|
|
||||||
content: String,
|
|
||||||
state: State<'_, Nostr>,
|
|
||||||
) -> Result<String, 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 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<String, 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()
|
|
||||||
.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]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
|
|||||||
@@ -113,15 +113,15 @@ pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<bool
|
|||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), String> {
|
pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||||
if let Some(window) = app_handle.get_window(&window.label) {
|
if let Some(current_window) = app_handle.get_window(&window.label) {
|
||||||
if window.is_visible().unwrap_or_default() {
|
if current_window.is_visible().unwrap_or_default() {
|
||||||
let _ = window.set_focus();
|
let _ = current_window.set_focus();
|
||||||
} else {
|
} else {
|
||||||
let _ = window.show();
|
let _ = current_window.show();
|
||||||
let _ = window.set_focus();
|
let _ = current_window.set_focus();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let window = WebviewWindowBuilder::new(
|
let new_window = WebviewWindowBuilder::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&window.label,
|
&window.label,
|
||||||
WebviewUrl::App(PathBuf::from(window.url)),
|
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)
|
.title(&window.title)
|
||||||
.min_inner_size(window.width, window.height)
|
.min_inner_size(window.width, window.height)
|
||||||
.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)
|
.title_bar_style(TitleBarStyle::Overlay)
|
||||||
.minimizable(window.minimizable)
|
.minimizable(window.minimizable)
|
||||||
.maximizable(window.maximizable)
|
.maximizable(window.maximizable)
|
||||||
@@ -144,7 +144,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Restore native border
|
// Restore native border
|
||||||
window.add_border(None);
|
new_window.add_border(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -217,7 +217,7 @@ pub fn reopen_lume(app: tauri::AppHandle) {
|
|||||||
|
|
||||||
// Set a custom inset to the traffic lights
|
// Set a custom inset to the traffic lights
|
||||||
#[cfg(target_os = "macos")]
|
#[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")]
|
#[cfg(target_os = "macos")]
|
||||||
let win = window.clone();
|
let win = window.clone();
|
||||||
@@ -225,7 +225,7 @@ pub fn reopen_lume(app: tauri::AppHandle) {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
window.on_window_event(move |event| {
|
window.on_window_event(move |event| {
|
||||||
if let tauri::WindowEvent::ThemeChanged(_) = 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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,23 @@ pub fn filter_converstation(events: Vec<Event>) -> Vec<Event> {
|
|||||||
events
|
events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|ev| {
|
.filter_map(|ev| {
|
||||||
let tags = ev.get_tags_content(TagKind::SingleLetter(SingleLetterTag::lowercase(
|
if ev.kind == Kind::TextNote {
|
||||||
Alphabet::E,
|
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() {
|
if tags.is_empty() {
|
||||||
Some(ev)
|
Some(ev)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
Some(ev)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<Event>>()
|
.collect::<Vec<Event>>()
|
||||||
|
|||||||
@@ -116,8 +116,12 @@ fn main() {
|
|||||||
check_contact,
|
check_contact,
|
||||||
toggle_contact,
|
toggle_contact,
|
||||||
get_mention_list,
|
get_mention_list,
|
||||||
get_lume_store,
|
set_group,
|
||||||
set_lume_store,
|
get_group,
|
||||||
|
get_all_groups,
|
||||||
|
set_interest,
|
||||||
|
get_interest,
|
||||||
|
get_all_interests,
|
||||||
set_wallet,
|
set_wallet,
|
||||||
load_wallet,
|
load_wallet,
|
||||||
remove_wallet,
|
remove_wallet,
|
||||||
@@ -134,11 +138,12 @@ fn main() {
|
|||||||
get_event_from,
|
get_event_from,
|
||||||
get_replies,
|
get_replies,
|
||||||
subscribe_to,
|
subscribe_to,
|
||||||
get_events_by,
|
get_all_events_by_author,
|
||||||
|
get_all_events_by_authors,
|
||||||
|
get_all_events_by_hashtags,
|
||||||
get_local_events,
|
get_local_events,
|
||||||
get_group_events,
|
|
||||||
get_global_events,
|
get_global_events,
|
||||||
get_hashtag_events,
|
is_deleted_event,
|
||||||
search,
|
search,
|
||||||
publish,
|
publish,
|
||||||
reply,
|
reply,
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import { createStore } from "@tauri-apps/plugin-store";
|
|||||||
import { routeTree } from "./routes.gen"; // auto generated file
|
import { routeTree } from "./routes.gen"; // auto generated file
|
||||||
|
|
||||||
const platform = type();
|
const platform = type();
|
||||||
// @ts-ignore, https://github.com/tauri-apps/plugins-workspace/pull/1860
|
// @ts-expect-error, required: https://github.com/tauri-apps/plugins-workspace/pull/1860
|
||||||
const store = await createStore(".cache", { autoSave: 100 });
|
const store = await createStore(".cache", { autoSave: 250 });
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
|
|||||||
@@ -163,17 +163,49 @@ async getMentionList() : Promise<Result<Mention[], string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getLumeStore(key: string) : Promise<Result<string, string>> {
|
async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise<Result<string, string>> {
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setLumeStore(key: string, content: string) : Promise<Result<string, string>> {
|
async getGroup(id: string) : Promise<Result<string, string>> {
|
||||||
try {
|
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<Result<string[], string>> {
|
||||||
|
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<Result<string, string>> {
|
||||||
|
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<Result<string, string>> {
|
||||||
|
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<Result<string[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_all_interests") };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
@@ -307,9 +339,25 @@ async subscribeTo(id: string) : Promise<Result<null, string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getEventsBy(publicKey: string, limit: number) : Promise<Result<RichEvent[], string>> {
|
async getAllEventsByAuthor(publicKey: string, limit: number) : Promise<Result<RichEvent[], string>> {
|
||||||
try {
|
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<Result<RichEvent[], string>> {
|
||||||
|
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<Result<RichEvent[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_hashtags", { hashtags, until }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
@@ -323,14 +371,6 @@ async getLocalEvents(until: string | null) : Promise<Result<RichEvent[], string>
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getGroupEvents(publicKeys: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
|
||||||
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<Result<RichEvent[], string>> {
|
async getGlobalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) };
|
||||||
@@ -339,9 +379,9 @@ async getGlobalEvents(until: string | null) : Promise<Result<RichEvent[], string
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getHashtagEvents(hashtags: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
async isDeletedEvent(id: string) : Promise<Result<boolean, string>> {
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { decode } from "light-bolt11-decoder";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import type { RichEvent, Settings } from "./commands.gen";
|
import type { RichEvent, Settings } from "./commands.gen";
|
||||||
import { LumeEvent } from "./system";
|
import { LumeEvent } from "./system";
|
||||||
import type { NostrEvent } from "./types";
|
import type { LumeColumn, NostrEvent } from "./types";
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
dayjs.extend(updateLocale);
|
dayjs.extend(updateLocale);
|
||||||
@@ -154,6 +154,7 @@ export function decodeZapInvoice(tags?: string[][]) {
|
|||||||
(s: { name: string }) => s.name === "amount",
|
(s: { name: string }) => s.name === "amount",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-ignore, its fine.
|
||||||
const amount = Number.parseInt(amountSection.value);
|
const amount = Number.parseInt(amountSection.value);
|
||||||
const displayValue = getBitcoinDisplayValues(amount);
|
const displayValue = getBitcoinDisplayValues(amount);
|
||||||
|
|
||||||
@@ -289,3 +290,5 @@ export const appSettings = new Store<Settings>({
|
|||||||
display_media: true,
|
display_media: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const appColumns = new Store<LumeColumn[]>([]);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
|
import { appColumns } from "@/commons";
|
||||||
import type { LumeColumn } from "@/types";
|
import type { LumeColumn } from "@/types";
|
||||||
import { CaretDown, Check } from "@phosphor-icons/react";
|
import { CaretDown, Check } from "@phosphor-icons/react";
|
||||||
import { useParams } from "@tanstack/react-router";
|
import { useParams } from "@tanstack/react-router";
|
||||||
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||||
@@ -88,7 +90,7 @@ export const Column = memo(function Column({ column }: { column: LumeColumn }) {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full w-[440px] shrink-0 border-r border-black/5 dark:border-white/5">
|
<div className="h-full w-[440px] shrink-0 border-r border-black/5 dark:border-white/5">
|
||||||
<div className="flex flex-col gap-px size-full">
|
<div className="flex flex-col gap-px size-full">
|
||||||
<Header label={column.label} name={column.name} />
|
<Header label={column.label} />
|
||||||
<div ref={container} className="flex-1 size-full">
|
<div ref={container} className="flex-1 size-full">
|
||||||
{!isCreated ? (
|
{!isCreated ? (
|
||||||
<div className="size-full flex items-center justify-center">
|
<div className="size-full flex items-center justify-center">
|
||||||
@@ -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 [title, setTitle] = useState("");
|
||||||
const [isChanged, setIsChanged] = useState(false);
|
const [isChanged, setIsChanged] = useState(false);
|
||||||
|
|
||||||
|
const column = useStore(appColumns, (state) =>
|
||||||
|
state.find((col) => col.label === label),
|
||||||
|
);
|
||||||
|
|
||||||
const saveNewTitle = async () => {
|
const saveNewTitle = async () => {
|
||||||
const mainWindow = getCurrentWindow();
|
const mainWindow = getCurrentWindow();
|
||||||
await mainWindow.emit("columns", { type: "set_title", label, title });
|
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)}
|
onBlur={(e) => setTitle(e.currentTarget.textContent)}
|
||||||
className="text-[12px] font-semibold focus:outline-none"
|
className="text-[12px] font-semibold focus:outline-none"
|
||||||
>
|
>
|
||||||
{name}
|
{column.name}
|
||||||
</div>
|
</div>
|
||||||
{isChanged ? (
|
{isChanged ? (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import { LumeWindow } from "@/system";
|
|
||||||
|
|
||||||
export function Hashtag({ tag }: { tag: string }) {
|
export function Hashtag({ tag }: { tag: string }) {
|
||||||
return (
|
return (
|
||||||
<button
|
<span className="leading-normal cursor-default text-blue-500 hover:text-blue-600 font-normal">
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openHashtag(tag)}
|
|
||||||
className="leading-normal cursor-default text-blue-500 hover:text-blue-600 font-normal"
|
|
||||||
>
|
|
||||||
{tag}
|
{tag}
|
||||||
</button>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { createFileRoute } from '@tanstack/react-router'
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
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 LoadingImport } from './routes/loading'
|
||||||
import { Route as BootstrapRelaysImport } from './routes/bootstrap-relays'
|
import { Route as BootstrapRelaysImport } from './routes/bootstrap-relays'
|
||||||
import { Route as IndexImport } from './routes/index'
|
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 AccountBackupImport } from './routes/$account/backup'
|
||||||
import { Route as AccountAppImport } from './routes/$account/_app'
|
import { Route as AccountAppImport } from './routes/$account/_app'
|
||||||
import { Route as ColumnsLayoutStoriesImport } from './routes/columns/_layout/stories'
|
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 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 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 AccountSettingsWalletImport } from './routes/$account/_settings/wallet'
|
||||||
import { Route as AccountSettingsRelayImport } from './routes/$account/_settings/relay'
|
import { Route as AccountSettingsRelayImport } from './routes/$account/_settings/relay'
|
||||||
import { Route as AccountSettingsProfileImport } from './routes/$account/_settings/profile'
|
import { Route as AccountSettingsProfileImport } from './routes/$account/_settings/profile'
|
||||||
import { Route as AccountSettingsGeneralImport } from './routes/$account/_settings/general'
|
import { Route as AccountSettingsGeneralImport } from './routes/$account/_settings/general'
|
||||||
import { Route as AccountSettingsBitcoinConnectImport } from './routes/$account/_settings/bitcoin-connect'
|
import { Route as AccountSettingsBitcoinConnectImport } from './routes/$account/_settings/bitcoin-connect'
|
||||||
import { Route as AccountAppHomeImport } from './routes/$account/_app/home'
|
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 ColumnsLayoutCreateNewsfeedUsersImport } from './routes/columns/_layout/create-newsfeed.users'
|
||||||
import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/_layout/create-newsfeed.f2f'
|
import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/_layout/create-newsfeed.f2f'
|
||||||
|
|
||||||
@@ -58,8 +60,8 @@ const ColumnsLayoutOnboardingLazyImport = createFileRoute(
|
|||||||
const ColumnsLayoutNotificationLazyImport = createFileRoute(
|
const ColumnsLayoutNotificationLazyImport = createFileRoute(
|
||||||
'/columns/_layout/notification',
|
'/columns/_layout/notification',
|
||||||
)()
|
)()
|
||||||
const ColumnsLayoutNewsfeedLazyImport = createFileRoute(
|
const ColumnsLayoutGalleryLazyImport = createFileRoute(
|
||||||
'/columns/_layout/newsfeed',
|
'/columns/_layout/gallery',
|
||||||
)()
|
)()
|
||||||
const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/users/$id',
|
'/columns/_layout/users/$id',
|
||||||
@@ -67,9 +69,6 @@ const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
|||||||
const ColumnsLayoutRepliesIdLazyImport = createFileRoute(
|
const ColumnsLayoutRepliesIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/replies/$id',
|
'/columns/_layout/replies/$id',
|
||||||
)()
|
)()
|
||||||
const ColumnsLayoutHashtagsContentLazyImport = createFileRoute(
|
|
||||||
'/columns/_layout/hashtags/$content',
|
|
||||||
)()
|
|
||||||
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/events/$id',
|
'/columns/_layout/events/$id',
|
||||||
)()
|
)()
|
||||||
@@ -96,6 +95,16 @@ const NewLazyRoute = NewLazyImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route))
|
} 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({
|
const LoadingRoute = LoadingImport.update({
|
||||||
path: '/loading',
|
path: '/loading',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
@@ -190,11 +199,11 @@ const ColumnsLayoutNotificationLazyRoute =
|
|||||||
import('./routes/columns/_layout/notification.lazy').then((d) => d.Route),
|
import('./routes/columns/_layout/notification.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
const ColumnsLayoutNewsfeedLazyRoute = ColumnsLayoutNewsfeedLazyImport.update({
|
const ColumnsLayoutGalleryLazyRoute = ColumnsLayoutGalleryLazyImport.update({
|
||||||
path: '/newsfeed',
|
path: '/gallery',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
} as any).lazy(() =>
|
} 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({
|
const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({
|
||||||
@@ -204,11 +213,11 @@ const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({
|
|||||||
import('./routes/columns/_layout/stories.lazy').then((d) => d.Route),
|
import('./routes/columns/_layout/stories.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
const ColumnsLayoutGroupRoute = ColumnsLayoutGroupImport.update({
|
const ColumnsLayoutNewsfeedRoute = ColumnsLayoutNewsfeedImport.update({
|
||||||
path: '/group',
|
path: '/newsfeed',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
} as any).lazy(() =>
|
} 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({
|
const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
||||||
@@ -216,26 +225,12 @@ const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
|||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
} as any)
|
} 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 =
|
const ColumnsLayoutCreateNewsfeedRoute =
|
||||||
ColumnsLayoutCreateNewsfeedImport.update({
|
ColumnsLayoutCreateNewsfeedImport.update({
|
||||||
path: '/create-newsfeed',
|
path: '/create-newsfeed',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
} as any)
|
} 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({
|
const AccountSettingsWalletRoute = AccountSettingsWalletImport.update({
|
||||||
path: '/wallet',
|
path: '/wallet',
|
||||||
getParentRoute: () => AccountSettingsLazyRoute,
|
getParentRoute: () => AccountSettingsLazyRoute,
|
||||||
@@ -297,16 +292,6 @@ const ColumnsLayoutRepliesIdLazyRoute = ColumnsLayoutRepliesIdLazyImport.update(
|
|||||||
import('./routes/columns/_layout/replies.$id.lazy').then((d) => d.Route),
|
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({
|
const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
||||||
path: '/events/$id',
|
path: '/events/$id',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
@@ -314,6 +299,20 @@ const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
|||||||
import('./routes/columns/_layout/events.$id.lazy').then((d) => d.Route),
|
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 =
|
const ColumnsLayoutCreateNewsfeedUsersRoute =
|
||||||
ColumnsLayoutCreateNewsfeedUsersImport.update({
|
ColumnsLayoutCreateNewsfeedUsersImport.update({
|
||||||
path: '/users',
|
path: '/users',
|
||||||
@@ -351,6 +350,20 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LoadingImport
|
preLoaderRoute: typeof LoadingImport
|
||||||
parentRoute: typeof rootRoute
|
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': {
|
'/new': {
|
||||||
id: '/new'
|
id: '/new'
|
||||||
path: '/new'
|
path: '/new'
|
||||||
@@ -484,13 +497,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AccountSettingsWalletImport
|
preLoaderRoute: typeof AccountSettingsWalletImport
|
||||||
parentRoute: typeof AccountSettingsLazyImport
|
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': {
|
'/columns/_layout/create-newsfeed': {
|
||||||
id: '/columns/_layout/create-newsfeed'
|
id: '/columns/_layout/create-newsfeed'
|
||||||
path: '/create-newsfeed'
|
path: '/create-newsfeed'
|
||||||
@@ -498,13 +504,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/gallery': {
|
|
||||||
id: '/columns/_layout/gallery'
|
|
||||||
path: '/gallery'
|
|
||||||
fullPath: '/columns/gallery'
|
|
||||||
preLoaderRoute: typeof ColumnsLayoutGalleryImport
|
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
|
||||||
}
|
|
||||||
'/columns/_layout/global': {
|
'/columns/_layout/global': {
|
||||||
id: '/columns/_layout/global'
|
id: '/columns/_layout/global'
|
||||||
path: '/global'
|
path: '/global'
|
||||||
@@ -512,11 +511,11 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/group': {
|
'/columns/_layout/newsfeed': {
|
||||||
id: '/columns/_layout/group'
|
id: '/columns/_layout/newsfeed'
|
||||||
path: '/group'
|
path: '/newsfeed'
|
||||||
fullPath: '/columns/group'
|
fullPath: '/columns/newsfeed'
|
||||||
preLoaderRoute: typeof ColumnsLayoutGroupImport
|
preLoaderRoute: typeof ColumnsLayoutNewsfeedImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/stories': {
|
'/columns/_layout/stories': {
|
||||||
@@ -526,11 +525,11 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutStoriesImport
|
preLoaderRoute: typeof ColumnsLayoutStoriesImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/newsfeed': {
|
'/columns/_layout/gallery': {
|
||||||
id: '/columns/_layout/newsfeed'
|
id: '/columns/_layout/gallery'
|
||||||
path: '/newsfeed'
|
path: '/gallery'
|
||||||
fullPath: '/columns/newsfeed'
|
fullPath: '/columns/gallery'
|
||||||
preLoaderRoute: typeof ColumnsLayoutNewsfeedLazyImport
|
preLoaderRoute: typeof ColumnsLayoutGalleryLazyImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/notification': {
|
'/columns/_layout/notification': {
|
||||||
@@ -575,6 +574,20 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedUsersImport
|
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedUsersImport
|
||||||
parentRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
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': {
|
'/columns/_layout/events/$id': {
|
||||||
id: '/columns/_layout/events/$id'
|
id: '/columns/_layout/events/$id'
|
||||||
path: '/events/$id'
|
path: '/events/$id'
|
||||||
@@ -582,13 +595,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
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': {
|
'/columns/_layout/replies/$id': {
|
||||||
id: '/columns/_layout/replies/$id'
|
id: '/columns/_layout/replies/$id'
|
||||||
path: '/replies/$id'
|
path: '/replies/$id'
|
||||||
@@ -672,38 +678,36 @@ const ColumnsLayoutCreateNewsfeedRouteWithChildren =
|
|||||||
)
|
)
|
||||||
|
|
||||||
interface ColumnsLayoutRouteChildren {
|
interface ColumnsLayoutRouteChildren {
|
||||||
ColumnsLayoutCreateGroupRoute: typeof ColumnsLayoutCreateGroupRoute
|
|
||||||
ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
ColumnsLayoutGalleryRoute: typeof ColumnsLayoutGalleryRoute
|
|
||||||
ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute
|
ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute
|
||||||
ColumnsLayoutGroupRoute: typeof ColumnsLayoutGroupRoute
|
ColumnsLayoutNewsfeedRoute: typeof ColumnsLayoutNewsfeedRoute
|
||||||
ColumnsLayoutStoriesRoute: typeof ColumnsLayoutStoriesRoute
|
ColumnsLayoutStoriesRoute: typeof ColumnsLayoutStoriesRoute
|
||||||
ColumnsLayoutNewsfeedLazyRoute: typeof ColumnsLayoutNewsfeedLazyRoute
|
ColumnsLayoutGalleryLazyRoute: typeof ColumnsLayoutGalleryLazyRoute
|
||||||
ColumnsLayoutNotificationLazyRoute: typeof ColumnsLayoutNotificationLazyRoute
|
ColumnsLayoutNotificationLazyRoute: typeof ColumnsLayoutNotificationLazyRoute
|
||||||
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
||||||
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
||||||
|
ColumnsLayoutGroupsIdRoute: typeof ColumnsLayoutGroupsIdRoute
|
||||||
|
ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute
|
||||||
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
ColumnsLayoutHashtagsContentLazyRoute: typeof ColumnsLayoutHashtagsContentLazyRoute
|
|
||||||
ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute
|
ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute
|
ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
||||||
ColumnsLayoutCreateGroupRoute: ColumnsLayoutCreateGroupRoute,
|
|
||||||
ColumnsLayoutCreateNewsfeedRoute:
|
ColumnsLayoutCreateNewsfeedRoute:
|
||||||
ColumnsLayoutCreateNewsfeedRouteWithChildren,
|
ColumnsLayoutCreateNewsfeedRouteWithChildren,
|
||||||
ColumnsLayoutGalleryRoute: ColumnsLayoutGalleryRoute,
|
|
||||||
ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute,
|
ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute,
|
||||||
ColumnsLayoutGroupRoute: ColumnsLayoutGroupRoute,
|
ColumnsLayoutNewsfeedRoute: ColumnsLayoutNewsfeedRoute,
|
||||||
ColumnsLayoutStoriesRoute: ColumnsLayoutStoriesRoute,
|
ColumnsLayoutStoriesRoute: ColumnsLayoutStoriesRoute,
|
||||||
ColumnsLayoutNewsfeedLazyRoute: ColumnsLayoutNewsfeedLazyRoute,
|
ColumnsLayoutGalleryLazyRoute: ColumnsLayoutGalleryLazyRoute,
|
||||||
ColumnsLayoutNotificationLazyRoute: ColumnsLayoutNotificationLazyRoute,
|
ColumnsLayoutNotificationLazyRoute: ColumnsLayoutNotificationLazyRoute,
|
||||||
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
||||||
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
||||||
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
||||||
|
ColumnsLayoutGroupsIdRoute: ColumnsLayoutGroupsIdRoute,
|
||||||
|
ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute,
|
||||||
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
||||||
ColumnsLayoutHashtagsContentLazyRoute: ColumnsLayoutHashtagsContentLazyRoute,
|
|
||||||
ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute,
|
ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute,
|
||||||
ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute,
|
ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute,
|
||||||
}
|
}
|
||||||
@@ -727,6 +731,8 @@ export interface FileRoutesByFullPath {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||||
'/loading': typeof LoadingRoute
|
'/loading': typeof LoadingRoute
|
||||||
|
'/set-group': typeof SetGroupRoute
|
||||||
|
'/set-interest': typeof SetInterestRoute
|
||||||
'/new': typeof NewLazyRoute
|
'/new': typeof NewLazyRoute
|
||||||
'/reset': typeof ResetLazyRoute
|
'/reset': typeof ResetLazyRoute
|
||||||
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
||||||
@@ -743,21 +749,20 @@ export interface FileRoutesByFullPath {
|
|||||||
'/$account/profile': typeof AccountSettingsProfileRoute
|
'/$account/profile': typeof AccountSettingsProfileRoute
|
||||||
'/$account/relay': typeof AccountSettingsRelayRoute
|
'/$account/relay': typeof AccountSettingsRelayRoute
|
||||||
'/$account/wallet': typeof AccountSettingsWalletRoute
|
'/$account/wallet': typeof AccountSettingsWalletRoute
|
||||||
'/columns/create-group': typeof ColumnsLayoutCreateGroupRoute
|
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/gallery': typeof ColumnsLayoutGalleryRoute
|
|
||||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/columns/group': typeof ColumnsLayoutGroupRoute
|
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute
|
||||||
'/columns/stories': typeof ColumnsLayoutStoriesRoute
|
'/columns/stories': typeof ColumnsLayoutStoriesRoute
|
||||||
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute
|
'/columns/gallery': typeof ColumnsLayoutGalleryLazyRoute
|
||||||
'/columns/notification': typeof ColumnsLayoutNotificationLazyRoute
|
'/columns/notification': typeof ColumnsLayoutNotificationLazyRoute
|
||||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
|
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||||
|
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
'/columns/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute
|
|
||||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
}
|
}
|
||||||
@@ -766,6 +771,8 @@ export interface FileRoutesByTo {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||||
'/loading': typeof LoadingRoute
|
'/loading': typeof LoadingRoute
|
||||||
|
'/set-group': typeof SetGroupRoute
|
||||||
|
'/set-interest': typeof SetInterestRoute
|
||||||
'/new': typeof NewLazyRoute
|
'/new': typeof NewLazyRoute
|
||||||
'/reset': typeof ResetLazyRoute
|
'/reset': typeof ResetLazyRoute
|
||||||
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
||||||
@@ -782,21 +789,20 @@ export interface FileRoutesByTo {
|
|||||||
'/$account/profile': typeof AccountSettingsProfileRoute
|
'/$account/profile': typeof AccountSettingsProfileRoute
|
||||||
'/$account/relay': typeof AccountSettingsRelayRoute
|
'/$account/relay': typeof AccountSettingsRelayRoute
|
||||||
'/$account/wallet': typeof AccountSettingsWalletRoute
|
'/$account/wallet': typeof AccountSettingsWalletRoute
|
||||||
'/columns/create-group': typeof ColumnsLayoutCreateGroupRoute
|
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/gallery': typeof ColumnsLayoutGalleryRoute
|
|
||||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/columns/group': typeof ColumnsLayoutGroupRoute
|
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute
|
||||||
'/columns/stories': typeof ColumnsLayoutStoriesRoute
|
'/columns/stories': typeof ColumnsLayoutStoriesRoute
|
||||||
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute
|
'/columns/gallery': typeof ColumnsLayoutGalleryLazyRoute
|
||||||
'/columns/notification': typeof ColumnsLayoutNotificationLazyRoute
|
'/columns/notification': typeof ColumnsLayoutNotificationLazyRoute
|
||||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
|
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||||
|
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
'/columns/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute
|
|
||||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
}
|
}
|
||||||
@@ -806,6 +812,8 @@ export interface FileRoutesById {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||||
'/loading': typeof LoadingRoute
|
'/loading': typeof LoadingRoute
|
||||||
|
'/set-group': typeof SetGroupRoute
|
||||||
|
'/set-interest': typeof SetInterestRoute
|
||||||
'/new': typeof NewLazyRoute
|
'/new': typeof NewLazyRoute
|
||||||
'/reset': typeof ResetLazyRoute
|
'/reset': typeof ResetLazyRoute
|
||||||
'/$account': typeof AccountRouteWithChildren
|
'/$account': typeof AccountRouteWithChildren
|
||||||
@@ -825,21 +833,20 @@ export interface FileRoutesById {
|
|||||||
'/$account/_settings/profile': typeof AccountSettingsProfileRoute
|
'/$account/_settings/profile': typeof AccountSettingsProfileRoute
|
||||||
'/$account/_settings/relay': typeof AccountSettingsRelayRoute
|
'/$account/_settings/relay': typeof AccountSettingsRelayRoute
|
||||||
'/$account/_settings/wallet': typeof AccountSettingsWalletRoute
|
'/$account/_settings/wallet': typeof AccountSettingsWalletRoute
|
||||||
'/columns/_layout/create-group': typeof ColumnsLayoutCreateGroupRoute
|
|
||||||
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/_layout/gallery': typeof ColumnsLayoutGalleryRoute
|
|
||||||
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/columns/_layout/group': typeof ColumnsLayoutGroupRoute
|
'/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedRoute
|
||||||
'/columns/_layout/stories': typeof ColumnsLayoutStoriesRoute
|
'/columns/_layout/stories': typeof ColumnsLayoutStoriesRoute
|
||||||
'/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute
|
'/columns/_layout/gallery': typeof ColumnsLayoutGalleryLazyRoute
|
||||||
'/columns/_layout/notification': typeof ColumnsLayoutNotificationLazyRoute
|
'/columns/_layout/notification': typeof ColumnsLayoutNotificationLazyRoute
|
||||||
'/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
'/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
'/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute
|
'/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute
|
||||||
'/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
'/columns/_layout/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
'/columns/_layout/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
'/columns/_layout/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
'/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/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
'/columns/_layout/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute
|
|
||||||
'/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
'/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
'/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
'/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
}
|
}
|
||||||
@@ -850,6 +857,8 @@ export interface FileRouteTypes {
|
|||||||
| '/'
|
| '/'
|
||||||
| '/bootstrap-relays'
|
| '/bootstrap-relays'
|
||||||
| '/loading'
|
| '/loading'
|
||||||
|
| '/set-group'
|
||||||
|
| '/set-interest'
|
||||||
| '/new'
|
| '/new'
|
||||||
| '/reset'
|
| '/reset'
|
||||||
| '/$account'
|
| '/$account'
|
||||||
@@ -866,21 +875,20 @@ export interface FileRouteTypes {
|
|||||||
| '/$account/profile'
|
| '/$account/profile'
|
||||||
| '/$account/relay'
|
| '/$account/relay'
|
||||||
| '/$account/wallet'
|
| '/$account/wallet'
|
||||||
| '/columns/create-group'
|
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
| '/columns/gallery'
|
|
||||||
| '/columns/global'
|
| '/columns/global'
|
||||||
| '/columns/group'
|
|
||||||
| '/columns/stories'
|
|
||||||
| '/columns/newsfeed'
|
| '/columns/newsfeed'
|
||||||
|
| '/columns/stories'
|
||||||
|
| '/columns/gallery'
|
||||||
| '/columns/notification'
|
| '/columns/notification'
|
||||||
| '/columns/onboarding'
|
| '/columns/onboarding'
|
||||||
| '/columns/search'
|
| '/columns/search'
|
||||||
| '/columns/trending'
|
| '/columns/trending'
|
||||||
| '/columns/create-newsfeed/f2f'
|
| '/columns/create-newsfeed/f2f'
|
||||||
| '/columns/create-newsfeed/users'
|
| '/columns/create-newsfeed/users'
|
||||||
|
| '/columns/groups/$id'
|
||||||
|
| '/columns/interests/$id'
|
||||||
| '/columns/events/$id'
|
| '/columns/events/$id'
|
||||||
| '/columns/hashtags/$content'
|
|
||||||
| '/columns/replies/$id'
|
| '/columns/replies/$id'
|
||||||
| '/columns/users/$id'
|
| '/columns/users/$id'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
@@ -888,6 +896,8 @@ export interface FileRouteTypes {
|
|||||||
| '/'
|
| '/'
|
||||||
| '/bootstrap-relays'
|
| '/bootstrap-relays'
|
||||||
| '/loading'
|
| '/loading'
|
||||||
|
| '/set-group'
|
||||||
|
| '/set-interest'
|
||||||
| '/new'
|
| '/new'
|
||||||
| '/reset'
|
| '/reset'
|
||||||
| '/$account'
|
| '/$account'
|
||||||
@@ -904,21 +914,20 @@ export interface FileRouteTypes {
|
|||||||
| '/$account/profile'
|
| '/$account/profile'
|
||||||
| '/$account/relay'
|
| '/$account/relay'
|
||||||
| '/$account/wallet'
|
| '/$account/wallet'
|
||||||
| '/columns/create-group'
|
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
| '/columns/gallery'
|
|
||||||
| '/columns/global'
|
| '/columns/global'
|
||||||
| '/columns/group'
|
|
||||||
| '/columns/stories'
|
|
||||||
| '/columns/newsfeed'
|
| '/columns/newsfeed'
|
||||||
|
| '/columns/stories'
|
||||||
|
| '/columns/gallery'
|
||||||
| '/columns/notification'
|
| '/columns/notification'
|
||||||
| '/columns/onboarding'
|
| '/columns/onboarding'
|
||||||
| '/columns/search'
|
| '/columns/search'
|
||||||
| '/columns/trending'
|
| '/columns/trending'
|
||||||
| '/columns/create-newsfeed/f2f'
|
| '/columns/create-newsfeed/f2f'
|
||||||
| '/columns/create-newsfeed/users'
|
| '/columns/create-newsfeed/users'
|
||||||
|
| '/columns/groups/$id'
|
||||||
|
| '/columns/interests/$id'
|
||||||
| '/columns/events/$id'
|
| '/columns/events/$id'
|
||||||
| '/columns/hashtags/$content'
|
|
||||||
| '/columns/replies/$id'
|
| '/columns/replies/$id'
|
||||||
| '/columns/users/$id'
|
| '/columns/users/$id'
|
||||||
id:
|
id:
|
||||||
@@ -926,6 +935,8 @@ export interface FileRouteTypes {
|
|||||||
| '/'
|
| '/'
|
||||||
| '/bootstrap-relays'
|
| '/bootstrap-relays'
|
||||||
| '/loading'
|
| '/loading'
|
||||||
|
| '/set-group'
|
||||||
|
| '/set-interest'
|
||||||
| '/new'
|
| '/new'
|
||||||
| '/reset'
|
| '/reset'
|
||||||
| '/$account'
|
| '/$account'
|
||||||
@@ -945,21 +956,20 @@ export interface FileRouteTypes {
|
|||||||
| '/$account/_settings/profile'
|
| '/$account/_settings/profile'
|
||||||
| '/$account/_settings/relay'
|
| '/$account/_settings/relay'
|
||||||
| '/$account/_settings/wallet'
|
| '/$account/_settings/wallet'
|
||||||
| '/columns/_layout/create-group'
|
|
||||||
| '/columns/_layout/create-newsfeed'
|
| '/columns/_layout/create-newsfeed'
|
||||||
| '/columns/_layout/gallery'
|
|
||||||
| '/columns/_layout/global'
|
| '/columns/_layout/global'
|
||||||
| '/columns/_layout/group'
|
|
||||||
| '/columns/_layout/stories'
|
|
||||||
| '/columns/_layout/newsfeed'
|
| '/columns/_layout/newsfeed'
|
||||||
|
| '/columns/_layout/stories'
|
||||||
|
| '/columns/_layout/gallery'
|
||||||
| '/columns/_layout/notification'
|
| '/columns/_layout/notification'
|
||||||
| '/columns/_layout/onboarding'
|
| '/columns/_layout/onboarding'
|
||||||
| '/columns/_layout/search'
|
| '/columns/_layout/search'
|
||||||
| '/columns/_layout/trending'
|
| '/columns/_layout/trending'
|
||||||
| '/columns/_layout/create-newsfeed/f2f'
|
| '/columns/_layout/create-newsfeed/f2f'
|
||||||
| '/columns/_layout/create-newsfeed/users'
|
| '/columns/_layout/create-newsfeed/users'
|
||||||
|
| '/columns/_layout/groups/$id'
|
||||||
|
| '/columns/_layout/interests/$id'
|
||||||
| '/columns/_layout/events/$id'
|
| '/columns/_layout/events/$id'
|
||||||
| '/columns/_layout/hashtags/$content'
|
|
||||||
| '/columns/_layout/replies/$id'
|
| '/columns/_layout/replies/$id'
|
||||||
| '/columns/_layout/users/$id'
|
| '/columns/_layout/users/$id'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
@@ -969,6 +979,8 @@ export interface RootRouteChildren {
|
|||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
BootstrapRelaysRoute: typeof BootstrapRelaysRoute
|
BootstrapRelaysRoute: typeof BootstrapRelaysRoute
|
||||||
LoadingRoute: typeof LoadingRoute
|
LoadingRoute: typeof LoadingRoute
|
||||||
|
SetGroupRoute: typeof SetGroupRoute
|
||||||
|
SetInterestRoute: typeof SetInterestRoute
|
||||||
NewLazyRoute: typeof NewLazyRoute
|
NewLazyRoute: typeof NewLazyRoute
|
||||||
ResetLazyRoute: typeof ResetLazyRoute
|
ResetLazyRoute: typeof ResetLazyRoute
|
||||||
AccountRoute: typeof AccountRouteWithChildren
|
AccountRoute: typeof AccountRouteWithChildren
|
||||||
@@ -984,6 +996,8 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
BootstrapRelaysRoute: BootstrapRelaysRoute,
|
BootstrapRelaysRoute: BootstrapRelaysRoute,
|
||||||
LoadingRoute: LoadingRoute,
|
LoadingRoute: LoadingRoute,
|
||||||
|
SetGroupRoute: SetGroupRoute,
|
||||||
|
SetInterestRoute: SetInterestRoute,
|
||||||
NewLazyRoute: NewLazyRoute,
|
NewLazyRoute: NewLazyRoute,
|
||||||
ResetLazyRoute: ResetLazyRoute,
|
ResetLazyRoute: ResetLazyRoute,
|
||||||
AccountRoute: AccountRouteWithChildren,
|
AccountRoute: AccountRouteWithChildren,
|
||||||
@@ -1010,6 +1024,8 @@ export const routeTree = rootRoute
|
|||||||
"/",
|
"/",
|
||||||
"/bootstrap-relays",
|
"/bootstrap-relays",
|
||||||
"/loading",
|
"/loading",
|
||||||
|
"/set-group",
|
||||||
|
"/set-interest",
|
||||||
"/new",
|
"/new",
|
||||||
"/reset",
|
"/reset",
|
||||||
"/$account",
|
"/$account",
|
||||||
@@ -1030,6 +1046,12 @@ export const routeTree = rootRoute
|
|||||||
"/loading": {
|
"/loading": {
|
||||||
"filePath": "loading.tsx"
|
"filePath": "loading.tsx"
|
||||||
},
|
},
|
||||||
|
"/set-group": {
|
||||||
|
"filePath": "set-group.tsx"
|
||||||
|
},
|
||||||
|
"/set-interest": {
|
||||||
|
"filePath": "set-interest.tsx"
|
||||||
|
},
|
||||||
"/new": {
|
"/new": {
|
||||||
"filePath": "new.lazy.tsx"
|
"filePath": "new.lazy.tsx"
|
||||||
},
|
},
|
||||||
@@ -1065,19 +1087,18 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout.tsx",
|
"filePath": "columns/_layout.tsx",
|
||||||
"parent": "/columns",
|
"parent": "/columns",
|
||||||
"children": [
|
"children": [
|
||||||
"/columns/_layout/create-group",
|
|
||||||
"/columns/_layout/create-newsfeed",
|
"/columns/_layout/create-newsfeed",
|
||||||
"/columns/_layout/gallery",
|
|
||||||
"/columns/_layout/global",
|
"/columns/_layout/global",
|
||||||
"/columns/_layout/group",
|
|
||||||
"/columns/_layout/stories",
|
|
||||||
"/columns/_layout/newsfeed",
|
"/columns/_layout/newsfeed",
|
||||||
|
"/columns/_layout/stories",
|
||||||
|
"/columns/_layout/gallery",
|
||||||
"/columns/_layout/notification",
|
"/columns/_layout/notification",
|
||||||
"/columns/_layout/onboarding",
|
"/columns/_layout/onboarding",
|
||||||
"/columns/_layout/search",
|
"/columns/_layout/search",
|
||||||
"/columns/_layout/trending",
|
"/columns/_layout/trending",
|
||||||
|
"/columns/_layout/groups/$id",
|
||||||
|
"/columns/_layout/interests/$id",
|
||||||
"/columns/_layout/events/$id",
|
"/columns/_layout/events/$id",
|
||||||
"/columns/_layout/hashtags/$content",
|
|
||||||
"/columns/_layout/replies/$id",
|
"/columns/_layout/replies/$id",
|
||||||
"/columns/_layout/users/$id"
|
"/columns/_layout/users/$id"
|
||||||
]
|
]
|
||||||
@@ -1132,10 +1153,6 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "$account/_settings/wallet.tsx",
|
"filePath": "$account/_settings/wallet.tsx",
|
||||||
"parent": "/$account/_settings"
|
"parent": "/$account/_settings"
|
||||||
},
|
},
|
||||||
"/columns/_layout/create-group": {
|
|
||||||
"filePath": "columns/_layout/create-group.tsx",
|
|
||||||
"parent": "/columns/_layout"
|
|
||||||
},
|
|
||||||
"/columns/_layout/create-newsfeed": {
|
"/columns/_layout/create-newsfeed": {
|
||||||
"filePath": "columns/_layout/create-newsfeed.tsx",
|
"filePath": "columns/_layout/create-newsfeed.tsx",
|
||||||
"parent": "/columns/_layout",
|
"parent": "/columns/_layout",
|
||||||
@@ -1144,24 +1161,20 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/create-newsfeed/users"
|
"/columns/_layout/create-newsfeed/users"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/columns/_layout/gallery": {
|
|
||||||
"filePath": "columns/_layout/gallery.tsx",
|
|
||||||
"parent": "/columns/_layout"
|
|
||||||
},
|
|
||||||
"/columns/_layout/global": {
|
"/columns/_layout/global": {
|
||||||
"filePath": "columns/_layout/global.tsx",
|
"filePath": "columns/_layout/global.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/group": {
|
"/columns/_layout/newsfeed": {
|
||||||
"filePath": "columns/_layout/group.tsx",
|
"filePath": "columns/_layout/newsfeed.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/stories": {
|
"/columns/_layout/stories": {
|
||||||
"filePath": "columns/_layout/stories.tsx",
|
"filePath": "columns/_layout/stories.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/newsfeed": {
|
"/columns/_layout/gallery": {
|
||||||
"filePath": "columns/_layout/newsfeed.lazy.tsx",
|
"filePath": "columns/_layout/gallery.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/notification": {
|
"/columns/_layout/notification": {
|
||||||
@@ -1188,12 +1201,16 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/create-newsfeed.users.tsx",
|
"filePath": "columns/_layout/create-newsfeed.users.tsx",
|
||||||
"parent": "/columns/_layout/create-newsfeed"
|
"parent": "/columns/_layout/create-newsfeed"
|
||||||
},
|
},
|
||||||
"/columns/_layout/events/$id": {
|
"/columns/_layout/groups/$id": {
|
||||||
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
"filePath": "columns/_layout/groups.$id.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/hashtags/$content": {
|
"/columns/_layout/interests/$id": {
|
||||||
"filePath": "columns/_layout/hashtags.$content.lazy.tsx",
|
"filePath": "columns/_layout/interests.$id.tsx",
|
||||||
|
"parent": "/columns/_layout"
|
||||||
|
},
|
||||||
|
"/columns/_layout/events/$id": {
|
||||||
|
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/replies/$id": {
|
"/columns/_layout/replies/$id": {
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
import type { LumeColumn } from "@/types";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
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")({
|
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 };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { appColumns } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { Column } from "@/components/column";
|
import { Column } from "@/components/column";
|
||||||
import { LumeWindow } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import type { ColumnEvent, LumeColumn } from "@/types";
|
import type { ColumnEvent, LumeColumn } from "@/types";
|
||||||
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||||
|
import { resolveResource } from "@tauri-apps/api/path";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||||
import useEmblaCarousel from "embla-carousel-react";
|
import useEmblaCarousel from "embla-carousel-react";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import {
|
import {
|
||||||
@@ -25,9 +28,8 @@ export const Route = createLazyFileRoute("/$account/_app/home")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { initialColumns } = Route.useRouteContext();
|
const columns = useStore(appColumns, (state) => state);
|
||||||
|
|
||||||
const [columns, setColumns] = useState<LumeColumn[]>([]);
|
|
||||||
const [emblaRef, emblaApi] = useEmblaCarousel({
|
const [emblaRef, emblaApi] = useEmblaCarousel({
|
||||||
watchDrag: false,
|
watchDrag: false,
|
||||||
loop: false,
|
loop: false,
|
||||||
@@ -51,11 +53,11 @@ function Screen() {
|
|||||||
|
|
||||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||||
column.label = `${column.label}-${nanoid()}`; // update col label
|
column.label = `${column.label}-${nanoid()}`; // update col label
|
||||||
setColumns((prev) => [column, ...prev]);
|
appColumns.setState((prev) => [column, ...prev]);
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
const remove = useDebouncedCallback((label: string) => {
|
const remove = useDebouncedCallback((label: string) => {
|
||||||
setColumns((prev) => prev.filter((t) => t.label !== label));
|
appColumns.setState((prev) => prev.filter((t) => t.label !== label));
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
const move = useDebouncedCallback(
|
const move = useDebouncedCallback(
|
||||||
@@ -70,12 +72,12 @@ function Screen() {
|
|||||||
if (direction === "left") newCols.splice(colIndex - 1, 0, col);
|
if (direction === "left") newCols.splice(colIndex - 1, 0, col);
|
||||||
if (direction === "right") newCols.splice(colIndex + 1, 0, col);
|
if (direction === "right") newCols.splice(colIndex + 1, 0, col);
|
||||||
|
|
||||||
setColumns(newCols);
|
appColumns.setState(() => newCols);
|
||||||
},
|
},
|
||||||
150,
|
150,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateName = useDebouncedCallback((label: string, title: string) => {
|
const update = useDebouncedCallback((label: string, title: string) => {
|
||||||
const currentColIndex = columns.findIndex((col) => col.label === label);
|
const currentColIndex = columns.findIndex((col) => col.label === label);
|
||||||
|
|
||||||
const updatedCol = Object.assign({}, columns[currentColIndex]);
|
const updatedCol = Object.assign({}, columns[currentColIndex]);
|
||||||
@@ -84,10 +86,10 @@ function Screen() {
|
|||||||
const newCols = columns.slice();
|
const newCols = columns.slice();
|
||||||
newCols[currentColIndex] = updatedCol;
|
newCols[currentColIndex] = updatedCol;
|
||||||
|
|
||||||
setColumns(newCols);
|
appColumns.setState(() => newCols);
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
const reset = useDebouncedCallback(() => setColumns([]), 150);
|
const reset = useDebouncedCallback(() => appColumns.setState(() => []), 150);
|
||||||
|
|
||||||
const handleKeyDown = useDebouncedCallback((event) => {
|
const handleKeyDown = useDebouncedCallback((event) => {
|
||||||
if (event.defaultPrevented) return;
|
if (event.defaultPrevented) return;
|
||||||
@@ -106,18 +108,6 @@ function Screen() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
const saveAllColumns = useDebouncedCallback(async () => {
|
|
||||||
const key = "lume_v4:columns";
|
|
||||||
const content = JSON.stringify(columns);
|
|
||||||
const res = await commands.setLumeStore(key, content);
|
|
||||||
|
|
||||||
if (res.status === "ok") {
|
|
||||||
return res.data;
|
|
||||||
} else {
|
|
||||||
console.log(res.error);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (emblaApi) {
|
if (emblaApi) {
|
||||||
emblaApi.on("scroll", emitScrollEvent);
|
emblaApi.on("scroll", emitScrollEvent);
|
||||||
@@ -132,14 +122,6 @@ function Screen() {
|
|||||||
};
|
};
|
||||||
}, [emblaApi, emitScrollEvent, emitResizeEvent]);
|
}, [emblaApi, emitScrollEvent, emitResizeEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (columns) saveAllColumns();
|
|
||||||
}, [columns]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setColumns(initialColumns);
|
|
||||||
}, [initialColumns]);
|
|
||||||
|
|
||||||
// Listen for keyboard event
|
// Listen for keyboard event
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("keydown", handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
@@ -158,7 +140,7 @@ function Screen() {
|
|||||||
if (data.payload.type === "move")
|
if (data.payload.type === "move")
|
||||||
move(data.payload.label, data.payload.direction);
|
move(data.payload.label, data.payload.direction);
|
||||||
if (data.payload.type === "set_title")
|
if (data.payload.type === "set_title")
|
||||||
updateName(data.payload.label, data.payload.title);
|
update(data.payload.label, data.payload.title);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
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 (
|
return (
|
||||||
<div className="size-full">
|
<div className="size-full">
|
||||||
<div ref={emblaRef} className="overflow-hidden size-full">
|
<div ref={emblaRef} className="overflow-hidden size-full">
|
||||||
|
|||||||
@@ -1,20 +1,3 @@
|
|||||||
import { commands } from "@/commands.gen";
|
|
||||||
import type { LumeColumn } from "@/types";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/$account/_app/home")({
|
export const Route = createFileRoute("/$account/_app/home")();
|
||||||
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 };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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<string[]>([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 (
|
|
||||||
<div className="flex flex-col items-center justify-center w-full h-full gap-4">
|
|
||||||
<div className="flex flex-col items-center justify-center text-center">
|
|
||||||
<h1 className="font-serif text-2xl font-medium">Create a group</h1>
|
|
||||||
<p className="leading-tight text-neutral-700 dark:text-neutral-300">
|
|
||||||
For the people that you want to keep up.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col w-4/5 max-w-full gap-3">
|
|
||||||
<div className="flex items-center w-full rounded-lg h-9 shrink-0 bg-neutral-200 dark:bg-neutral-800">
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="w-16 text-sm font-semibold text-center border-r border-neutral-300 dark:border-neutral-700 shrink-0"
|
|
||||||
>
|
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="name"
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center w-full gap-3">
|
|
||||||
<div className="overflow-y-auto scrollbar-none p-2 w-full h-[450px] flex flex-col gap-3 bg-neutral-200 dark:bg-neutral-900 rounded-xl">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<input
|
|
||||||
name="npub"
|
|
||||||
value={npub}
|
|
||||||
onChange={(e) => 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"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => addUser()}
|
|
||||||
className="inline-flex items-center justify-center text-neutral-500 rounded-lg size-9 bg-neutral-300 dark:bg-neutral-700 shrink-0 hover:bg-blue-500 hover:text-white"
|
|
||||||
>
|
|
||||||
<Plus className="size-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<span className="text-sm font-semibold">Added</span>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{users.length ? (
|
|
||||||
users.map((item: string) => (
|
|
||||||
<button
|
|
||||||
key={item}
|
|
||||||
type="button"
|
|
||||||
onClick={() => toggleUser(item)}
|
|
||||||
className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
|
||||||
>
|
|
||||||
<User.Provider pubkey={item}>
|
|
||||||
<User.Root className="flex items-center gap-2.5">
|
|
||||||
<User.Avatar className="rounded-full size-8" />
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<User.Name className="text-sm font-medium" />
|
|
||||||
</div>
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
<X className="size-4" />
|
|
||||||
</button>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center text-sm rounded-lg bg-neutral-300 dark:bg-neutral-700 h-14">
|
|
||||||
Empty.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<span className="text-sm font-semibold">Contacts</span>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{contacts.length ? (
|
|
||||||
contacts.map((item: string) => (
|
|
||||||
<button
|
|
||||||
key={item}
|
|
||||||
type="button"
|
|
||||||
onClick={() => toggleUser(item)}
|
|
||||||
className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
|
||||||
>
|
|
||||||
<User.Provider pubkey={item}>
|
|
||||||
<User.Root className="flex items-center gap-2.5">
|
|
||||||
<User.Avatar className="rounded-full size-8" />
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<User.Name className="text-sm font-medium" />
|
|
||||||
</div>
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
</button>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center text-sm rounded-lg bg-black/5 dark:bg-white/5 h-14">
|
|
||||||
<p>
|
|
||||||
Find more user at{" "}
|
|
||||||
<a
|
|
||||||
href="https://www.nostr.directory/"
|
|
||||||
target="_blank"
|
|
||||||
className="text-blue-600 after:content-['_↗']"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Nostr Directory
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => submit()}
|
|
||||||
disabled={isPending || users.length < 1}
|
|
||||||
className="inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-full w-36 h-9 hover:bg-blue-600 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{isPending ? <Spinner /> : "Confirm"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
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")({
|
export const Route = createLazyFileRoute("/columns/_layout/gallery")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { columns } = Route.useRouteContext();
|
|
||||||
|
|
||||||
const install = async (column: LumeColumn) => {
|
|
||||||
const mainWindow = getCurrentWindow();
|
|
||||||
await mainWindow.emit("columns", { type: "add", column });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
scrollHideDelay={300}
|
scrollHideDelay={300}
|
||||||
className="overflow-hidden size-full"
|
className="overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport className="relative h-full px-3">
|
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
||||||
{columns.map((column) => (
|
<MyGroups />
|
||||||
<div
|
<MyInterests />
|
||||||
key={column.label}
|
<Core />
|
||||||
className="mb-3 group flex px-4 items-center justify-between h-16 rounded-xl bg-white dark:bg-black border-[.5px] border-neutral-300 dark:border-neutral-700"
|
|
||||||
>
|
|
||||||
<div className="text-sm">
|
|
||||||
<div className="mb-px leading-tight font-semibold">
|
|
||||||
{column.name}
|
|
||||||
</div>
|
|
||||||
<div className="leading-tight text-neutral-500 dark:text-neutral-400">
|
|
||||||
{column.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => install(column)}
|
|
||||||
className="text-xs uppercase font-semibold w-16 h-7 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
|
|
||||||
>
|
|
||||||
Open
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</ScrollArea.Viewport>
|
</ScrollArea.Viewport>
|
||||||
<ScrollArea.Scrollbar
|
<ScrollArea.Scrollbar
|
||||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
@@ -55,3 +36,276 @@ function Screen() {
|
|||||||
</ScrollArea.Root>
|
</ScrollArea.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex items-center justify-between px-2">
|
||||||
|
<h3 className="font-semibold">Core</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data.map((column) => (
|
||||||
|
<div
|
||||||
|
key={column.label}
|
||||||
|
className="group flex px-4 items-center justify-between h-16 rounded-xl bg-white dark:bg-black border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
|
>
|
||||||
|
<div className="text-sm">
|
||||||
|
<div className="mb-px leading-tight font-semibold">
|
||||||
|
{column.name}
|
||||||
|
</div>
|
||||||
|
<div className="leading-tight text-neutral-500 dark:text-neutral-400">
|
||||||
|
{column.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => LumeWindow.openColumn(column)}
|
||||||
|
className="text-xs uppercase font-semibold w-16 h-7 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
|
||||||
|
>
|
||||||
|
Open
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="group flex flex-col rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50 border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
|
>
|
||||||
|
<div className="p-3 h-16 flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||||
|
{item.tags
|
||||||
|
.filter((tag) => tag[0] === "p")
|
||||||
|
.map((tag) => (
|
||||||
|
<div key={tag[1]}>
|
||||||
|
<User.Provider pubkey={tag[1]}>
|
||||||
|
<User.Root>
|
||||||
|
<User.Avatar className="size-8 rounded-full" />
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="p-3 flex items-center justify-between">
|
||||||
|
<div className="text-sm font-medium">{name}</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
LumeWindow.openColumn({
|
||||||
|
label: name,
|
||||||
|
name,
|
||||||
|
url: `/columns/groups/${item.id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-blue-600 hover:bg-blue-500 text-white"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-12 flex flex-col gap-3">
|
||||||
|
<div className="flex items-center justify-between px-2">
|
||||||
|
<h3 className="font-semibold">My groups</h3>
|
||||||
|
<div className="inline-flex items-center justify-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
className="size-7 inline-flex items-center justify-center rounded-full"
|
||||||
|
>
|
||||||
|
<ArrowClockwise className="size-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
LumeWindow.openPopup("New group", `/set-group?account=${account}`)
|
||||||
|
}
|
||||||
|
className="h-7 w-max px-2 inline-flex items-center justify-center gap-1 text-sm font-medium rounded-full bg-neutral-300 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" weight="bold" />
|
||||||
|
New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : !data.length ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||||
|
<p className="text-center">You don't have any groups yet.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data.map((item) => renderItem(item))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="group flex flex-col rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50 border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
|
>
|
||||||
|
<div className="p-3 h-16 flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||||
|
{item.tags
|
||||||
|
.filter((tag) => tag[0] === "t")
|
||||||
|
.map((tag) => (
|
||||||
|
<div key={tag[1]} className="text-sm font-medium">
|
||||||
|
{tag[1]}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="p-3 flex items-center justify-between">
|
||||||
|
<div className="text-sm font-medium">{name}</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
LumeWindow.openColumn({
|
||||||
|
label: name,
|
||||||
|
name,
|
||||||
|
url: `/columns/interests/${item.id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-blue-600 hover:bg-blue-500 text-white"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-12 flex flex-col gap-3">
|
||||||
|
<div className="flex items-center justify-between px-2">
|
||||||
|
<h3 className="font-semibold">My interests</h3>
|
||||||
|
<div className="inline-flex items-center justify-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
className="size-7 inline-flex items-center justify-center rounded-full"
|
||||||
|
>
|
||||||
|
<ArrowClockwise className="size-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
LumeWindow.openPopup(
|
||||||
|
"New interest",
|
||||||
|
`/set-interest?account=${account}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="h-7 w-max px-2 inline-flex items-center justify-center gap-1 text-sm font-medium rounded-full bg-neutral-300 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" weight="bold" />
|
||||||
|
New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : !data.length ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||||
|
<p className="text-center">You don't have any interests yet.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data.map((item) => renderItem(item))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -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",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -7,16 +7,19 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
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";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/group")({
|
export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function Screen() {
|
export function Screen() {
|
||||||
const { label, account } = Route.useSearch();
|
const group = Route.useLoaderData();
|
||||||
const { groups } = Route.useRouteContext();
|
const params = Route.useParams();
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -25,11 +28,11 @@ export function Screen() {
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
} = useInfiniteQuery({
|
} = useInfiniteQuery({
|
||||||
queryKey: [label, account],
|
queryKey: ["groups", params.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
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") {
|
if (res.status === "error") {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
@@ -39,6 +42,7 @@ export function Screen() {
|
|||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
|
enabled: group?.length > 0,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,6 +84,16 @@ export function Screen() {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen("synchronized", async () => {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ["groups", params.id] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
20
src/routes/columns/_layout/groups.$id.tsx
Normal file
20
src/routes/columns/_layout/groups.$id.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
|
import type { NostrEvent } from "@/types";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/columns/_layout/groups/$id")({
|
||||||
|
loader: async ({ params }) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -7,16 +7,19 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
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";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/hashtags/$content")({
|
export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function Screen() {
|
export function Screen() {
|
||||||
const { label, account } = Route.useSearch();
|
const hashtags = Route.useLoaderData();
|
||||||
const { content } = Route.useParams();
|
const params = Route.useParams();
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -25,12 +28,12 @@ export function Screen() {
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
} = useInfiniteQuery({
|
} = useInfiniteQuery({
|
||||||
queryKey: [label, account],
|
queryKey: ["hashtags", params.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
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 until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||||
const res = await commands.getHashtagEvents(hashtags, until);
|
const res = await commands.getAllEventsByHashtags(tags, until);
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
@@ -81,6 +84,18 @@ export function Screen() {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen("synchronized", async () => {
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["hashtags", params.id],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
20
src/routes/columns/_layout/interests.$id.tsx
Normal file
20
src/routes/columns/_layout/interests.$id.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
|
import type { NostrEvent } from "@/types";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/columns/_layout/interests/$id")({
|
||||||
|
loader: async ({ params }) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -6,11 +6,7 @@ import { Kind, type Meta } from "@/types";
|
|||||||
import { ArrowDown, ArrowUp } from "@phosphor-icons/react";
|
import { ArrowDown, ArrowUp } from "@phosphor-icons/react";
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
|
import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import {
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
Navigate,
|
|
||||||
createLazyFileRoute,
|
|
||||||
useLocation,
|
|
||||||
} from "@tanstack/react-router";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import {
|
import {
|
||||||
@@ -33,12 +29,12 @@ export const Route = createLazyFileRoute("/columns/_layout/newsfeed")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function Screen() {
|
export function Screen() {
|
||||||
|
const contacts = Route.useLoaderData();
|
||||||
const { queryClient } = Route.useRouteContext();
|
const { queryClient } = Route.useRouteContext();
|
||||||
const { label, account } = Route.useSearch();
|
const { label, account } = Route.useSearch();
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
|
||||||
isFetching,
|
isFetching,
|
||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
@@ -48,7 +44,7 @@ export function Screen() {
|
|||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||||
const res = await commands.getLocalEvents(until);
|
const res = await commands.getAllEventsByAuthors(contacts, until);
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
@@ -58,9 +54,9 @@ export function Screen() {
|
|||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1,
|
getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1,
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
|
enabled: contacts?.length > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
@@ -109,16 +105,6 @@ export function Screen() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return (
|
|
||||||
<Navigate
|
|
||||||
to="/columns/create-newsfeed/users"
|
|
||||||
// @ts-ignore, tanstack router bug.
|
|
||||||
search={{ label, account, redirect: location.href }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/create-group")({
|
export const Route = createFileRoute("/columns/_layout/newsfeed")({
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
const res = await commands.getContactList();
|
const res = await commands.getContactList();
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { type ReactNode, useEffect, useRef } from "react";
|
import { type ReactNode, useEffect, useMemo, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/notification")({
|
export const Route = createLazyFileRoute("/columns/_layout/notification")({
|
||||||
@@ -180,17 +180,7 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
{events.map((event) => (
|
{events.map((event) => (
|
||||||
<User.Provider
|
<ZapReceipt key={event.id} event={event} />
|
||||||
key={event.id}
|
|
||||||
pubkey={event.tags.find((tag) => tag[0] === "P")[1]}
|
|
||||||
>
|
|
||||||
<User.Root className="shrink-0 flex gap-1.5 rounded-full h-7 bg-black/10 dark:bg-white/10 p-[2px]">
|
|
||||||
<User.Avatar className="rounded-full size-6" />
|
|
||||||
<div className="flex-1 h-6 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-xs font-semibold truncate">
|
|
||||||
₿ {decodeZapInvoice(event.tags).bitcoinFormatted}
|
|
||||||
</div>
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -311,3 +301,36 @@ function TextNote({ event }: { event: LumeEvent }) {
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="shrink-0 flex gap-1.5 rounded-full h-7 bg-black/10 dark:bg-white/10 p-[2px]">
|
||||||
|
<div className="rounded-full size-6 bg-blue-500" />
|
||||||
|
<div className="flex-1 h-6 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-xs font-semibold truncate">
|
||||||
|
₿ {amount}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<User.Provider pubkey={sender}>
|
||||||
|
<User.Root className="shrink-0 flex gap-1.5 rounded-full h-7 bg-black/10 dark:bg-white/10 p-[2px]">
|
||||||
|
<User.Avatar className="rounded-full size-6" />
|
||||||
|
<div className="flex-1 h-6 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-xs font-semibold truncate">
|
||||||
|
₿ {amount}
|
||||||
|
</div>
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function StoryItem({ contact }: { contact: string }) {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["stories", contact],
|
queryKey: ["stories", contact],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await commands.getEventsBy(contact, 10);
|
const res = await commands.getAllEventsByAuthor(contact, 10);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const data = toLumeEvents(res.data);
|
const data = toLumeEvents(res.data);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function Screen() {
|
|||||||
const { isLoading, data: events } = useQuery({
|
const { isLoading, data: events } = useQuery({
|
||||||
queryKey: ["stories", id],
|
queryKey: ["stories", id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await commands.getEventsBy(id, 100);
|
const res = await commands.getAllEventsByAuthor(id, 100);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const data = toLumeEvents(res.data);
|
const data = toLumeEvents(res.data);
|
||||||
|
|||||||
@@ -21,25 +21,21 @@ function Screen() {
|
|||||||
href="/auth/connect"
|
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"
|
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"
|
||||||
>
|
>
|
||||||
<h3 className="mb-1.5 font-medium">Continue with Nostr Connect</h3>
|
<h3 className="mb-1 font-medium">Continue with Nostr Connect</h3>
|
||||||
<div className="text-sm">
|
<p className="text-xs text-neutral-500 dark:text-neutral-600">
|
||||||
<p className="text-neutral-500 dark:text-neutral-600">
|
Your account will be handled by a remote signer. Lume will not
|
||||||
Your account will be handled by a remote signer. Lume will not
|
store your account keys.
|
||||||
store your account keys.
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="/auth/import"
|
href="/auth/import"
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
<h3 className="mb-1.5 font-medium">Continue with Secret Key</h3>
|
<h3 className="mb-1 font-medium">Continue with Secret Key</h3>
|
||||||
<div className="text-sm">
|
<p className="text-xs text-neutral-500 dark:text-neutral-600">
|
||||||
<p className="text-neutral-500 dark:text-neutral-600">
|
Lume will store your keys in secure storage. You can provide a
|
||||||
Lume will store your keys in secure storage. You can provide a
|
password to add extra security.
|
||||||
password to add extra security.
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex-1 h-px bg-black/5 dark:bg-white/5" />
|
<div className="flex-1 h-px bg-black/5 dark:bg-white/5" />
|
||||||
|
|||||||
205
src/routes/set-group.lazy.tsx
Normal file
205
src/routes/set-group.lazy.tsx
Normal file
@@ -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<string[]>([]);
|
||||||
|
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 (
|
||||||
|
<div className="flex flex-col size-full">
|
||||||
|
<div data-tauri-drag-region className="shrink-0 h-11" />
|
||||||
|
<div className="shrink-0 h-14 px-3 flex items-center gap-2 justify-between border-b border-black/5 dark:border-white/5">
|
||||||
|
<div className="flex items-center flex-1 rounded-lg h-9 shrink-0 bg-black/10 dark:bg-white/10">
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="w-16 text-sm font-semibold text-center border-r border-neutral-300 dark:border-neutral-700 shrink-0"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="name"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => submit()}
|
||||||
|
disabled={isPending || users.length < 1}
|
||||||
|
className="shrink-0 inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-lg w-20 h-9 hover:bg-blue-600 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner /> : "Create"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<ScrollArea.Root
|
||||||
|
type={"scroll"}
|
||||||
|
scrollHideDelay={300}
|
||||||
|
className="flex-1 overflow-hidden"
|
||||||
|
>
|
||||||
|
<ScrollArea.Viewport className="bg-white dark:bg-black h-full p-3">
|
||||||
|
<div className="mb-3 flex flex-col gap-2">
|
||||||
|
<h3 className="text-sm font-semibold">Added</h3>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
name="npub"
|
||||||
|
value={npub}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => addUser()}
|
||||||
|
className="inline-flex items-center justify-center text-neutral-500 rounded-lg size-9 bg-neutral-200 dark:bg-neutral-800 shrink-0 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
<Plus className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{users.length ? (
|
||||||
|
users.map((item: string) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleUser(item)}
|
||||||
|
className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||||
|
>
|
||||||
|
<User.Provider pubkey={item}>
|
||||||
|
<User.Root className="flex items-center gap-2.5">
|
||||||
|
<User.Avatar className="rounded-full size-8" />
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<User.Name className="text-sm font-medium" />
|
||||||
|
</div>
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
<X className="size-4" />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center text-sm rounded-lg bg-neutral-100 dark:bg-neutral-900 h-14">
|
||||||
|
Please add some user to your group.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h3 className="text-sm font-semibold">Contacts</h3>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{contacts.length ? (
|
||||||
|
contacts
|
||||||
|
.filter((c) => !users.includes(c))
|
||||||
|
.map((item: string) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleUser(item)}
|
||||||
|
className="inline-flex items-center justify-between px-3 py-2 rounded-lg border-[.5px] border-neutral-300 dark:border-neutral-700 hover:border-blue-500"
|
||||||
|
>
|
||||||
|
<User.Provider pubkey={item}>
|
||||||
|
<User.Root className="flex items-center gap-2.5">
|
||||||
|
<User.Avatar className="rounded-full size-8" />
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<User.Name className="text-sm font-medium" />
|
||||||
|
</div>
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center text-sm rounded-lg bg-black/5 dark:bg-white/5 h-14">
|
||||||
|
<p>
|
||||||
|
Find more user at{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.nostr.directory/"
|
||||||
|
target="_blank"
|
||||||
|
className="text-blue-600 after:content-['_↗']"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Nostr Directory
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar
|
||||||
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
|
orientation="vertical"
|
||||||
|
>
|
||||||
|
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
<ScrollArea.Corner className="bg-transparent" />
|
||||||
|
</ScrollArea.Root>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/routes/set-group.tsx
Normal file
23
src/routes/set-group.tsx
Normal file
@@ -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<string, string>): 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
198
src/routes/set-interest.lazy.tsx
Normal file
198
src/routes/set-interest.lazy.tsx
Normal file
@@ -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<string[]>([]);
|
||||||
|
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 (
|
||||||
|
<div className="flex flex-col size-full">
|
||||||
|
<div data-tauri-drag-region className="shrink-0 h-11" />
|
||||||
|
<div className="shrink-0 h-14 px-3 flex items-center gap-2 justify-between border-b border-black/5 dark:border-white/5">
|
||||||
|
<div className="flex items-center flex-1 rounded-lg h-9 shrink-0 bg-black/10 dark:bg-white/10">
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="w-16 text-sm font-semibold text-center border-r border-neutral-300 dark:border-neutral-700 shrink-0"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="name"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => submit()}
|
||||||
|
className="shrink-0 inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-lg w-20 h-9 hover:bg-blue-600 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner /> : "Create"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<ScrollArea.Root
|
||||||
|
type={"scroll"}
|
||||||
|
scrollHideDelay={300}
|
||||||
|
className="flex-1 overflow-hidden"
|
||||||
|
>
|
||||||
|
<ScrollArea.Viewport className="bg-white dark:bg-black h-full p-3">
|
||||||
|
<div className="mb-3 flex flex-col gap-2">
|
||||||
|
<span className="text-sm font-semibold">Added</span>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{hashtags.length ? (
|
||||||
|
hashtags.map((item: string) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleHashtag(item)}
|
||||||
|
className="inline-flex items-center justify-between p-3 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||||
|
>
|
||||||
|
<p>{item}</p>
|
||||||
|
<X className="size-4" />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center text-sm rounded-lg bg-neutral-100 dark:bg-neutral-900 h-14">
|
||||||
|
Please add some hashtag.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-sm font-semibold">Hashtags</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
name="hashtag"
|
||||||
|
placeholder="#nostr"
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => addHashtag()}
|
||||||
|
className="inline-flex items-center justify-center text-neutral-500 rounded-lg size-9 bg-neutral-200 dark:bg-neutral-800 shrink-0 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
<Plus className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex flex-col gap-4">
|
||||||
|
{TOPICS.map((topic) => (
|
||||||
|
<div key={topic.title} className="flex flex-col gap-2">
|
||||||
|
<div className="text-sm font-semibold">{topic.title}</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{topic.content.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleHashtag(item)}
|
||||||
|
className={cn(
|
||||||
|
"text-sm p-2 rounded-full",
|
||||||
|
hashtags.includes(item)
|
||||||
|
? "bg-blue-500 text-white"
|
||||||
|
: "bg-neutral-100 dark:bg-neutral-900",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar
|
||||||
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
|
orientation="vertical"
|
||||||
|
>
|
||||||
|
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
<ScrollArea.Corner className="bg-transparent" />
|
||||||
|
</ScrollArea.Root>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/routes/set-interest.tsx
Normal file
13
src/routes/set-interest.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
type RouteSearch = {
|
||||||
|
account: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/set-interest")({
|
||||||
|
validateSearch: (search: Record<string, string>): RouteSearch => {
|
||||||
|
return {
|
||||||
|
account: search.account,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import type { LumeColumn, NostrEvent } from "@/types";
|
import type { LumeColumn, NostrEvent } from "@/types";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import type { LumeEvent } from "./event";
|
import type { LumeEvent } from "./event";
|
||||||
|
|
||||||
export const LumeWindow = {
|
export const LumeWindow = {
|
||||||
@@ -150,8 +151,22 @@ export const LumeWindow = {
|
|||||||
throw new Error(query.error);
|
throw new Error(query.error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openMainWindow: async () => {
|
openPopup: async (title: string, url: string) => {
|
||||||
const query = await commands.reopenLume();
|
const query = await commands.openWindow({
|
||||||
return query;
|
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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user