feat: negentropy progress
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"@radix-ui/react-avatar": "^1.1.1",
|
"@radix-ui/react-avatar": "^1.1.1",
|
||||||
"@radix-ui/react-checkbox": "^1.1.2",
|
"@radix-ui/react-checkbox": "^1.1.2",
|
||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||||
"@radix-ui/react-switch": "^1.1.1",
|
"@radix-ui/react-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
|
|||||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
|||||||
'@radix-ui/react-popover':
|
'@radix-ui/react-popover':
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
version: 1.1.2(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
||||||
|
'@radix-ui/react-progress':
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
||||||
'@radix-ui/react-scroll-area':
|
'@radix-ui/react-scroll-area':
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
version: 1.2.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
||||||
@@ -974,6 +977,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-progress@1.1.0':
|
||||||
|
resolution: {integrity: sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.0':
|
'@radix-ui/react-roving-focus@1.1.0':
|
||||||
resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
|
resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3044,6 +3060,16 @@ snapshots:
|
|||||||
'@types/react': types-react@19.0.0-rc.1
|
'@types/react': types-react@19.0.0-rc.1
|
||||||
'@types/react-dom': types-react-dom@19.0.0-rc.1
|
'@types/react-dom': types-react-dom@19.0.0-rc.1
|
||||||
|
|
||||||
|
'@radix-ui/react-progress@1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-context': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)
|
||||||
|
'@radix-ui/react-primitive': 2.0.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
||||||
|
react: 19.0.0-rc-d025ddd3-20240722
|
||||||
|
react-dom: 19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': types-react@19.0.0-rc.1
|
||||||
|
'@types/react-dom': types-react-dom@19.0.0-rc.1
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)':
|
'@radix-ui/react-roving-focus@1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.0
|
'@radix-ui/primitive': 1.1.0
|
||||||
|
|||||||
84
src-tauri/Cargo.lock
generated
84
src-tauri/Cargo.lock
generated
@@ -44,6 +44,7 @@ dependencies = [
|
|||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"tauri-specta",
|
"tauri-specta",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3479,7 +3480,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -3509,7 +3510,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
@@ -3523,7 +3524,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heed",
|
"heed",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3536,7 +3537,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3554,7 +3555,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"atomic-destructor",
|
"atomic-destructor",
|
||||||
@@ -3574,7 +3575,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-signer"
|
name = "nostr-signer"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3587,7 +3588,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-zapper"
|
name = "nostr-zapper"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3607,6 +3608,16 @@ dependencies = [
|
|||||||
"zbus 4.4.0",
|
"zbus 4.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -3721,7 +3732,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nwc"
|
name = "nwc"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a8114e090b333e6ca50fb80d8afc1ced4229ee33"
|
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3993,6 +4004,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "page_size"
|
name = "page_size"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -5316,6 +5333,15 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "share-picker"
|
name = "share-picker"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -6331,6 +6357,16 @@ dependencies = [
|
|||||||
"syn 2.0.79",
|
"syn 2.0.79",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -6606,6 +6642,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||||
|
dependencies = [
|
||||||
|
"nu-ansi-term",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6813,6 +6875,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ linkify = "0.10.0"
|
|||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
||||||
keyring-search = "1.2.0"
|
keyring-search = "1.2.0"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
wss://relay.damus.io,
|
wss://relay.damus.io,
|
||||||
wss://relay.nostr.net,
|
wss://relay.nostr.net,
|
||||||
|
wss://relay.primal.net,
|
||||||
wss://nostr.fmt.wiz.biz,
|
wss://nostr.fmt.wiz.biz,
|
||||||
wss://offchain.pub,
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use async_utility::thread::sleep;
|
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::{fs, str::FromStr, time::Duration};
|
use std::{str::FromStr, time::Duration};
|
||||||
use tauri::{Emitter, Manager, State};
|
use tauri::{Emitter, State};
|
||||||
|
|
||||||
use crate::{common::get_all_accounts, Nostr};
|
use crate::{common::get_all_accounts, Nostr};
|
||||||
|
|
||||||
use super::sync::sync_account;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
struct Account {
|
struct Account {
|
||||||
secret_key: String,
|
secret_key: String,
|
||||||
@@ -24,11 +21,8 @@ pub fn get_accounts() -> Vec<String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn watch_account(
|
pub async fn watch_account(id: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
id: String,
|
let client = &state.client;
|
||||||
state: State<'_, Nostr>,
|
|
||||||
app_handle: tauri::AppHandle,
|
|
||||||
) -> Result<String, String> {
|
|
||||||
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
||||||
let npub = public_key.to_bech32().map_err(|e| e.to_string())?;
|
let npub = public_key.to_bech32().map_err(|e| e.to_string())?;
|
||||||
let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?;
|
||||||
@@ -36,14 +30,14 @@ pub async fn watch_account(
|
|||||||
// Set empty password
|
// Set empty password
|
||||||
keyring.set_password("").map_err(|e| e.to_string())?;
|
keyring.set_password("").map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// Run sync for this account
|
|
||||||
sync_account(public_key, app_handle);
|
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.accounts.lock().unwrap().push(npub.clone());
|
let mut accounts = state.accounts.lock().unwrap().clone();
|
||||||
|
accounts.push(npub.clone());
|
||||||
|
|
||||||
// Fake loading
|
// Get user's profile
|
||||||
sleep(Duration::from_secs(4)).await;
|
let _ = client
|
||||||
|
.fetch_metadata(public_key, Some(Duration::from_secs(4)))
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(npub)
|
Ok(npub)
|
||||||
}
|
}
|
||||||
@@ -54,7 +48,6 @@ pub async fn import_account(
|
|||||||
key: String,
|
key: String,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
app_handle: tauri::AppHandle,
|
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
@@ -87,25 +80,21 @@ pub async fn import_account(
|
|||||||
// Update signer
|
// Update signer
|
||||||
client.set_signer(Some(signer)).await;
|
client.set_signer(Some(signer)).await;
|
||||||
|
|
||||||
// Run sync for this account
|
|
||||||
sync_account(public_key, app_handle);
|
|
||||||
|
|
||||||
// Fake loading
|
|
||||||
sleep(Duration::from_secs(4)).await;
|
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.accounts.lock().unwrap().push(npub.clone());
|
let mut accounts = state.accounts.lock().unwrap().clone();
|
||||||
|
accounts.push(npub.clone());
|
||||||
|
|
||||||
|
// Get user's profile
|
||||||
|
let _ = client
|
||||||
|
.fetch_metadata(public_key, Some(Duration::from_secs(4)))
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(npub)
|
Ok(npub)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn connect_account(
|
pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
uri: String,
|
|
||||||
state: State<'_, Nostr>,
|
|
||||||
app_handle: tauri::AppHandle,
|
|
||||||
) -> Result<String, String> {
|
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
match NostrConnectURI::parse(uri.clone()) {
|
match NostrConnectURI::parse(uri.clone()) {
|
||||||
@@ -118,9 +107,6 @@ pub async fn connect_account(
|
|||||||
let remote_user = bunker_uri.signer_public_key().unwrap();
|
let remote_user = bunker_uri.signer_public_key().unwrap();
|
||||||
let remote_npub = remote_user.to_bech32().unwrap();
|
let remote_npub = remote_user.to_bech32().unwrap();
|
||||||
|
|
||||||
// Run sync for this account
|
|
||||||
sync_account(remote_user, app_handle);
|
|
||||||
|
|
||||||
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None) {
|
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None) {
|
||||||
Ok(signer) => {
|
Ok(signer) => {
|
||||||
let mut url = Url::parse(&uri).unwrap();
|
let mut url = Url::parse(&uri).unwrap();
|
||||||
@@ -146,7 +132,13 @@ pub async fn connect_account(
|
|||||||
let _ = client.set_signer(Some(signer.into())).await;
|
let _ = client.set_signer(Some(signer.into())).await;
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.accounts.lock().unwrap().push(remote_npub.clone());
|
let mut accounts = state.accounts.lock().unwrap().clone();
|
||||||
|
accounts.push(remote_npub.clone());
|
||||||
|
|
||||||
|
// Get user's profile
|
||||||
|
let _ = client
|
||||||
|
.fetch_metadata(remote_user, Some(Duration::from_secs(4)))
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(remote_npub)
|
Ok(remote_npub)
|
||||||
}
|
}
|
||||||
@@ -198,24 +190,6 @@ pub fn delete_account(id: String) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub async fn is_new_account(id: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
|
|
||||||
let config_dir = app_handle.path().config_dir().map_err(|e| e.to_string())?;
|
|
||||||
let exist = fs::metadata(config_dir.join(id)).is_ok();
|
|
||||||
|
|
||||||
Ok(!exist)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub async fn toggle_new_account(id: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
|
||||||
let config_dir = app_handle.path().config_dir().map_err(|e| e.to_string())?;
|
|
||||||
fs::File::create(config_dir.join(id)).unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use futures::future::join_all;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::{str::FromStr, time::Duration};
|
use std::{str::FromStr, time::Duration};
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
use crate::common::{create_tags, get_latest_event, parse_event, process_event, Meta};
|
use crate::common::{create_tags, parse_event, process_event, Meta};
|
||||||
use crate::{Nostr, DEFAULT_DIFFICULTY, FETCH_LIMIT};
|
use crate::{Nostr, DEFAULT_DIFFICULTY, FETCH_LIMIT};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Type)]
|
#[derive(Debug, Clone, Serialize, Type)]
|
||||||
@@ -14,23 +13,15 @@ pub struct RichEvent {
|
|||||||
pub parsed: Option<Meta>,
|
pub parsed: Option<Meta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub async fn get_event_meta(content: String) -> Result<Meta, ()> {
|
|
||||||
let meta = parse_event(&content).await;
|
|
||||||
Ok(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent, String> {
|
pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
let event_id = EventId::from_str(&id).map_err(|err| err.to_string())?;
|
||||||
let filter = Filter::new().id(event_id);
|
|
||||||
|
|
||||||
match client.database().query(vec![filter.clone()]).await {
|
match client.database().event_by_id(&event_id).await {
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
if let Some(event) = get_latest_event(&events) {
|
if let Some(event) = events {
|
||||||
let raw = event.as_json();
|
let raw = event.as_json();
|
||||||
let parsed = if event.kind == Kind::TextNote {
|
let parsed = if event.kind == Kind::TextNote {
|
||||||
Some(parse_event(&event.content).await)
|
Some(parse_event(&event.content).await)
|
||||||
@@ -40,26 +31,7 @@ pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent,
|
|||||||
|
|
||||||
Ok(RichEvent { raw, parsed })
|
Ok(RichEvent { raw, parsed })
|
||||||
} else {
|
} else {
|
||||||
match client
|
Err("Event not found".to_string())
|
||||||
.fetch_events(vec![filter], Some(Duration::from_secs(10)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(events) => {
|
|
||||||
if let Some(event) = get_latest_event(&events) {
|
|
||||||
let raw = event.as_json();
|
|
||||||
let parsed = if event.kind == Kind::TextNote {
|
|
||||||
Some(parse_event(&event.content).await)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(RichEvent { raw, parsed })
|
|
||||||
} else {
|
|
||||||
Err("Not found.".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => Err(err.to_string()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
@@ -68,35 +40,8 @@ pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent,
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_event_from(
|
pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
|
||||||
id: String,
|
Ok(parse_event(&content).await)
|
||||||
_relay_hint: String,
|
|
||||||
state: State<'_, Nostr>,
|
|
||||||
) -> Result<RichEvent, String> {
|
|
||||||
let client = &state.client;
|
|
||||||
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
|
||||||
let filter = Filter::new().id(event_id);
|
|
||||||
|
|
||||||
match client
|
|
||||||
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(events) => {
|
|
||||||
if let Some(event) = events.first() {
|
|
||||||
let raw = event.as_json();
|
|
||||||
let parsed = if event.kind == Kind::TextNote {
|
|
||||||
Some(parse_event(&event.content).await)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(RichEvent { raw, parsed })
|
|
||||||
} else {
|
|
||||||
Err("Cannot found this event with current relay list".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => Err(err.to_string()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -110,21 +55,7 @@ pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<Rich
|
|||||||
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(events) => {
|
Ok(events) => Ok(process_event(client, events).await),
|
||||||
let futures = events.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()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ pub async fn set_group(
|
|||||||
.authors(public_keys)
|
.authors(public_keys)
|
||||||
.limit(500);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(report) = client.sync(filter, NegentropyOptions::default()).await {
|
if let Ok(report) = client.sync(filter, SyncOptions::default()).await {
|
||||||
println!("Received: {}", report.received.len());
|
println!("Received: {}", report.received.len());
|
||||||
handle.emit("synchronized", ()).unwrap();
|
handle.emit("synchronized", ()).unwrap();
|
||||||
};
|
};
|
||||||
@@ -331,7 +331,7 @@ pub async fn set_interest(
|
|||||||
.hashtags(hashtags)
|
.hashtags(hashtags)
|
||||||
.limit(500);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(report) = client.sync(filter, NegentropyOptions::default()).await {
|
if let Ok(report) = client.sync(filter, SyncOptions::default()).await {
|
||||||
println!("Received: {}", report.received.len());
|
println!("Received: {}", report.received.len());
|
||||||
handle.emit("synchronized", ()).unwrap();
|
handle.emit("synchronized", ()).unwrap();
|
||||||
};
|
};
|
||||||
@@ -560,15 +560,12 @@ pub async fn get_notifications(id: String, state: State<'_, Nostr>) -> Result<Ve
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new().pubkey(public_key).kinds(vec![
|
||||||
.pubkey(public_key)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::TextNote,
|
Kind::TextNote,
|
||||||
Kind::Repost,
|
Kind::Repost,
|
||||||
Kind::Reaction,
|
Kind::Reaction,
|
||||||
Kind::ZapReceipt,
|
Kind::ZapReceipt,
|
||||||
])
|
]);
|
||||||
.limit(200);
|
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
|
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::str::FromStr;
|
use std::{
|
||||||
use tauri::{AppHandle, Manager};
|
fs::{self, File},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use tauri::{ipc::Channel, AppHandle, Manager, State};
|
||||||
use tauri_specta::Event as TauriEvent;
|
use tauri_specta::Event as TauriEvent;
|
||||||
|
|
||||||
use crate::Nostr;
|
use crate::Nostr;
|
||||||
@@ -45,70 +48,19 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let bootstrap_relays = state.bootstrap_relays.lock().unwrap().clone();
|
let bootstrap_relays = state.bootstrap_relays.lock().unwrap().clone();
|
||||||
|
|
||||||
// NEG: Sync metadata
|
|
||||||
//
|
|
||||||
let metadata = Filter::new().authors(public_keys.clone()).kinds(vec![
|
|
||||||
Kind::Metadata,
|
|
||||||
Kind::ContactList,
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::EventDeletion,
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Custom(30315),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if let Ok(report) = client
|
|
||||||
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Others,
|
|
||||||
total_event: report.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEG: Sync notification
|
|
||||||
//
|
|
||||||
let notification = Filter::new()
|
|
||||||
.pubkeys(public_keys)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Reaction,
|
|
||||||
Kind::ZapReceipt,
|
|
||||||
])
|
|
||||||
.limit(5000);
|
|
||||||
|
|
||||||
if let Ok(report) = client
|
|
||||||
.sync_with(
|
|
||||||
&bootstrap_relays,
|
|
||||||
notification,
|
|
||||||
NegentropyOptions::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Notification,
|
|
||||||
total_event: report.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEG: Sync events for all pubkeys in local database
|
// NEG: Sync events for all pubkeys in local database
|
||||||
//
|
//
|
||||||
let pubkey_filter = Filter::new().kinds(vec![
|
if let Ok(events) = client
|
||||||
|
.database()
|
||||||
|
.query(vec![Filter::new().kinds(vec![
|
||||||
Kind::ContactList,
|
Kind::ContactList,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::MuteList,
|
||||||
Kind::Repost,
|
Kind::Repost,
|
||||||
Kind::TextNote,
|
Kind::TextNote,
|
||||||
Kind::FollowSet,
|
])])
|
||||||
]);
|
.await
|
||||||
|
{
|
||||||
if let Ok(events) = client.database().query(vec![pubkey_filter]).await {
|
|
||||||
let pubkeys: Vec<PublicKey> = events
|
let pubkeys: Vec<PublicKey> = events
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|ev| ev.tags.public_keys().copied())
|
.flat_map(|ev| ev.tags.public_keys().copied())
|
||||||
@@ -126,15 +78,15 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
let events = Filter::new()
|
let events = Filter::new()
|
||||||
.authors(authors.clone())
|
.authors(authors.clone())
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(5000);
|
.limit(1000);
|
||||||
|
|
||||||
if let Ok(report) = client
|
if let Ok(output) = client
|
||||||
.sync_with(&bootstrap_relays, events, NegentropyOptions::default())
|
.sync_with(&bootstrap_relays, events, SyncOptions::default())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
NegentropyEvent {
|
NegentropyEvent {
|
||||||
kind: NegentropyKind::Events,
|
kind: NegentropyKind::Events,
|
||||||
total_event: report.received.len() as i32,
|
total_event: output.received.len() as i32,
|
||||||
}
|
}
|
||||||
.emit(&app_handle)
|
.emit(&app_handle)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -144,124 +96,35 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
//
|
//
|
||||||
let metadata = Filter::new()
|
let metadata = Filter::new()
|
||||||
.authors(authors)
|
.authors(authors)
|
||||||
.kinds(vec![Kind::Metadata, Kind::ContactList]);
|
.kinds(vec![
|
||||||
|
|
||||||
if let Ok(report) = client
|
|
||||||
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Metadata,
|
|
||||||
total_event: report.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_account(public_key: PublicKey, app_handle: AppHandle) {
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
let state = app_handle.state::<Nostr>();
|
|
||||||
let client = &state.client;
|
|
||||||
let bootstrap_relays = state.bootstrap_relays.lock().unwrap().clone();
|
|
||||||
|
|
||||||
// NEG: Sync all user's metadata
|
|
||||||
//
|
|
||||||
let metadata = Filter::new().author(public_key).kinds(vec![
|
|
||||||
Kind::Metadata,
|
Kind::Metadata,
|
||||||
Kind::ContactList,
|
Kind::ContactList,
|
||||||
Kind::Interests,
|
Kind::Interests,
|
||||||
Kind::InterestSet,
|
Kind::InterestSet,
|
||||||
Kind::FollowSet,
|
Kind::FollowSet,
|
||||||
Kind::RelayList,
|
Kind::MuteList,
|
||||||
Kind::RelaySet,
|
Kind::RelaySet,
|
||||||
Kind::EventDeletion,
|
|
||||||
Kind::Custom(30315),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if let Ok(report) = client
|
|
||||||
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Metadata,
|
|
||||||
total_event: report.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(contact_list) = client.database().contacts_public_keys(public_key).await {
|
|
||||||
// NEG: Sync all contact's metadata
|
|
||||||
//
|
|
||||||
let metadata = Filter::new()
|
|
||||||
.authors(contact_list.clone())
|
|
||||||
.kinds(vec![Kind::Metadata, Kind::RelaySet, Kind::Custom(30315)])
|
|
||||||
.limit(1000);
|
|
||||||
|
|
||||||
if let Ok(report) = client
|
|
||||||
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Metadata,
|
|
||||||
total_event: report.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEG: Sync all contact's events
|
|
||||||
//
|
|
||||||
let metadata = Filter::new()
|
|
||||||
.authors(contact_list.clone())
|
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
|
||||||
.limit(1000);
|
|
||||||
|
|
||||||
if let Ok(report) = client
|
|
||||||
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Events,
|
|
||||||
total_event: report.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEG: Sync all contact's other metadata
|
|
||||||
//
|
|
||||||
let metadata = Filter::new()
|
|
||||||
.authors(contact_list)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::EventDeletion,
|
|
||||||
])
|
])
|
||||||
.limit(1000);
|
.limit(1000);
|
||||||
|
|
||||||
if let Ok(report) = client
|
if let Ok(output) = client
|
||||||
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
|
.sync_with(&bootstrap_relays, metadata, SyncOptions::default())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
NegentropyEvent {
|
NegentropyEvent {
|
||||||
kind: NegentropyKind::Metadata,
|
kind: NegentropyKind::Metadata,
|
||||||
total_event: report.received.len() as i32,
|
total_event: output.received.len() as i32,
|
||||||
}
|
}
|
||||||
.emit(&app_handle)
|
.emit(&app_handle)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NEG: Sync all user's metadata
|
// NEG: Sync notification
|
||||||
//
|
//
|
||||||
let notification = Filter::new()
|
let notification = Filter::new()
|
||||||
.pubkey(public_key)
|
.pubkeys(public_keys.clone())
|
||||||
.kinds(vec![
|
.kinds(vec![
|
||||||
Kind::TextNote,
|
Kind::TextNote,
|
||||||
Kind::Repost,
|
Kind::Repost,
|
||||||
@@ -270,20 +133,188 @@ pub fn sync_account(public_key: PublicKey, app_handle: AppHandle) {
|
|||||||
])
|
])
|
||||||
.limit(500);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(report) = client
|
if let Ok(output) = client
|
||||||
.sync_with(
|
.sync_with(&bootstrap_relays, notification, SyncOptions::default())
|
||||||
&bootstrap_relays,
|
|
||||||
notification,
|
|
||||||
NegentropyOptions::default(),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
NegentropyEvent {
|
NegentropyEvent {
|
||||||
kind: NegentropyKind::Notification,
|
kind: NegentropyKind::Notification,
|
||||||
total_event: report.received.len() as i32,
|
total_event: output.received.len() as i32,
|
||||||
|
}
|
||||||
|
.emit(&app_handle)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEG: Sync metadata
|
||||||
|
//
|
||||||
|
let metadata = Filter::new().authors(public_keys.clone()).kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::RelayList,
|
||||||
|
Kind::MuteList,
|
||||||
|
Kind::EventDeletion,
|
||||||
|
Kind::Bookmarks,
|
||||||
|
Kind::BookmarkSet,
|
||||||
|
Kind::Emojis,
|
||||||
|
Kind::EmojiSet,
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Custom(30315),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if let Ok(output) = client
|
||||||
|
.sync_with(&bootstrap_relays, metadata, SyncOptions::default())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
NegentropyEvent {
|
||||||
|
kind: NegentropyKind::Others,
|
||||||
|
total_event: output.received.len() as i32,
|
||||||
}
|
}
|
||||||
.emit(&app_handle)
|
.emit(&app_handle)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub fn is_account_sync(id: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
|
||||||
|
let config_dir = app_handle
|
||||||
|
.path()
|
||||||
|
.app_config_dir()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let exist = fs::metadata(config_dir.join(id)).is_ok();
|
||||||
|
|
||||||
|
Ok(exist)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn sync_account(
|
||||||
|
id: String,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
reader: Channel<f64>,
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let bootstrap_relays = state.bootstrap_relays.lock().unwrap().clone();
|
||||||
|
|
||||||
|
let public_key = PublicKey::from_bech32(&id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let filter = Filter::new().author(public_key).kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::RelayList,
|
||||||
|
Kind::MuteList,
|
||||||
|
Kind::EventDeletion,
|
||||||
|
Kind::Bookmarks,
|
||||||
|
Kind::BookmarkSet,
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Custom(30315),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let (tx, mut rx) = SyncProgress::channel();
|
||||||
|
let opts = SyncOptions::default().progress(tx);
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
while (rx.changed().await).is_ok() {
|
||||||
|
let SyncProgress { total, current } = *rx.borrow_and_update();
|
||||||
|
|
||||||
|
if total > 0 {
|
||||||
|
reader
|
||||||
|
.send((current as f64 / total as f64) * 100.0)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Ok(output) = client
|
||||||
|
.sync_with(&bootstrap_relays, filter, opts.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Success: {:?}", output.success);
|
||||||
|
println!("Failed: {:?}", output.failed);
|
||||||
|
|
||||||
|
let event_pubkeys = client
|
||||||
|
.database()
|
||||||
|
.query(vec![Filter::new().kinds(vec![
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::MuteList,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::TextNote,
|
||||||
|
])])
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if !event_pubkeys.is_empty() {
|
||||||
|
let pubkeys: Vec<PublicKey> = event_pubkeys
|
||||||
|
.iter()
|
||||||
|
.flat_map(|ev| ev.tags.public_keys().copied())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.authors(pubkeys)
|
||||||
|
.kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::EventDeletion,
|
||||||
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::RelayList,
|
||||||
|
Kind::MuteList,
|
||||||
|
Kind::EventDeletion,
|
||||||
|
Kind::Bookmarks,
|
||||||
|
Kind::BookmarkSet,
|
||||||
|
Kind::Custom(30315),
|
||||||
|
])
|
||||||
|
.limit(10000);
|
||||||
|
|
||||||
|
if let Ok(output) = client
|
||||||
|
.sync_with(&bootstrap_relays, filter, opts.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Success: {:?}", output.success);
|
||||||
|
println!("Failed: {:?}", output.failed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let event_ids = client
|
||||||
|
.database()
|
||||||
|
.query(vec![Filter::new().kinds(vec![
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Bookmarks,
|
||||||
|
Kind::BookmarkSet,
|
||||||
|
])])
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if !event_ids.is_empty() {
|
||||||
|
let ids: Vec<EventId> = event_ids.iter().map(|ev| ev.id).collect();
|
||||||
|
let filter = Filter::new().events(ids);
|
||||||
|
|
||||||
|
if let Ok(output) = client.sync_with(&bootstrap_relays, filter, opts).await {
|
||||||
|
println!("Success: {:?}", output.success);
|
||||||
|
println!("Failed: {:?}", output.failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_dir = app_handle
|
||||||
|
.path()
|
||||||
|
.app_config_dir()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let _ = File::create(config_dir.join(id));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,14 +34,12 @@ const NOSTR_EVENTS: [&str; 10] = [
|
|||||||
"Nostr:nevent1",
|
"Nostr:nevent1",
|
||||||
];
|
];
|
||||||
|
|
||||||
const NOSTR_MENTIONS: [&str; 10] = [
|
const NOSTR_MENTIONS: [&str; 8] = [
|
||||||
"@npub1",
|
"@npub1",
|
||||||
"nostr:npub1",
|
"nostr:npub1",
|
||||||
"nostr:nprofile1",
|
"nostr:nprofile1",
|
||||||
"nostr:naddr1",
|
|
||||||
"npub1",
|
"npub1",
|
||||||
"nprofile1",
|
"nprofile1",
|
||||||
"naddr1",
|
|
||||||
"Nostr:npub1",
|
"Nostr:npub1",
|
||||||
"Nostr:nprofile1",
|
"Nostr:nprofile1",
|
||||||
"Nostr:naddr1",
|
"Nostr:naddr1",
|
||||||
|
|||||||
@@ -5,14 +5,7 @@
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
||||||
use commands::{
|
use commands::{account::*, event::*, metadata::*, relay::*, sync::*, window::*};
|
||||||
account::*,
|
|
||||||
event::*,
|
|
||||||
metadata::*,
|
|
||||||
relay::*,
|
|
||||||
sync::{sync_all, NegentropyEvent},
|
|
||||||
window::*,
|
|
||||||
};
|
|
||||||
use common::{get_all_accounts, parse_event};
|
use common::{get_all_accounts, parse_event};
|
||||||
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -97,8 +90,12 @@ pub const FETCH_LIMIT: usize = 50;
|
|||||||
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let builder = Builder::<tauri::Wry>::new()
|
let builder = Builder::<tauri::Wry>::new()
|
||||||
.commands(collect_commands![
|
.commands(collect_commands![
|
||||||
|
sync_account,
|
||||||
|
is_account_sync,
|
||||||
get_relays,
|
get_relays,
|
||||||
connect_relay,
|
connect_relay,
|
||||||
remove_relay,
|
remove_relay,
|
||||||
@@ -111,8 +108,6 @@ fn main() {
|
|||||||
get_private_key,
|
get_private_key,
|
||||||
delete_account,
|
delete_account,
|
||||||
reset_password,
|
reset_password,
|
||||||
is_new_account,
|
|
||||||
toggle_new_account,
|
|
||||||
has_signer,
|
has_signer,
|
||||||
set_signer,
|
set_signer,
|
||||||
get_profile,
|
get_profile,
|
||||||
@@ -138,9 +133,8 @@ fn main() {
|
|||||||
get_user_settings,
|
get_user_settings,
|
||||||
set_user_settings,
|
set_user_settings,
|
||||||
verify_nip05,
|
verify_nip05,
|
||||||
get_event_meta,
|
get_meta_from_event,
|
||||||
get_event,
|
get_event,
|
||||||
get_event_from,
|
|
||||||
get_replies,
|
get_replies,
|
||||||
subscribe_to,
|
subscribe_to,
|
||||||
get_all_events_by_author,
|
get_all_events_by_author,
|
||||||
|
|||||||
@@ -5,6 +5,22 @@
|
|||||||
|
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
|
async syncAccount(id: string, reader: TAURI_CHANNEL<number>) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("sync_account", { id, reader }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async isAccountSync(id: string) : Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("is_account_sync", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async getRelays(id: string) : Promise<Result<Relays, string>> {
|
async getRelays(id: string) : Promise<Result<Relays, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) };
|
||||||
@@ -96,22 +112,6 @@ async resetPassword(key: string, password: string) : Promise<Result<null, string
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async isNewAccount(id: string) : Promise<Result<boolean, string>> {
|
|
||||||
try {
|
|
||||||
return { status: "ok", data: await TAURI_INVOKE("is_new_account", { id }) };
|
|
||||||
} catch (e) {
|
|
||||||
if(e instanceof Error) throw e;
|
|
||||||
else return { status: "error", error: e as any };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async toggleNewAccount(id: string) : Promise<Result<null, string>> {
|
|
||||||
try {
|
|
||||||
return { status: "ok", data: await TAURI_INVOKE("toggle_new_account", { id }) };
|
|
||||||
} catch (e) {
|
|
||||||
if(e instanceof Error) throw e;
|
|
||||||
else return { status: "error", error: e as any };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async hasSigner(id: string) : Promise<Result<boolean, string>> {
|
async hasSigner(id: string) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("has_signer", { id }) };
|
return { status: "ok", data: await TAURI_INVOKE("has_signer", { id }) };
|
||||||
@@ -312,9 +312,9 @@ async verifyNip05(id: string, nip05: string) : Promise<Result<boolean, string>>
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getEventMeta(content: string) : Promise<Result<Meta, null>> {
|
async getMetaFromEvent(content: string) : Promise<Result<Meta, null>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_event_meta", { content }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_meta_from_event", { content }) };
|
||||||
} 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 };
|
||||||
@@ -328,14 +328,6 @@ async getEvent(id: string) : Promise<Result<RichEvent, string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getEventFrom(id: string, relayHint: string) : Promise<Result<RichEvent, string>> {
|
|
||||||
try {
|
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_event_from", { id, relayHint }) };
|
|
||||||
} catch (e) {
|
|
||||||
if(e instanceof Error) throw e;
|
|
||||||
else return { status: "error", error: e as any };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getReplies(id: string) : Promise<Result<RichEvent[], string>> {
|
async getReplies(id: string) : Promise<Result<RichEvent[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_replies", { id }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_replies", { id }) };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { appSettings, cn } from "@/commons";
|
import { appSettings, cn } from "@/commons";
|
||||||
import { useStore } from "@tanstack/react-store";
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { type ReactNode, memo, useMemo, useState } from "react";
|
import { type ReactNode, useMemo, useState } from "react";
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { Hashtag } from "./mentions/hashtag";
|
import { Hashtag } from "./mentions/hashtag";
|
||||||
import { MentionNote } from "./mentions/note";
|
import { MentionNote } from "./mentions/note";
|
||||||
@@ -22,7 +22,6 @@ export function NoteContent({
|
|||||||
}) {
|
}) {
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
const visible = useStore(appSettings, (state) => state.display_media);
|
const visible = useStore(appSettings, (state) => state.display_media);
|
||||||
const warning = useMemo(() => event.warning, [event]);
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
// Get parsed meta
|
// Get parsed meta
|
||||||
@@ -88,9 +87,14 @@ export function NoteContent({
|
|||||||
}
|
}
|
||||||
}, [event.content]);
|
}, [event.content]);
|
||||||
|
|
||||||
|
const [blurred, setBlurred] = useState(() =>
|
||||||
|
event.warning ? event.warning.length > 0 : false,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col gap-2">
|
<div className="relative flex flex-col gap-2">
|
||||||
<ContentWarning warning={warning} />
|
{!blurred ? (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"select-text text-pretty content-break overflow-hidden",
|
"select-text text-pretty content-break overflow-hidden",
|
||||||
@@ -104,36 +108,27 @@ export function NoteContent({
|
|||||||
<Images urls={event.meta.images} />
|
<Images urls={event.meta.images} />
|
||||||
) : null
|
) : null
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</>
|
||||||
);
|
) : (
|
||||||
}
|
<div
|
||||||
|
className={cn(
|
||||||
const ContentWarning = memo(function ContentWarning({
|
"select-text text-pretty content-break overflow-hidden",
|
||||||
warning,
|
className,
|
||||||
}: { warning: string }) {
|
)}
|
||||||
const [blurred, setBlurred] = useState(() => warning?.length > 0);
|
>
|
||||||
|
<p className="text-yellow-600 dark:text-yellow-400">
|
||||||
if (!blurred) {
|
The content is hidden because the author marked it with a warning
|
||||||
return null;
|
for a reason: <span className="font-semibold">{event.warning}</span>
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="absolute inset-0 z-10 flex items-center justify-center w-full bg-black/80 backdrop-blur-lg">
|
|
||||||
<div className="flex flex-col items-center justify-center gap-2 text-center">
|
|
||||||
<p className="text-sm text-white/60">
|
|
||||||
The content is hidden because the author
|
|
||||||
<br />
|
|
||||||
marked it with a warning for a reason:
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm font-medium text-white">{warning}</p>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setBlurred(false)}
|
onClick={() => setBlurred(false)}
|
||||||
className="inline-flex items-center justify-center px-2 mt-4 text-sm font-medium border rounded-lg text-white/70 h-9 w-max bg-white/20 hover:bg-white/30 border-white/5"
|
className="font-medium text-sm text-blue-500 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
View anyway
|
View anyway
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ export const MentionNote = memo(function MentionNote({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative my-2">
|
<div className="relative my-2">
|
||||||
<div className="min-h-[64px] pl-3 before:content-[''] before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:border-l-[2px] before:border-black/10 dark:before:border-white/10">
|
<div className="pl-3 before:content-[''] before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:border-l-[2px] before:border-black/10 dark:before:border-white/10">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="h-[64px] flex items-center">
|
<div className="h-[32px] flex items-center gap-2 text-sm">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
Loadng note
|
||||||
</div>
|
</div>
|
||||||
) : isError || !event ? (
|
) : isError || !event ? (
|
||||||
<div className="h-[64px] flex items-center">
|
<div className="flex flex-col break-all">
|
||||||
<p className="text-sm font-medium text-red-500">
|
<p className="text-sm font-medium text-red-500">
|
||||||
{error.message || "Note can be found with your current relay set"}
|
{error?.message ??
|
||||||
|
"Cannot found this note within your current relay set"}
|
||||||
</p>
|
</p>
|
||||||
|
<p className="text-sm">{eventId}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Note.Provider event={event}>
|
<Note.Provider event={event}>
|
||||||
|
|||||||
@@ -39,10 +39,6 @@ export function ImagePreview({ url }: { url: string }) {
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
style={{ contentVisibility: "auto" }}
|
style={{ contentVisibility: "auto" }}
|
||||||
className="max-h-[400px] w-full h-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
className="max-h-[400px] w-full h-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||||
onError={({ currentTarget }) => {
|
|
||||||
currentTarget.onerror = null;
|
|
||||||
currentTarget.src = "/404.jpg";
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,9 +21,15 @@ export function Images({ urls }: { urls: string[] }) {
|
|||||||
let newUrls: string[];
|
let newUrls: string[];
|
||||||
|
|
||||||
if (urls.length === 1) {
|
if (urls.length === 1) {
|
||||||
newUrls = urls.map(
|
newUrls = urls.map((url) => {
|
||||||
(url) => `${service}?url=${url}&ll&af&default=1&n=-1`,
|
if (url.includes("_next/")) {
|
||||||
);
|
return url;
|
||||||
|
}
|
||||||
|
if (url.includes("bsky.network")) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return `${service}?url=${url}&ll&af&default=1&n=-1`;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
newUrls = urls.map(
|
newUrls = urls.map(
|
||||||
(url) => `${service}?url=${url}&w=480&h=640&ll&af&default=1&n=-1`,
|
(url) => `${service}?url=${url}&w=480&h=640&ll&af&default=1&n=-1`,
|
||||||
@@ -83,10 +89,6 @@ export function Images({ urls }: { urls: string[] }) {
|
|||||||
className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||||
onClick={() => urls[0]}
|
onClick={() => urls[0]}
|
||||||
onKeyDown={() => urls[0]}
|
onKeyDown={() => urls[0]}
|
||||||
onError={({ currentTarget }) => {
|
|
||||||
currentTarget.onerror = null;
|
|
||||||
currentTarget.src = "/404.jpg";
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -162,10 +164,6 @@ function LazyImage({ url, inView }: { url: string; inView: boolean }) {
|
|||||||
onClick={() => open(url)}
|
onClick={() => open(url)}
|
||||||
onKeyDown={() => open(url)}
|
onKeyDown={() => open(url)}
|
||||||
onLoad={setLoaded}
|
onLoad={setLoaded}
|
||||||
onError={({ currentTarget }) => {
|
|
||||||
currentTarget.onerror = null;
|
|
||||||
currentTarget.src = "/404.jpg";
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export function UserAvatar({ className }: { className?: string }) {
|
|||||||
if (user.profile?.picture.includes("_next/")) {
|
if (user.profile?.picture.includes("_next/")) {
|
||||||
return user.profile?.picture;
|
return user.profile?.picture;
|
||||||
}
|
}
|
||||||
|
if (user.profile?.picture.includes("bsky.network")) {
|
||||||
|
return user.profile?.picture;
|
||||||
|
}
|
||||||
return `${service}?url=${user.profile?.picture}&w=100&h=100&n=-1&default=${user.profile?.picture}`;
|
return `${service}?url=${user.profile?.picture}&w=100&h=100&n=-1&default=${user.profile?.picture}`;
|
||||||
} else {
|
} else {
|
||||||
return user.profile?.picture;
|
return user.profile?.picture;
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ const ColumnsLayoutSearchLazyImport = createFileRoute(
|
|||||||
const ColumnsLayoutOnboardingLazyImport = createFileRoute(
|
const ColumnsLayoutOnboardingLazyImport = createFileRoute(
|
||||||
'/columns/_layout/onboarding',
|
'/columns/_layout/onboarding',
|
||||||
)()
|
)()
|
||||||
const ColumnsLayoutLaunchpadLazyImport = createFileRoute(
|
|
||||||
'/columns/_layout/launchpad',
|
|
||||||
)()
|
|
||||||
const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/users/$id',
|
'/columns/_layout/users/$id',
|
||||||
)()
|
)()
|
||||||
@@ -63,6 +60,9 @@ const ColumnsLayoutRepliesIdLazyImport = createFileRoute(
|
|||||||
const ColumnsLayoutNotificationIdLazyImport = createFileRoute(
|
const ColumnsLayoutNotificationIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/notification/$id',
|
'/columns/_layout/notification/$id',
|
||||||
)()
|
)()
|
||||||
|
const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute(
|
||||||
|
'/columns/_layout/launchpad/$id',
|
||||||
|
)()
|
||||||
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/events/$id',
|
'/columns/_layout/events/$id',
|
||||||
)()
|
)()
|
||||||
@@ -171,15 +171,6 @@ const ColumnsLayoutOnboardingLazyRoute =
|
|||||||
import('./routes/columns/_layout/onboarding.lazy').then((d) => d.Route),
|
import('./routes/columns/_layout/onboarding.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
const ColumnsLayoutLaunchpadLazyRoute = ColumnsLayoutLaunchpadLazyImport.update(
|
|
||||||
{
|
|
||||||
path: '/launchpad',
|
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
|
||||||
} as any,
|
|
||||||
).lazy(() =>
|
|
||||||
import('./routes/columns/_layout/launchpad.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const SettingsIdWalletRoute = SettingsIdWalletImport.update({
|
const SettingsIdWalletRoute = SettingsIdWalletImport.update({
|
||||||
path: '/wallet',
|
path: '/wallet',
|
||||||
getParentRoute: () => SettingsIdLazyRoute,
|
getParentRoute: () => SettingsIdLazyRoute,
|
||||||
@@ -245,6 +236,14 @@ const ColumnsLayoutNotificationIdLazyRoute =
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ColumnsLayoutLaunchpadIdLazyRoute =
|
||||||
|
ColumnsLayoutLaunchpadIdLazyImport.update({
|
||||||
|
path: '/launchpad/$id',
|
||||||
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/columns/_layout/launchpad.$id.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
||||||
path: '/events/$id',
|
path: '/events/$id',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
@@ -436,13 +435,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SettingsIdWalletImport
|
preLoaderRoute: typeof SettingsIdWalletImport
|
||||||
parentRoute: typeof SettingsIdLazyImport
|
parentRoute: typeof SettingsIdLazyImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/launchpad': {
|
|
||||||
id: '/columns/_layout/launchpad'
|
|
||||||
path: '/launchpad'
|
|
||||||
fullPath: '/columns/launchpad'
|
|
||||||
preLoaderRoute: typeof ColumnsLayoutLaunchpadLazyImport
|
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
|
||||||
}
|
|
||||||
'/columns/_layout/onboarding': {
|
'/columns/_layout/onboarding': {
|
||||||
id: '/columns/_layout/onboarding'
|
id: '/columns/_layout/onboarding'
|
||||||
path: '/onboarding'
|
path: '/onboarding'
|
||||||
@@ -513,6 +505,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
|
'/columns/_layout/launchpad/$id': {
|
||||||
|
id: '/columns/_layout/launchpad/$id'
|
||||||
|
path: '/launchpad/$id'
|
||||||
|
fullPath: '/columns/launchpad/$id'
|
||||||
|
preLoaderRoute: typeof ColumnsLayoutLaunchpadIdLazyImport
|
||||||
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
|
}
|
||||||
'/columns/_layout/notification/$id': {
|
'/columns/_layout/notification/$id': {
|
||||||
id: '/columns/_layout/notification/$id'
|
id: '/columns/_layout/notification/$id'
|
||||||
path: '/notification/$id'
|
path: '/notification/$id'
|
||||||
@@ -569,7 +568,6 @@ const ColumnsLayoutCreateNewsfeedRouteWithChildren =
|
|||||||
interface ColumnsLayoutRouteChildren {
|
interface ColumnsLayoutRouteChildren {
|
||||||
ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute
|
ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute
|
||||||
ColumnsLayoutLaunchpadLazyRoute: typeof ColumnsLayoutLaunchpadLazyRoute
|
|
||||||
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
||||||
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
||||||
@@ -578,6 +576,7 @@ interface ColumnsLayoutRouteChildren {
|
|||||||
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
||||||
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
|
ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute
|
ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute
|
ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
@@ -587,7 +586,6 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
|||||||
ColumnsLayoutCreateNewsfeedRoute:
|
ColumnsLayoutCreateNewsfeedRoute:
|
||||||
ColumnsLayoutCreateNewsfeedRouteWithChildren,
|
ColumnsLayoutCreateNewsfeedRouteWithChildren,
|
||||||
ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute,
|
ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute,
|
||||||
ColumnsLayoutLaunchpadLazyRoute: ColumnsLayoutLaunchpadLazyRoute,
|
|
||||||
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
||||||
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
||||||
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
||||||
@@ -596,6 +594,7 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
|||||||
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
||||||
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
||||||
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
||||||
|
ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute,
|
||||||
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
||||||
ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute,
|
ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute,
|
||||||
ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute,
|
ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute,
|
||||||
@@ -654,7 +653,6 @@ export interface FileRoutesByFullPath {
|
|||||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
||||||
'/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute
|
|
||||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
@@ -665,6 +663,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
|
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
@@ -689,7 +688,6 @@ export interface FileRoutesByTo {
|
|||||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
||||||
'/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute
|
|
||||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
@@ -700,6 +698,7 @@ export interface FileRoutesByTo {
|
|||||||
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
|
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
@@ -727,7 +726,6 @@ export interface FileRoutesById {
|
|||||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
||||||
'/columns/_layout/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute
|
|
||||||
'/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
|
||||||
@@ -738,6 +736,7 @@ export interface FileRoutesById {
|
|||||||
'/columns/_layout/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
'/columns/_layout/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
'/columns/_layout/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
'/columns/_layout/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||||
'/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||||
|
'/columns/_layout/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
'/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
'/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
'/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
'/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||||
'/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
'/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||||
@@ -765,7 +764,6 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/$id/profile'
|
| '/settings/$id/profile'
|
||||||
| '/settings/$id/relay'
|
| '/settings/$id/relay'
|
||||||
| '/settings/$id/wallet'
|
| '/settings/$id/wallet'
|
||||||
| '/columns/launchpad'
|
|
||||||
| '/columns/onboarding'
|
| '/columns/onboarding'
|
||||||
| '/columns/search'
|
| '/columns/search'
|
||||||
| '/columns/trending'
|
| '/columns/trending'
|
||||||
@@ -776,6 +774,7 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/newsfeed/$id'
|
| '/columns/newsfeed/$id'
|
||||||
| '/columns/stories/$id'
|
| '/columns/stories/$id'
|
||||||
| '/columns/events/$id'
|
| '/columns/events/$id'
|
||||||
|
| '/columns/launchpad/$id'
|
||||||
| '/columns/notification/$id'
|
| '/columns/notification/$id'
|
||||||
| '/columns/replies/$id'
|
| '/columns/replies/$id'
|
||||||
| '/columns/users/$id'
|
| '/columns/users/$id'
|
||||||
@@ -799,7 +798,6 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/$id/profile'
|
| '/settings/$id/profile'
|
||||||
| '/settings/$id/relay'
|
| '/settings/$id/relay'
|
||||||
| '/settings/$id/wallet'
|
| '/settings/$id/wallet'
|
||||||
| '/columns/launchpad'
|
|
||||||
| '/columns/onboarding'
|
| '/columns/onboarding'
|
||||||
| '/columns/search'
|
| '/columns/search'
|
||||||
| '/columns/trending'
|
| '/columns/trending'
|
||||||
@@ -810,6 +808,7 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/newsfeed/$id'
|
| '/columns/newsfeed/$id'
|
||||||
| '/columns/stories/$id'
|
| '/columns/stories/$id'
|
||||||
| '/columns/events/$id'
|
| '/columns/events/$id'
|
||||||
|
| '/columns/launchpad/$id'
|
||||||
| '/columns/notification/$id'
|
| '/columns/notification/$id'
|
||||||
| '/columns/replies/$id'
|
| '/columns/replies/$id'
|
||||||
| '/columns/users/$id'
|
| '/columns/users/$id'
|
||||||
@@ -835,7 +834,6 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/$id/profile'
|
| '/settings/$id/profile'
|
||||||
| '/settings/$id/relay'
|
| '/settings/$id/relay'
|
||||||
| '/settings/$id/wallet'
|
| '/settings/$id/wallet'
|
||||||
| '/columns/_layout/launchpad'
|
|
||||||
| '/columns/_layout/onboarding'
|
| '/columns/_layout/onboarding'
|
||||||
| '/columns/_layout/search'
|
| '/columns/_layout/search'
|
||||||
| '/columns/_layout/trending'
|
| '/columns/_layout/trending'
|
||||||
@@ -846,6 +844,7 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/_layout/newsfeed/$id'
|
| '/columns/_layout/newsfeed/$id'
|
||||||
| '/columns/_layout/stories/$id'
|
| '/columns/_layout/stories/$id'
|
||||||
| '/columns/_layout/events/$id'
|
| '/columns/_layout/events/$id'
|
||||||
|
| '/columns/_layout/launchpad/$id'
|
||||||
| '/columns/_layout/notification/$id'
|
| '/columns/_layout/notification/$id'
|
||||||
| '/columns/_layout/replies/$id'
|
| '/columns/_layout/replies/$id'
|
||||||
| '/columns/_layout/users/$id'
|
| '/columns/_layout/users/$id'
|
||||||
@@ -938,7 +937,6 @@ export const routeTree = rootRoute
|
|||||||
"children": [
|
"children": [
|
||||||
"/columns/_layout/create-newsfeed",
|
"/columns/_layout/create-newsfeed",
|
||||||
"/columns/_layout/global",
|
"/columns/_layout/global",
|
||||||
"/columns/_layout/launchpad",
|
|
||||||
"/columns/_layout/onboarding",
|
"/columns/_layout/onboarding",
|
||||||
"/columns/_layout/search",
|
"/columns/_layout/search",
|
||||||
"/columns/_layout/trending",
|
"/columns/_layout/trending",
|
||||||
@@ -947,6 +945,7 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/newsfeed/$id",
|
"/columns/_layout/newsfeed/$id",
|
||||||
"/columns/_layout/stories/$id",
|
"/columns/_layout/stories/$id",
|
||||||
"/columns/_layout/events/$id",
|
"/columns/_layout/events/$id",
|
||||||
|
"/columns/_layout/launchpad/$id",
|
||||||
"/columns/_layout/notification/$id",
|
"/columns/_layout/notification/$id",
|
||||||
"/columns/_layout/replies/$id",
|
"/columns/_layout/replies/$id",
|
||||||
"/columns/_layout/users/$id"
|
"/columns/_layout/users/$id"
|
||||||
@@ -1008,10 +1007,6 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "settings.$id/wallet.tsx",
|
"filePath": "settings.$id/wallet.tsx",
|
||||||
"parent": "/settings/$id"
|
"parent": "/settings/$id"
|
||||||
},
|
},
|
||||||
"/columns/_layout/launchpad": {
|
|
||||||
"filePath": "columns/_layout/launchpad.lazy.tsx",
|
|
||||||
"parent": "/columns/_layout"
|
|
||||||
},
|
|
||||||
"/columns/_layout/onboarding": {
|
"/columns/_layout/onboarding": {
|
||||||
"filePath": "columns/_layout/onboarding.lazy.tsx",
|
"filePath": "columns/_layout/onboarding.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
@@ -1052,6 +1047,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
|
"/columns/_layout/launchpad/$id": {
|
||||||
|
"filePath": "columns/_layout/launchpad.$id.lazy.tsx",
|
||||||
|
"parent": "/columns/_layout"
|
||||||
|
},
|
||||||
"/columns/_layout/notification/$id": {
|
"/columns/_layout/notification/$id": {
|
||||||
"filePath": "columns/_layout/notification.$id.lazy.tsx",
|
"filePath": "columns/_layout/notification.$id.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ function Topbar() {
|
|||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="relative z-[200] flex-1 flex items-center justify-end gap-4"
|
className="relative z-[200] flex-1 flex items-center justify-end gap-4"
|
||||||
>
|
>
|
||||||
{accounts?.length ? (
|
|
||||||
<div className="inline-flex items-center gap-2">
|
<div className="inline-flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -78,8 +77,7 @@ function Topbar() {
|
|||||||
<MagnifyingGlass className="size-4" />
|
<MagnifyingGlass className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
<div id="toolbar" className="inline-flex items-center gap-1" />
|
||||||
<div id="toolbar" className="inline-flex items-center gap-2" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { appColumns } from "@/commons";
|
import { commands } from "@/commands.gen";
|
||||||
|
import { appColumns, displayNpub } from "@/commons";
|
||||||
import { Column, Spinner } from "@/components";
|
import { Column, Spinner } from "@/components";
|
||||||
import { LumeWindow } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import type { ColumnEvent, LumeColumn } from "@/types";
|
import type { ColumnEvent, LumeColumn, Metadata } from "@/types";
|
||||||
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
import { ArrowLeft, ArrowRight, Plus } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useStore } from "@tanstack/react-store";
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import useEmblaCarousel from "embla-carousel-react";
|
import useEmblaCarousel from "embla-carousel-react";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import {
|
import {
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -45,12 +46,15 @@ function Screen() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||||
column.label = `${column.label}-${nanoid()}`; // update col label
|
const exist = columns.find((col) => col.label === column.label);
|
||||||
|
|
||||||
|
if (!exist) {
|
||||||
appColumns.setState((prev) => [column, ...prev]);
|
appColumns.setState((prev) => [column, ...prev]);
|
||||||
|
|
||||||
if (emblaApi) {
|
if (emblaApi) {
|
||||||
emblaApi.scrollTo(0, true);
|
emblaApi.scrollTo(0, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
const remove = useDebouncedCallback((label: string) => {
|
const remove = useDebouncedCallback((label: string) => {
|
||||||
@@ -141,47 +145,79 @@ function Screen() {
|
|||||||
<Column key={column.label} column={column} />
|
<Column key={column.label} column={column} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
<div className="shrink-0 p-2 h-full w-[440px]">
|
<OpenLaunchpad />
|
||||||
<div className="size-full flex items-center justify-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openLaunchpad()}
|
|
||||||
className="inline-flex items-center justify-center gap-1 rounded-full text-sm font-medium h-8 w-max pl-2 pr-3 bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<Plus className="size-4" />
|
|
||||||
Add Column
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openLaunchpad()}
|
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
|
||||||
>
|
|
||||||
<StackPlus className="size-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => scrollPrev()}
|
onClick={() => scrollPrev()}
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="size-4" />
|
<ArrowLeft className="size-4" weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => scrollNext()}
|
onClick={() => scrollNext()}
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<ArrowRight className="size-4" />
|
<ArrowRight className="size-4" weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OpenLaunchpad() {
|
||||||
|
const { accounts } = Route.useRouteContext();
|
||||||
|
|
||||||
|
const showContextMenu = useCallback(
|
||||||
|
async (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const list: Promise<MenuItem>[] = [];
|
||||||
|
|
||||||
|
for (const account of accounts) {
|
||||||
|
const res = await commands.getProfile(account);
|
||||||
|
let name = "unknown";
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
const profile: Metadata = JSON.parse(res.data);
|
||||||
|
name = profile.display_name ?? profile.name ?? "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(
|
||||||
|
MenuItem.new({
|
||||||
|
text: `Open Launchpad for ${name} (${displayNpub(account, 16)})`,
|
||||||
|
action: () => LumeWindow.openLaunchpad(account),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await Promise.all(list);
|
||||||
|
const menu = await Menu.new({ items });
|
||||||
|
|
||||||
|
await menu.popup().catch((e) => console.error(e));
|
||||||
|
},
|
||||||
|
[accounts],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="shrink-0 p-2 h-full w-[440px]">
|
||||||
|
<div className="size-full flex items-center justify-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => showContextMenu(e)}
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full text-sm font-medium h-8 w-max pl-2.5 pr-3 bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" weight="bold" />
|
||||||
|
Add Column
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Toolbar({ children }: { children: ReactNode[] }) {
|
function Toolbar({ children }: { children: ReactNode[] }) {
|
||||||
const [domReady, setDomReady] = useState(false);
|
const [domReady, setDomReady] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
import type { LumeColumn } from '@/types'
|
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 { nanoid } from "nanoid";
|
||||||
import { readTextFile } from '@tauri-apps/plugin-fs'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/_app/')({
|
export const Route = createFileRoute("/_app/")({
|
||||||
loader: async ({ context }) => {
|
loader: async ({ context }) => {
|
||||||
const prevColumns = window.localStorage.getItem('columns')
|
const accounts = context.accounts;
|
||||||
|
const prevColumns = window.localStorage.getItem("columns");
|
||||||
|
|
||||||
if (!prevColumns) {
|
let initialAppColumns: LumeColumn[] = [];
|
||||||
const resourcePath = await resolveResource('resources/columns.json')
|
|
||||||
const resourceFile = await readTextFile(resourcePath)
|
|
||||||
const content: LumeColumn[] = JSON.parse(resourceFile)
|
|
||||||
const initialAppColumns = content.filter((col) => col.default)
|
|
||||||
|
|
||||||
return initialAppColumns
|
if (!prevColumns || prevColumns.length < 1) {
|
||||||
} else {
|
initialAppColumns.push({
|
||||||
const parsed: LumeColumn[] = JSON.parse(prevColumns)
|
label: "onboarding",
|
||||||
const initialAppColumns = parsed.filter((item) =>
|
name: "Onboarding",
|
||||||
item.account ? context.accounts.includes(item.account) : item,
|
url: "/columns/onboarding",
|
||||||
)
|
});
|
||||||
|
|
||||||
return initialAppColumns
|
for (const account of accounts) {
|
||||||
|
initialAppColumns.push({
|
||||||
|
label: `launchpad-${nanoid()}`,
|
||||||
|
name: "Launchpad",
|
||||||
|
url: `/columns/launchpad/${account}`,
|
||||||
|
account,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const parsed: LumeColumn[] = JSON.parse(prevColumns);
|
||||||
|
|
||||||
|
initialAppColumns = parsed.filter((item) =>
|
||||||
|
item.account ? context.accounts.includes(item.account) : item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialAppColumns;
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -4,19 +4,35 @@ import { Spinner, User } from "@/components";
|
|||||||
import { LumeWindow } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import type { LumeColumn, NostrEvent } from "@/types";
|
import type { LumeColumn, NostrEvent } from "@/types";
|
||||||
import { ArrowClockwise, Plus } from "@phosphor-icons/react";
|
import { ArrowClockwise, Plus } from "@phosphor-icons/react";
|
||||||
|
import * as Progress from "@radix-ui/react-progress";
|
||||||
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 { useQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { Channel } from "@tauri-apps/api/core";
|
||||||
import { resolveResource } from "@tauri-apps/api/path";
|
import { resolveResource } from "@tauri-apps/api/path";
|
||||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { useCallback } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/launchpad")({
|
export const Route = createLazyFileRoute("/columns/_layout/launchpad/$id")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
|
const { id } = Route.useParams();
|
||||||
|
const { data: isSync } = useQuery({
|
||||||
|
queryKey: ["is-sync", id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await commands.isAccountSync(id);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
@@ -24,10 +40,15 @@ function Screen() {
|
|||||||
className="overflow-hidden size-full"
|
className="overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
||||||
|
{!isSync ? (
|
||||||
|
<SyncProgress />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Groups />
|
<Groups />
|
||||||
<Interests />
|
<Interests />
|
||||||
<Accounts />
|
|
||||||
<Core />
|
<Core />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</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"
|
||||||
@@ -40,8 +61,74 @@ function Screen() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SyncProgress() {
|
||||||
|
const { id } = Route.useParams();
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
|
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (progress >= 100) {
|
||||||
|
await queryClient.invalidateQueries();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [progress]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const channel = new Channel<number>();
|
||||||
|
|
||||||
|
channel.onmessage = (message) => {
|
||||||
|
setProgress(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const res = await commands.syncAccount(id, channel);
|
||||||
|
|
||||||
|
if (res.status === "error") {
|
||||||
|
setError(res.error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="size-full">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="h-32 flex flex-col items-center justify-center rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800">
|
||||||
|
<div className="w-2/3 flex flex-col gap-2">
|
||||||
|
<Progress.Root
|
||||||
|
className="relative overflow-hidden bg-black/20 dark:bg-white/20 rounded-full w-full h-1"
|
||||||
|
style={{
|
||||||
|
transform: "translateZ(0)",
|
||||||
|
}}
|
||||||
|
value={progress}
|
||||||
|
>
|
||||||
|
<Progress.Indicator
|
||||||
|
className="bg-blue-500 size-full transition-transform duration-[660ms] ease-[cubic-bezier(0.65, 0, 0.35, 1)]"
|
||||||
|
style={{ transform: `translateX(-${100 - progress}%)` }}
|
||||||
|
/>
|
||||||
|
</Progress.Root>
|
||||||
|
<span className="text-center text-xs">
|
||||||
|
{error ? error : "Syncing in Progress..."}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://github.com/hoytech/strfry/blob/nextneg/docs/negentropy.md"
|
||||||
|
target="_blank"
|
||||||
|
className="text-center !underline text-xs font-medium text-blue-500"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Learn more about Negentropy
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Groups() {
|
function Groups() {
|
||||||
const { isLoading, data, refetch, isRefetching } = useQuery({
|
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
||||||
queryKey: ["others", "groups"],
|
queryKey: ["others", "groups"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await commands.getAllGroups();
|
const res = await commands.getAllGroups();
|
||||||
@@ -72,7 +159,7 @@ function Groups() {
|
|||||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||||
>
|
>
|
||||||
<div className="px-2 pt-2">
|
<div className="px-2 pt-2">
|
||||||
<div className="p-3 h-16 bg-neutral-100 rounded-lg flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
<div className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||||
{item.tags
|
{item.tags
|
||||||
.filter((tag) => tag[0] === "p")
|
.filter((tag) => tag[0] === "p")
|
||||||
.map((tag) => (
|
.map((tag) => (
|
||||||
@@ -148,12 +235,16 @@ function Groups() {
|
|||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : isError ? (
|
||||||
|
<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">{error?.message ?? "Error"}</p>
|
||||||
|
</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">
|
<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>
|
<p className="text-center">You don't have any groups yet.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
data.map((item) => renderItem(item))
|
data?.map((item) => renderItem(item))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,7 +252,7 @@ function Groups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Interests() {
|
function Interests() {
|
||||||
const { isLoading, data, refetch, isRefetching } = useQuery({
|
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
||||||
queryKey: ["others", "interests"],
|
queryKey: ["others", "interests"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await commands.getAllInterests();
|
const res = await commands.getAllInterests();
|
||||||
@@ -193,7 +284,7 @@ function Interests() {
|
|||||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||||
>
|
>
|
||||||
<div className="px-2 pt-2">
|
<div className="px-2 pt-2">
|
||||||
<div className="p-3 h-16 bg-neutral-100 rounded-lg flex flex-wrap items-center justify-center gap-4 overflow-y-auto">
|
<div className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg flex flex-wrap items-center justify-center gap-4 overflow-y-auto">
|
||||||
{item.tags
|
{item.tags
|
||||||
.filter((tag) => tag[0] === "t")
|
.filter((tag) => tag[0] === "t")
|
||||||
.map((tag) => (
|
.map((tag) => (
|
||||||
@@ -267,87 +358,16 @@ function Interests() {
|
|||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : isError ? (
|
||||||
|
<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">{error?.message ?? "Error"}</p>
|
||||||
|
</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">
|
<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>
|
<p className="text-center">You don't have any interests yet.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
data.map((item) => renderItem(item))
|
data?.map((item) => renderItem(item))
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Accounts() {
|
|
||||||
const { isLoading, data: accounts } = useQuery({
|
|
||||||
queryKey: ["accounts"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await commands.getAccounts();
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mb-12 flex flex-col gap-3">
|
|
||||||
<div className="flex items-center justify-between px-2">
|
|
||||||
<h3 className="font-semibold">Accounts</h3>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="inline-flex items-center gap-1.5 text-sm">
|
|
||||||
<Spinner className="size-4" />
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
accounts.map((account) => (
|
|
||||||
<div
|
|
||||||
key={account}
|
|
||||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
|
||||||
>
|
|
||||||
<div className="px-2 pt-2">
|
|
||||||
<User.Provider pubkey={account}>
|
|
||||||
<User.Root className="inline-flex items-center gap-2">
|
|
||||||
<User.Avatar className="size-7 rounded-full" />
|
|
||||||
<User.Name className="text-xs font-medium" />
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2 p-2">
|
|
||||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
|
||||||
<div className="text-sm font-medium">Newsfeed</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openNewsfeed(account)}
|
|
||||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
|
||||||
<div className="text-sm font-medium">Stories</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openStory(account)}
|
|
||||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
|
||||||
<div className="text-sm font-medium">Notification</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openNotification(account)}
|
|
||||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -355,8 +375,9 @@ function Accounts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Core() {
|
function Core() {
|
||||||
const { isLoading, data } = useQuery({
|
const { id } = Route.useParams();
|
||||||
queryKey: ["other-columns"],
|
const { data } = useQuery({
|
||||||
|
queryKey: ["core-columns"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const systemPath = "resources/columns.json";
|
const systemPath = "resources/columns.json";
|
||||||
const resourcePath = await resolveResource(systemPath);
|
const resourcePath = await resolveResource(systemPath);
|
||||||
@@ -373,38 +394,56 @@ function Core() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="flex items-center justify-between px-2">
|
<div className="flex items-center justify-between px-2">
|
||||||
<h3 className="font-semibold">Others</h3>
|
<h3 className="font-semibold">Core</h3>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="inline-flex items-center gap-1.5 text-sm">
|
|
||||||
<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>
|
</div>
|
||||||
|
<div className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800">
|
||||||
|
<div className="flex flex-col gap-2 p-2">
|
||||||
|
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||||
|
<div className="text-sm font-medium">Newsfeed</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => LumeWindow.openColumn(column)}
|
onClick={() => LumeWindow.openNewsfeed(id)}
|
||||||
className="text-xs 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"
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))
|
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||||
)}
|
<div className="text-sm font-medium">Stories</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => LumeWindow.openStory(id)}
|
||||||
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||||
|
<div className="text-sm font-medium">Notification</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => LumeWindow.openNotification(id)}
|
||||||
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{data?.map((column) => (
|
||||||
|
<div
|
||||||
|
key={column.label}
|
||||||
|
className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800"
|
||||||
|
>
|
||||||
|
<div className="text-sm font-medium">{column.name}</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => LumeWindow.openColumn(column)}
|
||||||
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -52,7 +52,11 @@ function Screen() {
|
|||||||
|
|
||||||
if (rootId) {
|
if (rootId) {
|
||||||
if (reactions.has(rootId)) {
|
if (reactions.has(rootId)) {
|
||||||
reactions.get(rootId).push(event);
|
const ev = reactions.get(rootId);
|
||||||
|
|
||||||
|
if (ev) {
|
||||||
|
ev.push(event);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
reactions.set(rootId, [event]);
|
reactions.set(rootId, [event]);
|
||||||
}
|
}
|
||||||
@@ -64,7 +68,11 @@ function Screen() {
|
|||||||
|
|
||||||
if (rootId) {
|
if (rootId) {
|
||||||
if (zaps.has(rootId)) {
|
if (zaps.has(rootId)) {
|
||||||
zaps.get(rootId).push(event);
|
const ev = zaps.get(rootId);
|
||||||
|
|
||||||
|
if (ev) {
|
||||||
|
ev.push(event);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
zaps.set(rootId, [event]);
|
zaps.set(rootId, [event]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export class LumeEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async build(event: NostrEvent) {
|
static async build(event: NostrEvent) {
|
||||||
const query = await commands.getEventMeta(event.content);
|
const query = await commands.getMetaFromEvent(event.content);
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
event.meta = query.data;
|
event.meta = query.data;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import type { NostrEvent } from "@/types";
|
import type { NostrEvent } from "@/types";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
import { LumeEvent } from "./event";
|
import { LumeEvent } from "./event";
|
||||||
|
|
||||||
export function useEvent(id: string, repost?: string) {
|
export function useEvent(id: string, repost?: string) {
|
||||||
@@ -10,7 +11,7 @@ export function useEvent(id: string, repost?: string) {
|
|||||||
try {
|
try {
|
||||||
if (repost?.length) {
|
if (repost?.length) {
|
||||||
const nostrEvent: NostrEvent = JSON.parse(repost);
|
const nostrEvent: NostrEvent = JSON.parse(repost);
|
||||||
const res = await commands.getEventMeta(nostrEvent.content);
|
const res = await commands.getMetaFromEvent(nostrEvent.content);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
nostrEvent.meta = res.data;
|
nostrEvent.meta = res.data;
|
||||||
@@ -19,12 +20,17 @@ export function useEvent(id: string, repost?: string) {
|
|||||||
return new LumeEvent(nostrEvent);
|
return new LumeEvent(nostrEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ID
|
let normalizedId = id.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||||
const normalizeId: string = id
|
|
||||||
.replace("nostr:", "")
|
|
||||||
.replace(/[^\w\s]/gi, "");
|
|
||||||
|
|
||||||
const res = await commands.getEvent(normalizeId);
|
if (normalizedId.startsWith("nevent")) {
|
||||||
|
const decoded = nip19.decode(normalizedId);
|
||||||
|
|
||||||
|
if (decoded.type === "nevent") {
|
||||||
|
normalizedId = decoded.data.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await commands.getEvent(normalizedId);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const data = res.data;
|
const data = res.data;
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ export function useProfile(pubkey: string, embed?: string) {
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
let normalizeId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
let normalizedId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||||
|
|
||||||
if (normalizeId.startsWith("nprofile")) {
|
if (normalizedId.startsWith("nprofile")) {
|
||||||
const decoded = nip19.decode(normalizeId);
|
const decoded = nip19.decode(normalizedId);
|
||||||
|
|
||||||
if (decoded.type === "nprofile") {
|
if (decoded.type === "nprofile") {
|
||||||
normalizeId = decoded.data.pubkey;
|
normalizedId = decoded.data.pubkey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = await commands.getProfile(normalizeId);
|
const query = await commands.getProfile(normalizedId);
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return JSON.parse(query.data) as Metadata;
|
return JSON.parse(query.data) as Metadata;
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ export const LumeWindow = {
|
|||||||
column,
|
column,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
openLaunchpad: async () => {
|
openLaunchpad: async (account: string) => {
|
||||||
await getCurrentWindow().emit("columns", {
|
await getCurrentWindow().emit("columns", {
|
||||||
type: "add",
|
type: "add",
|
||||||
column: {
|
column: {
|
||||||
label: "launchpad",
|
label: "launchpad",
|
||||||
name: "Launchpad",
|
name: "Launchpad",
|
||||||
url: "/columns/launchpad",
|
url: `/columns/launchpad/${account}`,
|
||||||
|
account,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user