Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca20bbd298 | |||
| c6da06cd4d | |||
| 50bf6c04c1 | |||
| 490417771c | |||
| 0cb491eaf9 | |||
| ece6bcc125 | |||
| 4b79e559d2 | |||
| 322e510db2 | |||
| 4e279f127d | |||
| 5655a8136d | |||
| d80534c51f | |||
| 0b97248fb8 | |||
| f54f448ecb | |||
| bd1f2b899d | |||
| efd3c83193 | |||
| 85fa1e2359 |
@@ -4,10 +4,7 @@
|
|||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": [
|
"ignore": ["./src/routes.gen.ts", "./src/commands.gen.ts"]
|
||||||
"./src/routes.gen.ts",
|
|
||||||
"./src/commands.gen.ts"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
86
src-tauri/Cargo.lock
generated
86
src-tauri/Cargo.lock
generated
@@ -12,6 +12,7 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
|||||||
name = "Lume"
|
name = "Lume"
|
||||||
version = "24.11.0"
|
version = "24.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"border",
|
"border",
|
||||||
"futures",
|
"futures",
|
||||||
"keyring",
|
"keyring",
|
||||||
@@ -47,6 +48,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
|
"webbrowser",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -444,8 +446,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-wsocket"
|
name = "async-wsocket"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
source = "git+https://github.com/shadowylab/async-wsocket?rev=4d6a5b1780e65dc657ac36e5990a97c10feef072#4d6a5b1780e65dc657ac36e5990a97c10feef072"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a107e3bdbe61e8e1e1341c57241b4b2d50501127b44bd2eff13b4635ab42d35a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -2509,6 +2512,15 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@@ -3081,7 +3093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3479,8 +3491,8 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -3510,8 +3522,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-utility",
|
"async-utility",
|
||||||
@@ -3524,8 +3536,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
@@ -3538,8 +3550,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heed",
|
"heed",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3551,8 +3563,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3569,8 +3581,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"atomic-destructor",
|
"atomic-destructor",
|
||||||
@@ -3588,12 +3600,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-zapper"
|
name = "nostr-zapper"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"nostr",
|
"nostr",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"webln",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3724,7 +3737,7 @@ version = "0.7.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate 1.3.1",
|
"proc-macro-crate 3.2.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.79",
|
"syn 2.0.79",
|
||||||
@@ -3732,8 +3745,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nwc"
|
name = "nwc"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#497c72f5a255c3d0cdf2a837e85c24be3d162fc0"
|
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-utility",
|
"async-utility",
|
||||||
@@ -7107,6 +7120,24 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webbrowser"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e5f07fb9bc8de2ddfe6b24a71a75430673fd679e568c48b52716cef1cfae923"
|
||||||
|
dependencies = [
|
||||||
|
"block2",
|
||||||
|
"core-foundation 0.10.0",
|
||||||
|
"home",
|
||||||
|
"jni",
|
||||||
|
"log",
|
||||||
|
"ndk-context",
|
||||||
|
"objc2",
|
||||||
|
"objc2-foundation",
|
||||||
|
"url",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webkit2gtk"
|
name = "webkit2gtk"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -7151,6 +7182,19 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webln"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75257015c2a40fc43c672fb03b70311f75e48b1020c8acff808ca628c46d87c"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"secp256k1",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.26.6"
|
version = "0.26.6"
|
||||||
@@ -7224,7 +7268,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ tauri-plugin-theme = "2.1.2"
|
|||||||
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
||||||
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
|
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
|
||||||
|
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb"] }
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
|
|
||||||
specta = "^2.0.0-rc.20"
|
specta = "^2.0.0-rc.20"
|
||||||
@@ -49,6 +49,8 @@ regex = "1.10.4"
|
|||||||
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
||||||
keyring-search = { git = "https://github.com/reyamir/keyring-search" }
|
keyring-search = { git = "https://github.com/reyamir/keyring-search" }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
||||||
|
async-trait = "0.1.83"
|
||||||
|
webbrowser = "1.0.2"
|
||||||
|
|
||||||
[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" }
|
||||||
|
|||||||
@@ -46,9 +46,7 @@
|
|||||||
"decorum:allow-show-snap-overlay",
|
"decorum:allow-show-snap-overlay",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
"clipboard-manager:allow-read-text",
|
"clipboard-manager:allow-read-text",
|
||||||
"dialog:allow-open",
|
"dialog:default",
|
||||||
"dialog:allow-ask",
|
|
||||||
"dialog:allow-message",
|
|
||||||
"process:allow-restart",
|
"process:allow-restart",
|
||||||
"process:allow-exit",
|
"process:allow-exit",
|
||||||
"fs:allow-read-file",
|
"fs:allow-read-file",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","core:menu:allow-new","core:menu:allow-popup","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","shell:allow-open","store:default","prevent-default:default","theme:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}}
|
{"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","core:menu:allow-new","core:menu:allow-popup","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:default","process:allow-restart","process:allow-exit","fs:allow-read-file","shell:allow-open","store:default","prevent-default:default","theme:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}}
|
||||||
BIN
src-tauri/icons/tray.png
Normal file
BIN
src-tauri/icons/tray.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 620 B |
@@ -14,6 +14,17 @@ struct Account {
|
|||||||
nostr_connect: Option<String>,
|
nostr_connect: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct AuthHandler;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AuthUrlHandler for AuthHandler {
|
||||||
|
async fn on_auth_url(&self, auth_url: Url) -> Result<()> {
|
||||||
|
webbrowser::open(auth_url.as_str())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn get_accounts() -> Vec<String> {
|
pub fn get_accounts() -> Vec<String> {
|
||||||
@@ -94,19 +105,29 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<Str
|
|||||||
let remote_npub = remote_user.to_bech32().map_err(|err| err.to_string())?;
|
let remote_npub = remote_user.to_bech32().map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
// Init nostr connect
|
// Init nostr connect
|
||||||
let nostr_connect = NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None)
|
let mut nostr_connect = NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None)
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let bunker_uri = nostr_connect
|
// Handle auth url
|
||||||
|
nostr_connect.auth_url_handler(AuthHandler);
|
||||||
|
|
||||||
|
let keyring = Entry::new("Lume Safe Storage", &remote_npub).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let reuse_bunker = nostr_connect
|
||||||
.bunker_uri()
|
.bunker_uri()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let keyring = Entry::new("Lume Safe Storage", &remote_npub).map_err(|err| err.to_string())?;
|
let mut reuse_uri = reuse_bunker.to_string();
|
||||||
|
|
||||||
|
if let Some(secret) = reuse_bunker.secret() {
|
||||||
|
let replace = format!("&secret={}", secret);
|
||||||
|
reuse_uri = reuse_uri.replace(replace.as_str(), "");
|
||||||
|
}
|
||||||
|
|
||||||
let account = Account {
|
let account = Account {
|
||||||
secret_key: app_secret,
|
secret_key: app_secret,
|
||||||
nostr_connect: Some(bunker_uri.to_string()),
|
nostr_connect: Some(reuse_uri),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save secret key to keyring
|
// Save secret key to keyring
|
||||||
@@ -217,7 +238,9 @@ pub async fn set_signer(
|
|||||||
let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?;
|
let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
|
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
|
||||||
Ok(signer) => {
|
Ok(mut signer) => {
|
||||||
|
// Handle auth url
|
||||||
|
signer.auth_url_handler(AuthHandler);
|
||||||
// Update signer
|
// Update signer
|
||||||
client.set_signer(signer).await;
|
client.set_signer(signer).await;
|
||||||
// Emit to front-end
|
// Emit to front-end
|
||||||
|
|||||||
@@ -239,6 +239,173 @@ pub async fn get_all_events_from(
|
|||||||
Ok(alt_events)
|
Ok(alt_events)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn get_all_events_by_kind(
|
||||||
|
kind: u16,
|
||||||
|
until: Option<String>,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
) -> Result<Vec<String>, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
|
||||||
|
let as_of = match until {
|
||||||
|
Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?,
|
||||||
|
None => Timestamp::now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Custom(kind))
|
||||||
|
.limit(FETCH_LIMIT)
|
||||||
|
.until(as_of);
|
||||||
|
|
||||||
|
let mut events = Events::new(&[filter.clone()]);
|
||||||
|
|
||||||
|
let mut rx = client
|
||||||
|
.stream_events(vec![filter], Some(Duration::from_secs(3)))
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
while let Some(event) = rx.next().await {
|
||||||
|
events.insert(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alt_events: Vec<String> = events.iter().map(|ev| ev.as_json()).collect();
|
||||||
|
|
||||||
|
Ok(alt_events)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn get_all_providers(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Custom(31990))
|
||||||
|
.custom_tag(SingleLetterTag::lowercase(Alphabet::K), vec!["5300"]);
|
||||||
|
|
||||||
|
let mut events = Events::new(&[filter.clone()]);
|
||||||
|
|
||||||
|
let mut rx = client
|
||||||
|
.stream_events(vec![filter], Some(Duration::from_secs(3)))
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
while let Some(event) = rx.next().await {
|
||||||
|
events.insert(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alt_events: Vec<String> = events.iter().map(|ev| ev.as_json()).collect();
|
||||||
|
|
||||||
|
Ok(alt_events)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn request_events_from_provider(
|
||||||
|
provider: String,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let signer = client.signer().await.map_err(|err| err.to_string())?;
|
||||||
|
let public_key = signer
|
||||||
|
.get_public_key()
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let provider = PublicKey::parse(&provider).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Get current user's relay list
|
||||||
|
let relay_list = client
|
||||||
|
.database()
|
||||||
|
.relay_list(public_key)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let relay_list: Vec<String> = relay_list.iter().map(|item| item.0.to_string()).collect();
|
||||||
|
|
||||||
|
// Create job request
|
||||||
|
let builder = EventBuilder::job_request(
|
||||||
|
Kind::JobRequest(5300),
|
||||||
|
vec![
|
||||||
|
Tag::public_key(provider),
|
||||||
|
Tag::custom(TagKind::Relays, relay_list),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
match client.send_event_builder(builder).await {
|
||||||
|
Ok(output) => {
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::JobResult(6300))
|
||||||
|
.author(provider)
|
||||||
|
.pubkey(public_key)
|
||||||
|
.since(Timestamp::now());
|
||||||
|
|
||||||
|
let opts = SubscribeAutoCloseOptions::default()
|
||||||
|
.filter(FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(2)));
|
||||||
|
|
||||||
|
let _ = client.subscribe(vec![filter], Some(opts)).await;
|
||||||
|
|
||||||
|
Ok(output.val.to_hex())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn get_all_events_by_request(
|
||||||
|
id: String,
|
||||||
|
provider: String,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
) -> Result<Vec<RichEvent>, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let public_key = PublicKey::parse(&id).map_err(|err| err.to_string())?;
|
||||||
|
let provider = PublicKey::parse(&provider).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::JobResult(6300))
|
||||||
|
.author(provider)
|
||||||
|
.pubkey(public_key)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let events = client
|
||||||
|
.database()
|
||||||
|
.query(vec![filter])
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
if let Some(event) = events.first() {
|
||||||
|
let parsed: Vec<Vec<String>> =
|
||||||
|
serde_json::from_str(&event.content).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let vec: Vec<Tag> = parsed
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|item| Tag::parse(&item).ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let tags = Tags::new(vec);
|
||||||
|
let ids: Vec<EventId> = tags.event_ids().copied().collect();
|
||||||
|
|
||||||
|
let filter = Filter::new().ids(ids);
|
||||||
|
let mut events = Events::new(&[filter.clone()]);
|
||||||
|
|
||||||
|
let mut rx = client
|
||||||
|
.stream_events(vec![filter], Some(Duration::from_secs(3)))
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
while let Some(event) = rx.next().await {
|
||||||
|
events.insert(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alt_events = process_event(client, events, false).await;
|
||||||
|
|
||||||
|
Ok(alt_events)
|
||||||
|
} else {
|
||||||
|
Err("Job result not found.".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_local_events(
|
pub async fn get_local_events(
|
||||||
|
|||||||
@@ -381,6 +381,35 @@ pub async fn get_all_local_interests(
|
|||||||
Ok(alt_events)
|
Ok(alt_events)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn get_relay_list(id: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.author(public_key)
|
||||||
|
.kind(Kind::RelayList)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let mut events = Events::new(&[filter.clone()]);
|
||||||
|
|
||||||
|
let mut rx = client
|
||||||
|
.stream_events(vec![filter], Some(Duration::from_secs(3)))
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
while let Some(event) = rx.next().await {
|
||||||
|
events.insert(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(event) = events.first() {
|
||||||
|
Ok(event.as_json())
|
||||||
|
} else {
|
||||||
|
Err("Relay list not found".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_all_profiles(state: State<'_, Nostr>) -> Result<Vec<Mention>, String> {
|
pub async fn get_all_profiles(state: State<'_, Nostr>) -> Result<Vec<Mention>, String> {
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ pub mod account;
|
|||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod relay;
|
pub mod relay;
|
||||||
pub mod sync;
|
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::{
|
use std::str::FromStr;
|
||||||
fs::OpenOptions,
|
use tauri::State;
|
||||||
io::{self, BufRead, Write},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
use tauri::{path::BaseDirectory, Manager, State};
|
|
||||||
|
|
||||||
use crate::{Nostr, FETCH_LIMIT};
|
use crate::{Nostr, FETCH_LIMIT};
|
||||||
|
|
||||||
@@ -20,82 +16,17 @@ pub struct Relays {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_relays(id: String, state: State<'_, Nostr>) -> Result<Relays, String> {
|
pub async fn get_all_relays(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
let relays = client.pool().all_relays().await;
|
||||||
|
let v: Vec<String> = relays.iter().map(|item| item.0.to_string()).collect();
|
||||||
|
|
||||||
let connected_relays = client
|
Ok(v)
|
||||||
.relays()
|
|
||||||
.await
|
|
||||||
.into_keys()
|
|
||||||
.map(|url| url.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let filter = Filter::new()
|
|
||||||
.author(public_key)
|
|
||||||
.kind(Kind::RelayList)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
|
||||||
Ok(events) => {
|
|
||||||
if let Some(event) = events.first() {
|
|
||||||
let nip65_list = nip65::extract_relay_list(event).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let read = nip65_list
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(url, meta)| {
|
|
||||||
if let Some(RelayMetadata::Read) = meta {
|
|
||||||
Some(url.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let write = nip65_list
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(url, meta)| {
|
|
||||||
if let Some(RelayMetadata::Write) = meta {
|
|
||||||
Some(url.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let both = nip65_list
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(url, meta)| {
|
|
||||||
if meta.is_none() {
|
|
||||||
Some(url.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Relays {
|
|
||||||
connected: connected_relays,
|
|
||||||
read: Some(read),
|
|
||||||
write: Some(write),
|
|
||||||
both: Some(both),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(Relays {
|
|
||||||
connected: connected_relays,
|
|
||||||
read: None,
|
|
||||||
write: None,
|
|
||||||
both: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_all_relays(
|
pub async fn get_all_relay_lists(
|
||||||
until: Option<String>,
|
until: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<Vec<String>, String> {
|
) -> Result<Vec<String>, String> {
|
||||||
@@ -149,36 +80,3 @@ pub async fn remove_relay(relay: String, state: State<'_, Nostr>) -> Result<(),
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub fn get_bootstrap_relays(app: tauri::AppHandle) -> Result<Vec<String>, String> {
|
|
||||||
let relays_path = app
|
|
||||||
.path()
|
|
||||||
.resolve("resources/relays.txt", BaseDirectory::Resource)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let file = std::fs::File::open(relays_path).map_err(|e| e.to_string())?;
|
|
||||||
let reader = io::BufReader::new(file);
|
|
||||||
|
|
||||||
reader
|
|
||||||
.lines()
|
|
||||||
.collect::<Result<Vec<String>, io::Error>>()
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub fn set_bootstrap_relays(relays: String, app: tauri::AppHandle) -> Result<(), String> {
|
|
||||||
let relays_path = app
|
|
||||||
.path()
|
|
||||||
.resolve("resources/relays.txt", BaseDirectory::Resource)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.open(relays_path)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
file.write_all(relays.as_bytes()).map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
use nostr_sdk::prelude::*;
|
|
||||||
use std::fs::{self, File};
|
|
||||||
use tauri::{ipc::Channel, Manager, State};
|
|
||||||
|
|
||||||
use crate::Nostr;
|
|
||||||
|
|
||||||
#[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 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(filter, &opts).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(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(())
|
|
||||||
}
|
|
||||||
@@ -71,11 +71,11 @@ pub async fn create_column(
|
|||||||
if let Ok(public_key) = PublicKey::parse(&id) {
|
if let Ok(public_key) = PublicKey::parse(&id) {
|
||||||
let is_newsfeed = payload.url().to_string().contains("newsfeed");
|
let is_newsfeed = payload.url().to_string().contains("newsfeed");
|
||||||
|
|
||||||
|
if is_newsfeed {
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = webview.state::<Nostr>();
|
let state = webview.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
if is_newsfeed {
|
|
||||||
if let Ok(contact_list) =
|
if let Ok(contact_list) =
|
||||||
client.database().contacts_public_keys(public_key).await
|
client.database().contacts_public_keys(public_key).await
|
||||||
{
|
{
|
||||||
@@ -102,9 +102,12 @@ pub async fn create_column(
|
|||||||
println!("Subscription error: {}", e);
|
println!("Subscription error: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else if let Ok(event_id) = EventId::parse(&id) {
|
} else if let Ok(event_id) = EventId::parse(&id) {
|
||||||
|
let is_thread = payload.url().to_string().contains("events");
|
||||||
|
|
||||||
|
if is_thread {
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = webview.state::<Nostr>();
|
let state = webview.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@@ -126,6 +129,7 @@ pub async fn create_column(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
PageLoadEvent::Finished => {
|
PageLoadEvent::Finished => {
|
||||||
println!("{} finished loading", payload.url());
|
println!("{} finished loading", payload.url());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,14 @@ use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use specta_typescript::Typescript;
|
use specta_typescript::Typescript;
|
||||||
use std::{
|
use std::{collections::HashSet, fs, str::FromStr, time::Duration};
|
||||||
collections::HashSet,
|
use tauri::{
|
||||||
fs,
|
menu::{Menu, MenuItem},
|
||||||
io::{self, BufRead},
|
Emitter, EventTarget, Listener, Manager, WebviewWindowBuilder,
|
||||||
str::FromStr,
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
use tauri::{path::BaseDirectory, Emitter, EventTarget, Listener, Manager};
|
|
||||||
use tauri_plugin_decorum::WebviewWindowExt;
|
use tauri_plugin_decorum::WebviewWindowExt;
|
||||||
use tauri_plugin_notification::{NotificationExt, PermissionState};
|
use tauri_plugin_notification::{NotificationExt, PermissionState};
|
||||||
|
use tauri_plugin_store::StoreExt;
|
||||||
use tauri_specta::{collect_commands, Builder};
|
use tauri_specta::{collect_commands, Builder};
|
||||||
use tokio::{sync::RwLock, time::sleep};
|
use tokio::{sync::RwLock, time::sleep};
|
||||||
|
|
||||||
@@ -30,7 +28,6 @@ pub mod common;
|
|||||||
pub struct Nostr {
|
pub struct Nostr {
|
||||||
client: Client,
|
client: Client,
|
||||||
queue: RwLock<HashSet<PublicKey>>,
|
queue: RwLock<HashSet<PublicKey>>,
|
||||||
is_syncing: RwLock<bool>,
|
|
||||||
settings: RwLock<Settings>,
|
settings: RwLock<Settings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +36,7 @@ pub struct Payload {
|
|||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
resize_service: bool,
|
resize_service: bool,
|
||||||
content_warning: bool,
|
content_warning: bool,
|
||||||
@@ -64,20 +61,26 @@ impl Default for Settings {
|
|||||||
|
|
||||||
pub const DEFAULT_DIFFICULTY: u8 = 0;
|
pub const DEFAULT_DIFFICULTY: u8 = 0;
|
||||||
pub const FETCH_LIMIT: usize = 50;
|
pub const FETCH_LIMIT: usize = 50;
|
||||||
pub const QUEUE_DELAY: u64 = 300;
|
pub const QUEUE_DELAY: u64 = 150;
|
||||||
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
||||||
|
// Will be removed when almost relays support negentropy
|
||||||
|
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://relay.primal.net",
|
||||||
|
"wss://nostr.fmt.wiz.biz",
|
||||||
|
"wss://directory.yabu.me",
|
||||||
|
"wss://purplepag.es",
|
||||||
|
];
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
|
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
|
||||||
get_relays,
|
|
||||||
get_all_relays,
|
get_all_relays,
|
||||||
|
get_all_relay_lists,
|
||||||
is_relay_connected,
|
is_relay_connected,
|
||||||
connect_relay,
|
connect_relay,
|
||||||
remove_relay,
|
remove_relay,
|
||||||
get_bootstrap_relays,
|
|
||||||
set_bootstrap_relays,
|
|
||||||
get_accounts,
|
get_accounts,
|
||||||
watch_account,
|
watch_account,
|
||||||
import_account,
|
import_account,
|
||||||
@@ -102,6 +105,7 @@ fn main() {
|
|||||||
get_interest,
|
get_interest,
|
||||||
get_all_interests,
|
get_all_interests,
|
||||||
get_all_local_interests,
|
get_all_local_interests,
|
||||||
|
get_relay_list,
|
||||||
set_wallet,
|
set_wallet,
|
||||||
load_wallet,
|
load_wallet,
|
||||||
remove_wallet,
|
remove_wallet,
|
||||||
@@ -117,6 +121,10 @@ fn main() {
|
|||||||
get_all_events_by_authors,
|
get_all_events_by_authors,
|
||||||
get_all_events_by_hashtags,
|
get_all_events_by_hashtags,
|
||||||
get_all_events_from,
|
get_all_events_from,
|
||||||
|
get_all_events_by_kind,
|
||||||
|
get_all_providers,
|
||||||
|
request_events_from_provider,
|
||||||
|
get_all_events_by_request,
|
||||||
get_local_events,
|
get_local_events,
|
||||||
get_global_events,
|
get_global_events,
|
||||||
search,
|
search,
|
||||||
@@ -173,6 +181,53 @@ fn main() {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
main_window.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
main_window.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
||||||
|
|
||||||
|
// Setup tray menu item
|
||||||
|
let open_i = MenuItem::with_id(app, "open", "Open Lume", true, None::<&str>)?;
|
||||||
|
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||||
|
// Create tray menu
|
||||||
|
let menu = Menu::with_items(app, &[&open_i, &quit_i])?;
|
||||||
|
// Get main tray
|
||||||
|
let tray = app.tray_by_id("main").unwrap();
|
||||||
|
// Set menu
|
||||||
|
tray.set_menu(Some(menu)).unwrap();
|
||||||
|
// Listen to tray events
|
||||||
|
tray.on_menu_event(|handle, event| match event.id().as_ref() {
|
||||||
|
"open" => {
|
||||||
|
if let Some(window) = handle.get_window("main") {
|
||||||
|
if window.is_visible().unwrap_or_default() {
|
||||||
|
let _ = window.set_focus();
|
||||||
|
} else {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let window = WebviewWindowBuilder::from_config(
|
||||||
|
handle,
|
||||||
|
handle.config().app.windows.first().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Set decoration
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
window.create_overlay_titlebar().unwrap();
|
||||||
|
|
||||||
|
// Restore native border
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
window.add_border(None);
|
||||||
|
|
||||||
|
// Set a custom inset to the traffic lights
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
window.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"quit" => {
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
|
||||||
let client = tauri::async_runtime::block_on(async move {
|
let client = tauri::async_runtime::block_on(async move {
|
||||||
// Setup database
|
// Setup database
|
||||||
let database = NostrLMDB::open(config_dir.join("nostr"))
|
let database = NostrLMDB::open(config_dir.join("nostr"))
|
||||||
@@ -181,8 +236,7 @@ fn main() {
|
|||||||
// Config
|
// Config
|
||||||
let opts = Options::new()
|
let opts = Options::new()
|
||||||
.gossip(true)
|
.gossip(true)
|
||||||
.max_avg_latency(Duration::from_millis(300))
|
.max_avg_latency(Duration::from_millis(500))
|
||||||
.automatic_authentication(true)
|
|
||||||
.timeout(Duration::from_secs(5));
|
.timeout(Duration::from_secs(5));
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
@@ -191,7 +245,7 @@ fn main() {
|
|||||||
.opts(opts)
|
.opts(opts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Get bootstrap relays
|
/* Get bootstrap relays
|
||||||
if let Ok(path) = handle
|
if let Ok(path) = handle
|
||||||
.path()
|
.path()
|
||||||
.resolve("resources/relays.txt", BaseDirectory::Resource)
|
.resolve("resources/relays.txt", BaseDirectory::Resource)
|
||||||
@@ -218,6 +272,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
for relay in BOOTSTRAP_RELAYS {
|
||||||
|
let _ = client.add_relay(relay).await;
|
||||||
|
}
|
||||||
|
|
||||||
let _ = client.add_discovery_relay("wss://user.kindpag.es/").await;
|
let _ = client.add_discovery_relay("wss://user.kindpag.es/").await;
|
||||||
|
|
||||||
@@ -227,112 +286,29 @@ fn main() {
|
|||||||
client
|
client
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load app settings
|
||||||
|
let store = app.store(".data")?;
|
||||||
|
|
||||||
|
// Parse app settings if exist
|
||||||
|
let settings = if let Some(data) = store.get("tanstack-query-[\"settings\"]") {
|
||||||
|
if let Some(str) = data.as_str() {
|
||||||
|
let v: Value = serde_json::from_str(str).unwrap();
|
||||||
|
let data = v["state"]["data"].clone();
|
||||||
|
let parse: Settings = serde_json::from_value(data).unwrap();
|
||||||
|
|
||||||
|
RwLock::new(parse)
|
||||||
|
} else {
|
||||||
|
RwLock::new(Settings::default())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RwLock::new(Settings::default())
|
||||||
|
};
|
||||||
|
|
||||||
// Create global state
|
// Create global state
|
||||||
app.manage(Nostr {
|
app.manage(Nostr {
|
||||||
client,
|
client,
|
||||||
|
settings,
|
||||||
queue: RwLock::new(HashSet::new()),
|
queue: RwLock::new(HashSet::new()),
|
||||||
is_syncing: RwLock::new(false),
|
|
||||||
settings: RwLock::new(Settings::default()),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trigger some actions for window events
|
|
||||||
main_window.on_window_event(move |event| match event {
|
|
||||||
tauri::WindowEvent::Focused(focused) => {
|
|
||||||
if !focused {
|
|
||||||
let handle = handle_clone_event.clone();
|
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
let state = handle.state::<Nostr>();
|
|
||||||
let client = &state.client;
|
|
||||||
|
|
||||||
if *state.is_syncing.read().await {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut is_syncing = state.is_syncing.write().await;
|
|
||||||
|
|
||||||
// Mark sync in progress
|
|
||||||
*is_syncing = true;
|
|
||||||
|
|
||||||
let opts = SyncOptions::default();
|
|
||||||
let accounts = get_all_accounts();
|
|
||||||
|
|
||||||
if !accounts.is_empty() {
|
|
||||||
let public_keys: Vec<PublicKey> = accounts
|
|
||||||
.iter()
|
|
||||||
.filter_map(|acc| PublicKey::from_str(acc).ok())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let filter = Filter::new().pubkeys(public_keys).kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Reaction,
|
|
||||||
Kind::ZapReceipt,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if let Ok(output) = client.sync(filter, &opts).await {
|
|
||||||
println!("Received: {}", output.received.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let filter = Filter::new().kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::ContactList,
|
|
||||||
Kind::FollowSet,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Get all public keys in database
|
|
||||||
if let Ok(events) = client.database().query(vec![filter]).await {
|
|
||||||
let public_keys: HashSet<PublicKey> = events
|
|
||||||
.iter()
|
|
||||||
.flat_map(|ev| ev.tags.public_keys().copied())
|
|
||||||
.collect();
|
|
||||||
let pk_vec: Vec<PublicKey> = public_keys.into_iter().collect();
|
|
||||||
|
|
||||||
for chunk in pk_vec.chunks(500) {
|
|
||||||
if chunk.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let authors = chunk.to_owned();
|
|
||||||
|
|
||||||
let filter = Filter::new()
|
|
||||||
.authors(authors.clone())
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::Metadata,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
|
||||||
])
|
|
||||||
.limit(1000);
|
|
||||||
|
|
||||||
if let Ok(output) = client.sync(filter, &opts).await {
|
|
||||||
println!("Received: {}", output.received.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
let filter = Filter::new()
|
|
||||||
.authors(authors)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::EventDeletion,
|
|
||||||
])
|
|
||||||
.limit(500);
|
|
||||||
|
|
||||||
if let Ok(output) = client.sync(filter, &opts).await {
|
|
||||||
println!("Received: {}", output.received.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark sync is done
|
|
||||||
*is_syncing = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tauri::WindowEvent::Moved(_size) => {}
|
|
||||||
_ => {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for request metadata
|
// Listen for request metadata
|
||||||
@@ -379,7 +355,106 @@ fn main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run notification thread
|
// Run a thread for negentropy
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let state = handle_clone_event.state::<Nostr>();
|
||||||
|
let client = &state.client;
|
||||||
|
|
||||||
|
// Use default sync options
|
||||||
|
let opts = SyncOptions::default();
|
||||||
|
|
||||||
|
// Set interval
|
||||||
|
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(600));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
|
||||||
|
let accounts = get_all_accounts();
|
||||||
|
let public_keys: Vec<PublicKey> = accounts
|
||||||
|
.iter()
|
||||||
|
.filter_map(|acc| PublicKey::from_str(acc).ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !public_keys.is_empty() {
|
||||||
|
// Create filter for notification
|
||||||
|
//
|
||||||
|
let filter = Filter::new().pubkeys(public_keys.clone()).kinds(vec![
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Reaction,
|
||||||
|
Kind::ZapReceipt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Sync notification
|
||||||
|
//
|
||||||
|
if let Ok(output) = client.sync_with(BOOTSTRAP_RELAYS, filter, &opts).await
|
||||||
|
{
|
||||||
|
println!("Received: {}", output.received.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create filter for contact list
|
||||||
|
//
|
||||||
|
let filter = Filter::new()
|
||||||
|
.authors(public_keys)
|
||||||
|
.kinds(vec![Kind::ContactList, Kind::FollowSet]);
|
||||||
|
|
||||||
|
// Sync events for contact list
|
||||||
|
//
|
||||||
|
if let Ok(events) = client.database().query(vec![filter]).await {
|
||||||
|
// Get unique public keys
|
||||||
|
let public_keys: HashSet<PublicKey> = events
|
||||||
|
.iter()
|
||||||
|
.flat_map(|ev| ev.tags.public_keys().copied())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Convert to vector
|
||||||
|
let public_keys: Vec<PublicKey> = public_keys.into_iter().collect();
|
||||||
|
|
||||||
|
for chunk in public_keys.chunks(1000) {
|
||||||
|
if chunk.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let authors = chunk.to_owned();
|
||||||
|
|
||||||
|
// Create filter for metadata
|
||||||
|
//
|
||||||
|
let filter = Filter::new().authors(authors.clone()).kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Sync metadata
|
||||||
|
//
|
||||||
|
if let Ok(output) =
|
||||||
|
client.sync_with(BOOTSTRAP_RELAYS, filter, &opts).await
|
||||||
|
{
|
||||||
|
println!("Received: {}", output.received.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create filter for text note
|
||||||
|
//
|
||||||
|
let filter = Filter::new()
|
||||||
|
.authors(authors)
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::EventDeletion])
|
||||||
|
.limit(100);
|
||||||
|
|
||||||
|
// Sync text note
|
||||||
|
//
|
||||||
|
if let Ok(output) =
|
||||||
|
client.sync_with(BOOTSTRAP_RELAYS, filter, &opts).await
|
||||||
|
{
|
||||||
|
println!("Received: {}", output.received.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run a thread for handle notification
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = handle_clone.state::<Nostr>();
|
let state = handle_clone.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@@ -499,8 +574,13 @@ fn main() {
|
|||||||
.plugin(tauri_plugin_upload::init())
|
.plugin(tauri_plugin_upload::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.run(ctx)
|
.build(ctx)
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application")
|
||||||
|
.run(|_app_handle, event| {
|
||||||
|
if let tauri::RunEvent::ExitRequested { api, .. } = event {
|
||||||
|
api.prevent_exit();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "Lume",
|
"productName": "Lume",
|
||||||
"version": "24.11.1",
|
"version": "24.11.6",
|
||||||
"identifier": "nu.lume.Lume",
|
"identifier": "nu.lume.Lume",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
@@ -30,6 +30,13 @@
|
|||||||
"$RESOURCE/*"
|
"$RESOURCE/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"trayIcon": {
|
||||||
|
"id": "main",
|
||||||
|
"iconAsTemplate": true,
|
||||||
|
"menuOnLeftClick": true,
|
||||||
|
"tooltip": "Lume",
|
||||||
|
"iconPath": "./icons/tray.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
@@ -39,10 +46,7 @@
|
|||||||
"targets": "all",
|
"targets": "all",
|
||||||
"active": true,
|
"active": true,
|
||||||
"category": "SocialNetworking",
|
"category": "SocialNetworking",
|
||||||
"resources": [
|
"resources": ["resources/*", "locales/*"],
|
||||||
"resources/*",
|
|
||||||
"locales/*"
|
|
||||||
],
|
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
|
|||||||
@@ -13,9 +13,7 @@
|
|||||||
"hiddenTitle": true,
|
"hiddenTitle": true,
|
||||||
"transparent": true,
|
"transparent": true,
|
||||||
"windowEffects": {
|
"windowEffects": {
|
||||||
"effects": [
|
"effects": ["underWindowBackground"]
|
||||||
"underWindowBackground"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,17 +5,17 @@
|
|||||||
|
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
async getRelays(id: string) : Promise<Result<Relays, string>> {
|
async getAllRelays() : Promise<Result<string[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_all_relays") };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getAllRelays(until: string | null) : Promise<Result<string[], string>> {
|
async getAllRelayLists(until: string | null) : Promise<Result<string[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_all_relays", { until }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_all_relay_lists", { until }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
@@ -45,22 +45,6 @@ async removeRelay(relay: string) : Promise<Result<null, string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getBootstrapRelays() : Promise<Result<string[], string>> {
|
|
||||||
try {
|
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_bootstrap_relays") };
|
|
||||||
} catch (e) {
|
|
||||||
if(e instanceof Error) throw e;
|
|
||||||
else return { status: "error", error: e as any };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async setBootstrapRelays(relays: string) : Promise<Result<null, string>> {
|
|
||||||
try {
|
|
||||||
return { status: "ok", data: await TAURI_INVOKE("set_bootstrap_relays", { relays }) };
|
|
||||||
} catch (e) {
|
|
||||||
if(e instanceof Error) throw e;
|
|
||||||
else return { status: "error", error: e as any };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getAccounts() : Promise<string[]> {
|
async getAccounts() : Promise<string[]> {
|
||||||
return await TAURI_INVOKE("get_accounts");
|
return await TAURI_INVOKE("get_accounts");
|
||||||
},
|
},
|
||||||
@@ -248,6 +232,14 @@ async getAllLocalInterests(until: string | null) : Promise<Result<RichEvent[], s
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getRelayList(id: string) : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_relay_list", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async setWallet(uri: string) : Promise<Result<boolean, string>> {
|
async setWallet(uri: string) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("set_wallet", { uri }) };
|
return { status: "ok", data: await TAURI_INVOKE("set_wallet", { uri }) };
|
||||||
@@ -368,6 +360,38 @@ async getAllEventsFrom(url: string, until: string | null) : Promise<Result<RichE
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getAllEventsByKind(kind: number, until: string | null) : Promise<Result<string[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_kind", { kind, until }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAllProviders() : Promise<Result<string[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_all_providers") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async requestEventsFromProvider(provider: string) : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("request_events_from_provider", { provider }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAllEventsByRequest(id: string, provider: string) : Promise<Result<RichEvent[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_request", { id, provider }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async getLocalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
async getLocalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_local_events", { until }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_local_events", { until }) };
|
||||||
@@ -528,7 +552,6 @@ export type Column = { label: string; url: string; x: number; y: number; width:
|
|||||||
export type Mention = { pubkey: string; avatar: string; display_name: string; name: string }
|
export type Mention = { pubkey: string; avatar: string; display_name: string; name: string }
|
||||||
export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
||||||
export type NewWindow = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean }
|
export type NewWindow = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean }
|
||||||
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
|
||||||
export type RichEvent = { raw: string; parsed: Meta | null }
|
export type RichEvent = { raw: string; parsed: Meta | null }
|
||||||
export type Settings = { resize_service: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
export type Settings = { resize_service: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import type {
|
|||||||
MaybePromise,
|
MaybePromise,
|
||||||
PersistedQuery,
|
PersistedQuery,
|
||||||
} from "@tanstack/query-persist-client-core";
|
} from "@tanstack/query-persist-client-core";
|
||||||
import { ask, message, open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import { readFile } from "@tauri-apps/plugin-fs";
|
import { readFile } from "@tauri-apps/plugin-fs";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
|
||||||
import type { Store as TauriStore } from "@tauri-apps/plugin-store";
|
import type { Store as TauriStore } from "@tauri-apps/plugin-store";
|
||||||
import { check } from "@tauri-apps/plugin-updater";
|
|
||||||
import { BitcoinUnit } from "bitcoin-units";
|
import { BitcoinUnit } from "bitcoin-units";
|
||||||
import { type ClassValue, clsx } from "clsx";
|
import { type ClassValue, clsx } from "clsx";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -23,6 +21,15 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValidRelayUrl(string: string) {
|
||||||
|
try {
|
||||||
|
const newUrl = new URL(string);
|
||||||
|
return newUrl.protocol === "ws:" || newUrl.protocol === "wss:";
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const isImagePath = (path: string) => {
|
export const isImagePath = (path: string) => {
|
||||||
const exts = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"];
|
const exts = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"];
|
||||||
|
|
||||||
@@ -170,41 +177,6 @@ export function decodeZapInvoice(tags: string[][]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkForAppUpdates(silent: boolean) {
|
|
||||||
const update = await check();
|
|
||||||
|
|
||||||
if (!update) {
|
|
||||||
if (silent) return;
|
|
||||||
|
|
||||||
await message("You are on the latest version. Stay awesome!", {
|
|
||||||
title: "No Update Available",
|
|
||||||
kind: "info",
|
|
||||||
okLabel: "OK",
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update?.available) {
|
|
||||||
const yes = await ask(
|
|
||||||
`Update to ${update.version} is available!\n\nRelease notes: ${update.body}`,
|
|
||||||
{
|
|
||||||
title: "Update Available",
|
|
||||||
kind: "info",
|
|
||||||
okLabel: "Update",
|
|
||||||
cancelLabel: "Cancel",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (yes) {
|
|
||||||
await update.downloadAndInstall();
|
|
||||||
await relaunch();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function upload(filePath?: string) {
|
export async function upload(filePath?: string) {
|
||||||
const allowExts = [
|
const allowExts = [
|
||||||
"png",
|
"png",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function Column({ column }: { column: LumeColumn }) {
|
|||||||
y: rect.y,
|
y: rect.y,
|
||||||
width: rect.width,
|
width: rect.width,
|
||||||
height: rect.height,
|
height: rect.height,
|
||||||
url: `${column.url}?label=${column.label}&name=${column.name}`,
|
url: `${column.url}?label=${column.label}&name=${column.name}&account=${column.account}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
@@ -160,7 +160,7 @@ function Header({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => showContextMenu(e)}
|
onClick={(e) => showContextMenu(e)}
|
||||||
className="hidden shrink-0 group-hover:inline-flex items-center justify-center size-6 border-[.5px] border-neutral-200 dark:border-neutral-800 shadow shadow-neutral-200/50 dark:shadow-none rounded-full bg-white dark:bg-black"
|
className="hidden shrink-0 group-hover:inline-flex items-center justify-center size-6 bg-white dark:bg-neutral-800 border-[.5px] border-neutral-200 dark:border-neutral-800 shadow shadow-neutral-200/50 dark:shadow-none rounded-full"
|
||||||
>
|
>
|
||||||
<CaretDown className="size-3" weight="bold" />
|
<CaretDown className="size-3" weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function NoteQuote({
|
|||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => LumeWindow.openEditor(null, event.id)}
|
onClick={() => LumeWindow.openEditor(undefined, event.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
|
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
|
||||||
label
|
label
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ export function NoteRepost({
|
|||||||
const signer = await commands.hasSigner(account);
|
const signer = await commands.hasSigner(account);
|
||||||
|
|
||||||
if (signer.status === "ok") {
|
if (signer.status === "ok") {
|
||||||
if (!signer.data) {
|
|
||||||
if (!signer.data) {
|
if (!signer.data) {
|
||||||
const res = await commands.setSigner(account);
|
const res = await commands.setSigner(account);
|
||||||
|
|
||||||
@@ -113,7 +112,6 @@ export function NoteRepost({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
repost.mutate();
|
repost.mutate();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ import { ZapIcon } from "@/components";
|
|||||||
import { settingsQueryOptions } from "@/routes/__root";
|
import { settingsQueryOptions } from "@/routes/__root";
|
||||||
import { LumeWindow } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { useSearch } from "@tanstack/react-router";
|
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
|
|
||||||
export function NoteZap({
|
export function NoteZap({
|
||||||
label = false,
|
label = false,
|
||||||
smol = false,
|
smol = false,
|
||||||
}: { label?: boolean; smol?: boolean }) {
|
}: { label?: boolean; smol?: boolean }) {
|
||||||
const search = useSearch({ strict: false });
|
|
||||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
|
|
||||||
@@ -19,7 +17,7 @@ export function NoteZap({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => LumeWindow.openZap(event.id, search.account)}
|
onClick={() => LumeWindow.openZap(event.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-7 rounded-full inline-flex items-center justify-center text-neutral-800 hover:bg-black/5 dark:hover:bg-white/5 dark:text-neutral-200 text-sm font-medium",
|
"h-7 rounded-full inline-flex items-center justify-center text-neutral-800 hover:bg-black/5 dark:hover:bg-white/5 dark:text-neutral-200 text-sm font-medium",
|
||||||
label ? "w-24 gap-1.5" : "w-14",
|
label ? "w-24 gap-1.5" : "w-14",
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import { cn } from "@/commons";
|
import { cn, displayNpub } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useTransition } from "react";
|
import { useCallback, useTransition } from "react";
|
||||||
import { useUserContext } from "./provider";
|
import { useUserContext } from "./provider";
|
||||||
|
import type { Metadata } from "@/types";
|
||||||
|
import { MenuItem, Menu } from "@tauri-apps/api/menu";
|
||||||
|
|
||||||
export function UserButton({ className }: { className?: string }) {
|
export function UserButton({ className }: { className?: string }) {
|
||||||
const user = useUserContext();
|
const user = useUserContext();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { queryClient } = useRouteContext({ strict: false });
|
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
data: isFollow,
|
data: isFollow,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["status", user.pubkey],
|
queryKey: ["status", user?.pubkey],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
const res = await commands.isContact(user.pubkey);
|
const res = await commands.isContact(user.pubkey);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
@@ -27,38 +32,99 @@ export function UserButton({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
retry: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const toggleFollow = () => {
|
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
|
||||||
startTransition(async () => {
|
e.preventDefault();
|
||||||
|
|
||||||
|
const accounts = await commands.getAccounts();
|
||||||
|
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 ?? "anon";
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(
|
||||||
|
MenuItem.new({
|
||||||
|
text: `Follow as ${name} (${displayNpub(account, 16)})`,
|
||||||
|
action: async () => submit(account),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await Promise.all(list);
|
||||||
|
const menu = await Menu.new({ items });
|
||||||
|
|
||||||
|
await menu.popup().catch((e) => console.error(e));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleFollow = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
// Cancel any outgoing refetches
|
||||||
|
await queryClient.cancelQueries({ queryKey: ["status", user.pubkey] });
|
||||||
|
|
||||||
|
// Optimistically update to the new value
|
||||||
|
queryClient.setQueryData(
|
||||||
|
["status", user.pubkey],
|
||||||
|
(data: boolean) => !data,
|
||||||
|
);
|
||||||
|
|
||||||
const res = await commands.toggleContact(user.pubkey, null);
|
const res = await commands.toggleContact(user.pubkey, null);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
queryClient.setQueryData(
|
|
||||||
["status", user.pubkey],
|
|
||||||
(prev: boolean) => !prev,
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalidate cache
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ["status", user.pubkey],
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
queryClient.setQueryData(["status", user?.pubkey], false);
|
||||||
|
},
|
||||||
|
onSettled: async () => {
|
||||||
|
return await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["status", user?.pubkey],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = (account: string) => {
|
||||||
|
startTransition(async () => {
|
||||||
|
const signer = await commands.hasSigner(account);
|
||||||
|
|
||||||
|
if (signer.status === "ok") {
|
||||||
|
if (!signer.data) {
|
||||||
|
const res = await commands.setSigner(account);
|
||||||
|
|
||||||
|
if (res.status === "error") {
|
||||||
await message(res.error, { kind: "error" });
|
await message(res.error, { kind: "error" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFollow.mutate();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={isPending}
|
disabled={isPending || isLoading}
|
||||||
onClick={() => toggleFollow()}
|
onClick={(e) => showContextMenu(e)}
|
||||||
className={cn("w-max gap-1", className)}
|
className={cn("w-max gap-1", className)}
|
||||||
>
|
>
|
||||||
{isError ? "Error" : null}
|
{isError ? "Error" : null}
|
||||||
|
|||||||
@@ -13,18 +13,17 @@ import { createFileRoute } from '@tanstack/react-router'
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
import { Route as BootstrapRelaysImport } from './routes/bootstrap-relays'
|
|
||||||
import { Route as AppImport } from './routes/_app'
|
import { Route as AppImport } from './routes/_app'
|
||||||
import { Route as NewPostIndexImport } from './routes/new-post/index'
|
import { Route as NewPostIndexImport } from './routes/new-post/index'
|
||||||
import { Route as AppIndexImport } from './routes/_app/index'
|
import { Route as AppIndexImport } from './routes/_app/index'
|
||||||
import { Route as ZapIdImport } from './routes/zap.$id'
|
import { Route as ZapIdImport } from './routes/zap.$id'
|
||||||
|
import { Route as SettingsWalletImport } from './routes/settings/wallet'
|
||||||
|
import { Route as SettingsRelaysImport } from './routes/settings/relays'
|
||||||
|
import { Route as SettingsGeneralImport } from './routes/settings/general'
|
||||||
import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
|
import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
|
||||||
import { Route as IdSetProfileImport } from './routes/$id.set-profile'
|
import { Route as IdSetProfileImport } from './routes/$id.set-profile'
|
||||||
import { Route as IdSetInterestImport } from './routes/$id.set-interest'
|
import { Route as IdSetInterestImport } from './routes/$id.set-interest'
|
||||||
import { Route as IdSetGroupImport } from './routes/$id.set-group'
|
import { Route as IdSetGroupImport } from './routes/$id.set-group'
|
||||||
import { Route as SettingsIdWalletImport } from './routes/settings.$id/wallet'
|
|
||||||
import { Route as SettingsIdRelayImport } from './routes/settings.$id/relay'
|
|
||||||
import { Route as SettingsIdGeneralImport } from './routes/settings.$id/general'
|
|
||||||
import { Route as ColumnsLayoutGlobalImport } from './routes/columns/_layout/global'
|
import { Route as ColumnsLayoutGlobalImport } from './routes/columns/_layout/global'
|
||||||
import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed'
|
import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed'
|
||||||
import { Route as ColumnsLayoutStoriesIdImport } from './routes/columns/_layout/stories.$id'
|
import { Route as ColumnsLayoutStoriesIdImport } from './routes/columns/_layout/stories.$id'
|
||||||
@@ -38,8 +37,8 @@ import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/
|
|||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
const ColumnsImport = createFileRoute('/columns')()
|
const ColumnsImport = createFileRoute('/columns')()
|
||||||
|
const SettingsLazyImport = createFileRoute('/settings')()
|
||||||
const NewLazyImport = createFileRoute('/new')()
|
const NewLazyImport = createFileRoute('/new')()
|
||||||
const SettingsIdLazyImport = createFileRoute('/settings/$id')()
|
|
||||||
const NewAccountWatchLazyImport = createFileRoute('/new-account/watch')()
|
const NewAccountWatchLazyImport = createFileRoute('/new-account/watch')()
|
||||||
const NewAccountImportLazyImport = createFileRoute('/new-account/import')()
|
const NewAccountImportLazyImport = createFileRoute('/new-account/import')()
|
||||||
const NewAccountConnectLazyImport = createFileRoute('/new-account/connect')()
|
const NewAccountConnectLazyImport = createFileRoute('/new-account/connect')()
|
||||||
@@ -76,6 +75,9 @@ const ColumnsLayoutNotificationIdLazyImport = createFileRoute(
|
|||||||
const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute(
|
const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/launchpad/$id',
|
'/columns/_layout/launchpad/$id',
|
||||||
)()
|
)()
|
||||||
|
const ColumnsLayoutDvmIdLazyImport = createFileRoute(
|
||||||
|
'/columns/_layout/dvm/$id',
|
||||||
|
)()
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
@@ -85,20 +87,18 @@ const ColumnsRoute = ColumnsImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const SettingsLazyRoute = SettingsLazyImport.update({
|
||||||
|
id: '/settings',
|
||||||
|
path: '/settings',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any).lazy(() => import('./routes/settings.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
const NewLazyRoute = NewLazyImport.update({
|
const NewLazyRoute = NewLazyImport.update({
|
||||||
id: '/new',
|
id: '/new',
|
||||||
path: '/new',
|
path: '/new',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route))
|
} as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
const BootstrapRelaysRoute = BootstrapRelaysImport.update({
|
|
||||||
id: '/bootstrap-relays',
|
|
||||||
path: '/bootstrap-relays',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/bootstrap-relays.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const AppRoute = AppImport.update({
|
const AppRoute = AppImport.update({
|
||||||
id: '/_app',
|
id: '/_app',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
@@ -118,12 +118,6 @@ const AppIndexRoute = AppIndexImport.update({
|
|||||||
getParentRoute: () => AppRoute,
|
getParentRoute: () => AppRoute,
|
||||||
} as any).lazy(() => import('./routes/_app/index.lazy').then((d) => d.Route))
|
} as any).lazy(() => import('./routes/_app/index.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
const SettingsIdLazyRoute = SettingsIdLazyImport.update({
|
|
||||||
id: '/settings/$id',
|
|
||||||
path: '/settings/$id',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any).lazy(() => import('./routes/settings.$id.lazy').then((d) => d.Route))
|
|
||||||
|
|
||||||
const NewAccountWatchLazyRoute = NewAccountWatchLazyImport.update({
|
const NewAccountWatchLazyRoute = NewAccountWatchLazyImport.update({
|
||||||
id: '/new-account/watch',
|
id: '/new-account/watch',
|
||||||
path: '/new-account/watch',
|
path: '/new-account/watch',
|
||||||
@@ -154,6 +148,30 @@ const ZapIdRoute = ZapIdImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any).lazy(() => import('./routes/zap.$id.lazy').then((d) => d.Route))
|
} as any).lazy(() => import('./routes/zap.$id.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
|
const SettingsWalletRoute = SettingsWalletImport.update({
|
||||||
|
id: '/wallet',
|
||||||
|
path: '/wallet',
|
||||||
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/settings/wallet.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
|
const SettingsRelaysRoute = SettingsRelaysImport.update({
|
||||||
|
id: '/relays',
|
||||||
|
path: '/relays',
|
||||||
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/settings/relays.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
|
const SettingsGeneralRoute = SettingsGeneralImport.update({
|
||||||
|
id: '/general',
|
||||||
|
path: '/general',
|
||||||
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/settings/general.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const ColumnsLayoutRoute = ColumnsLayoutImport.update({
|
const ColumnsLayoutRoute = ColumnsLayoutImport.update({
|
||||||
id: '/_layout',
|
id: '/_layout',
|
||||||
getParentRoute: () => ColumnsRoute,
|
getParentRoute: () => ColumnsRoute,
|
||||||
@@ -239,30 +257,6 @@ const ColumnsLayoutDiscoverInterestsLazyRoute =
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const SettingsIdWalletRoute = SettingsIdWalletImport.update({
|
|
||||||
id: '/wallet',
|
|
||||||
path: '/wallet',
|
|
||||||
getParentRoute: () => SettingsIdLazyRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/settings.$id/wallet.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const SettingsIdRelayRoute = SettingsIdRelayImport.update({
|
|
||||||
id: '/relay',
|
|
||||||
path: '/relay',
|
|
||||||
getParentRoute: () => SettingsIdLazyRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/settings.$id/relay.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const SettingsIdGeneralRoute = SettingsIdGeneralImport.update({
|
|
||||||
id: '/general',
|
|
||||||
path: '/general',
|
|
||||||
getParentRoute: () => SettingsIdLazyRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/settings.$id/general.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
||||||
id: '/global',
|
id: '/global',
|
||||||
path: '/global',
|
path: '/global',
|
||||||
@@ -324,6 +318,14 @@ const ColumnsLayoutLaunchpadIdLazyRoute =
|
|||||||
import('./routes/columns/_layout/launchpad.$id.lazy').then((d) => d.Route),
|
import('./routes/columns/_layout/launchpad.$id.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ColumnsLayoutDvmIdLazyRoute = ColumnsLayoutDvmIdLazyImport.update({
|
||||||
|
id: '/dvm/$id',
|
||||||
|
path: '/dvm/$id',
|
||||||
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/columns/_layout/dvm.$id.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const ColumnsLayoutStoriesIdRoute = ColumnsLayoutStoriesIdImport.update({
|
const ColumnsLayoutStoriesIdRoute = ColumnsLayoutStoriesIdImport.update({
|
||||||
id: '/stories/$id',
|
id: '/stories/$id',
|
||||||
path: '/stories/$id',
|
path: '/stories/$id',
|
||||||
@@ -389,13 +391,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AppImport
|
preLoaderRoute: typeof AppImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/bootstrap-relays': {
|
|
||||||
id: '/bootstrap-relays'
|
|
||||||
path: '/bootstrap-relays'
|
|
||||||
fullPath: '/bootstrap-relays'
|
|
||||||
preLoaderRoute: typeof BootstrapRelaysImport
|
|
||||||
parentRoute: typeof rootRoute
|
|
||||||
}
|
|
||||||
'/new': {
|
'/new': {
|
||||||
id: '/new'
|
id: '/new'
|
||||||
path: '/new'
|
path: '/new'
|
||||||
@@ -403,6 +398,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof NewLazyImport
|
preLoaderRoute: typeof NewLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/settings': {
|
||||||
|
id: '/settings'
|
||||||
|
path: '/settings'
|
||||||
|
fullPath: '/settings'
|
||||||
|
preLoaderRoute: typeof SettingsLazyImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/$id/set-group': {
|
'/$id/set-group': {
|
||||||
id: '/$id/set-group'
|
id: '/$id/set-group'
|
||||||
path: '/$id/set-group'
|
path: '/$id/set-group'
|
||||||
@@ -438,6 +440,27 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutImport
|
preLoaderRoute: typeof ColumnsLayoutImport
|
||||||
parentRoute: typeof ColumnsRoute
|
parentRoute: typeof ColumnsRoute
|
||||||
}
|
}
|
||||||
|
'/settings/general': {
|
||||||
|
id: '/settings/general'
|
||||||
|
path: '/general'
|
||||||
|
fullPath: '/settings/general'
|
||||||
|
preLoaderRoute: typeof SettingsGeneralImport
|
||||||
|
parentRoute: typeof SettingsLazyImport
|
||||||
|
}
|
||||||
|
'/settings/relays': {
|
||||||
|
id: '/settings/relays'
|
||||||
|
path: '/relays'
|
||||||
|
fullPath: '/settings/relays'
|
||||||
|
preLoaderRoute: typeof SettingsRelaysImport
|
||||||
|
parentRoute: typeof SettingsLazyImport
|
||||||
|
}
|
||||||
|
'/settings/wallet': {
|
||||||
|
id: '/settings/wallet'
|
||||||
|
path: '/wallet'
|
||||||
|
fullPath: '/settings/wallet'
|
||||||
|
preLoaderRoute: typeof SettingsWalletImport
|
||||||
|
parentRoute: typeof SettingsLazyImport
|
||||||
|
}
|
||||||
'/zap/$id': {
|
'/zap/$id': {
|
||||||
id: '/zap/$id'
|
id: '/zap/$id'
|
||||||
path: '/zap/$id'
|
path: '/zap/$id'
|
||||||
@@ -466,13 +489,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof NewAccountWatchLazyImport
|
preLoaderRoute: typeof NewAccountWatchLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/settings/$id': {
|
|
||||||
id: '/settings/$id'
|
|
||||||
path: '/settings/$id'
|
|
||||||
fullPath: '/settings/$id'
|
|
||||||
preLoaderRoute: typeof SettingsIdLazyImport
|
|
||||||
parentRoute: typeof rootRoute
|
|
||||||
}
|
|
||||||
'/_app/': {
|
'/_app/': {
|
||||||
id: '/_app/'
|
id: '/_app/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@@ -501,27 +517,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/settings/$id/general': {
|
|
||||||
id: '/settings/$id/general'
|
|
||||||
path: '/general'
|
|
||||||
fullPath: '/settings/$id/general'
|
|
||||||
preLoaderRoute: typeof SettingsIdGeneralImport
|
|
||||||
parentRoute: typeof SettingsIdLazyImport
|
|
||||||
}
|
|
||||||
'/settings/$id/relay': {
|
|
||||||
id: '/settings/$id/relay'
|
|
||||||
path: '/relay'
|
|
||||||
fullPath: '/settings/$id/relay'
|
|
||||||
preLoaderRoute: typeof SettingsIdRelayImport
|
|
||||||
parentRoute: typeof SettingsIdLazyImport
|
|
||||||
}
|
|
||||||
'/settings/$id/wallet': {
|
|
||||||
id: '/settings/$id/wallet'
|
|
||||||
path: '/wallet'
|
|
||||||
fullPath: '/settings/$id/wallet'
|
|
||||||
preLoaderRoute: typeof SettingsIdWalletImport
|
|
||||||
parentRoute: typeof SettingsIdLazyImport
|
|
||||||
}
|
|
||||||
'/columns/_layout/discover-interests': {
|
'/columns/_layout/discover-interests': {
|
||||||
id: '/columns/_layout/discover-interests'
|
id: '/columns/_layout/discover-interests'
|
||||||
path: '/discover-interests'
|
path: '/discover-interests'
|
||||||
@@ -613,6 +608,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutStoriesIdImport
|
preLoaderRoute: typeof ColumnsLayoutStoriesIdImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
|
'/columns/_layout/dvm/$id': {
|
||||||
|
id: '/columns/_layout/dvm/$id'
|
||||||
|
path: '/dvm/$id'
|
||||||
|
fullPath: '/columns/dvm/$id'
|
||||||
|
preLoaderRoute: typeof ColumnsLayoutDvmIdLazyImport
|
||||||
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
|
}
|
||||||
'/columns/_layout/launchpad/$id': {
|
'/columns/_layout/launchpad/$id': {
|
||||||
id: '/columns/_layout/launchpad/$id'
|
id: '/columns/_layout/launchpad/$id'
|
||||||
path: '/launchpad/$id'
|
path: '/launchpad/$id'
|
||||||
@@ -663,6 +665,22 @@ const AppRouteChildren: AppRouteChildren = {
|
|||||||
|
|
||||||
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
||||||
|
|
||||||
|
interface SettingsLazyRouteChildren {
|
||||||
|
SettingsGeneralRoute: typeof SettingsGeneralRoute
|
||||||
|
SettingsRelaysRoute: typeof SettingsRelaysRoute
|
||||||
|
SettingsWalletRoute: typeof SettingsWalletRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsLazyRouteChildren: SettingsLazyRouteChildren = {
|
||||||
|
SettingsGeneralRoute: SettingsGeneralRoute,
|
||||||
|
SettingsRelaysRoute: SettingsRelaysRoute,
|
||||||
|
SettingsWalletRoute: SettingsWalletRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren(
|
||||||
|
SettingsLazyRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
interface ColumnsLayoutCreateNewsfeedRouteChildren {
|
interface ColumnsLayoutCreateNewsfeedRouteChildren {
|
||||||
ColumnsLayoutCreateNewsfeedF2fRoute: typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
ColumnsLayoutCreateNewsfeedF2fRoute: typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
ColumnsLayoutCreateNewsfeedUsersRoute: typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
ColumnsLayoutCreateNewsfeedUsersRoute: typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
@@ -694,6 +712,7 @@ interface ColumnsLayoutRouteChildren {
|
|||||||
ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute
|
ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute
|
||||||
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
||||||
|
ColumnsLayoutDvmIdLazyRoute: typeof ColumnsLayoutDvmIdLazyRoute
|
||||||
ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute
|
ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
ColumnsLayoutRelaysUrlLazyRoute: typeof ColumnsLayoutRelaysUrlLazyRoute
|
ColumnsLayoutRelaysUrlLazyRoute: typeof ColumnsLayoutRelaysUrlLazyRoute
|
||||||
@@ -718,6 +737,7 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
|||||||
ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute,
|
ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute,
|
||||||
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
||||||
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
||||||
|
ColumnsLayoutDvmIdLazyRoute: ColumnsLayoutDvmIdLazyRoute,
|
||||||
ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute,
|
ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute,
|
||||||
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
||||||
ColumnsLayoutRelaysUrlLazyRoute: ColumnsLayoutRelaysUrlLazyRoute,
|
ColumnsLayoutRelaysUrlLazyRoute: ColumnsLayoutRelaysUrlLazyRoute,
|
||||||
@@ -740,42 +760,25 @@ const ColumnsRouteChildren: ColumnsRouteChildren = {
|
|||||||
const ColumnsRouteWithChildren =
|
const ColumnsRouteWithChildren =
|
||||||
ColumnsRoute._addFileChildren(ColumnsRouteChildren)
|
ColumnsRoute._addFileChildren(ColumnsRouteChildren)
|
||||||
|
|
||||||
interface SettingsIdLazyRouteChildren {
|
|
||||||
SettingsIdGeneralRoute: typeof SettingsIdGeneralRoute
|
|
||||||
SettingsIdRelayRoute: typeof SettingsIdRelayRoute
|
|
||||||
SettingsIdWalletRoute: typeof SettingsIdWalletRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
const SettingsIdLazyRouteChildren: SettingsIdLazyRouteChildren = {
|
|
||||||
SettingsIdGeneralRoute: SettingsIdGeneralRoute,
|
|
||||||
SettingsIdRelayRoute: SettingsIdRelayRoute,
|
|
||||||
SettingsIdWalletRoute: SettingsIdWalletRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const SettingsIdLazyRouteWithChildren = SettingsIdLazyRoute._addFileChildren(
|
|
||||||
SettingsIdLazyRouteChildren,
|
|
||||||
)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'': typeof AppRouteWithChildren
|
'': typeof AppRouteWithChildren
|
||||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
|
||||||
'/new': typeof NewLazyRoute
|
'/new': typeof NewLazyRoute
|
||||||
|
'/settings': typeof SettingsLazyRouteWithChildren
|
||||||
'/$id/set-group': typeof IdSetGroupRoute
|
'/$id/set-group': typeof IdSetGroupRoute
|
||||||
'/$id/set-interest': typeof IdSetInterestRoute
|
'/$id/set-interest': typeof IdSetInterestRoute
|
||||||
'/$id/set-profile': typeof IdSetProfileRoute
|
'/$id/set-profile': typeof IdSetProfileRoute
|
||||||
'/columns': typeof ColumnsLayoutRouteWithChildren
|
'/columns': typeof ColumnsLayoutRouteWithChildren
|
||||||
|
'/settings/general': typeof SettingsGeneralRoute
|
||||||
|
'/settings/relays': typeof SettingsRelaysRoute
|
||||||
|
'/settings/wallet': typeof SettingsWalletRoute
|
||||||
'/zap/$id': typeof ZapIdRoute
|
'/zap/$id': typeof ZapIdRoute
|
||||||
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
||||||
'/new-account/import': typeof NewAccountImportLazyRoute
|
'/new-account/import': typeof NewAccountImportLazyRoute
|
||||||
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
||||||
'/settings/$id': typeof SettingsIdLazyRouteWithChildren
|
|
||||||
'/': typeof AppIndexRoute
|
'/': typeof AppIndexRoute
|
||||||
'/new-post': typeof NewPostIndexRoute
|
'/new-post': typeof NewPostIndexRoute
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
|
||||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
|
||||||
'/columns/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute
|
'/columns/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute
|
||||||
'/columns/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute
|
'/columns/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute
|
||||||
'/columns/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute
|
'/columns/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute
|
||||||
@@ -789,6 +792,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||||
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||||
|
'/columns/dvm/$id': typeof ColumnsLayoutDvmIdLazyRoute
|
||||||
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
'/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute
|
'/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute
|
||||||
@@ -797,24 +801,23 @@ export interface FileRoutesByFullPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
|
||||||
'/new': typeof NewLazyRoute
|
'/new': typeof NewLazyRoute
|
||||||
|
'/settings': typeof SettingsLazyRouteWithChildren
|
||||||
'/$id/set-group': typeof IdSetGroupRoute
|
'/$id/set-group': typeof IdSetGroupRoute
|
||||||
'/$id/set-interest': typeof IdSetInterestRoute
|
'/$id/set-interest': typeof IdSetInterestRoute
|
||||||
'/$id/set-profile': typeof IdSetProfileRoute
|
'/$id/set-profile': typeof IdSetProfileRoute
|
||||||
'/columns': typeof ColumnsLayoutRouteWithChildren
|
'/columns': typeof ColumnsLayoutRouteWithChildren
|
||||||
|
'/settings/general': typeof SettingsGeneralRoute
|
||||||
|
'/settings/relays': typeof SettingsRelaysRoute
|
||||||
|
'/settings/wallet': typeof SettingsWalletRoute
|
||||||
'/zap/$id': typeof ZapIdRoute
|
'/zap/$id': typeof ZapIdRoute
|
||||||
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
||||||
'/new-account/import': typeof NewAccountImportLazyRoute
|
'/new-account/import': typeof NewAccountImportLazyRoute
|
||||||
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
||||||
'/settings/$id': typeof SettingsIdLazyRouteWithChildren
|
|
||||||
'/': typeof AppIndexRoute
|
'/': typeof AppIndexRoute
|
||||||
'/new-post': typeof NewPostIndexRoute
|
'/new-post': typeof NewPostIndexRoute
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
|
||||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
|
||||||
'/columns/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute
|
'/columns/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute
|
||||||
'/columns/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute
|
'/columns/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute
|
||||||
'/columns/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute
|
'/columns/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute
|
||||||
@@ -828,6 +831,7 @@ export interface FileRoutesByTo {
|
|||||||
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||||
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||||
|
'/columns/dvm/$id': typeof ColumnsLayoutDvmIdLazyRoute
|
||||||
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
'/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute
|
'/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute
|
||||||
@@ -838,25 +842,24 @@ export interface FileRoutesByTo {
|
|||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRoute
|
__root__: typeof rootRoute
|
||||||
'/_app': typeof AppRouteWithChildren
|
'/_app': typeof AppRouteWithChildren
|
||||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
|
||||||
'/new': typeof NewLazyRoute
|
'/new': typeof NewLazyRoute
|
||||||
|
'/settings': typeof SettingsLazyRouteWithChildren
|
||||||
'/$id/set-group': typeof IdSetGroupRoute
|
'/$id/set-group': typeof IdSetGroupRoute
|
||||||
'/$id/set-interest': typeof IdSetInterestRoute
|
'/$id/set-interest': typeof IdSetInterestRoute
|
||||||
'/$id/set-profile': typeof IdSetProfileRoute
|
'/$id/set-profile': typeof IdSetProfileRoute
|
||||||
'/columns': typeof ColumnsRouteWithChildren
|
'/columns': typeof ColumnsRouteWithChildren
|
||||||
'/columns/_layout': typeof ColumnsLayoutRouteWithChildren
|
'/columns/_layout': typeof ColumnsLayoutRouteWithChildren
|
||||||
|
'/settings/general': typeof SettingsGeneralRoute
|
||||||
|
'/settings/relays': typeof SettingsRelaysRoute
|
||||||
|
'/settings/wallet': typeof SettingsWalletRoute
|
||||||
'/zap/$id': typeof ZapIdRoute
|
'/zap/$id': typeof ZapIdRoute
|
||||||
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
||||||
'/new-account/import': typeof NewAccountImportLazyRoute
|
'/new-account/import': typeof NewAccountImportLazyRoute
|
||||||
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
||||||
'/settings/$id': typeof SettingsIdLazyRouteWithChildren
|
|
||||||
'/_app/': typeof AppIndexRoute
|
'/_app/': typeof AppIndexRoute
|
||||||
'/new-post/': typeof NewPostIndexRoute
|
'/new-post/': typeof NewPostIndexRoute
|
||||||
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
|
||||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
|
||||||
'/columns/_layout/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute
|
'/columns/_layout/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute
|
||||||
'/columns/_layout/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute
|
'/columns/_layout/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute
|
||||||
'/columns/_layout/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute
|
'/columns/_layout/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute
|
||||||
@@ -870,6 +873,7 @@ export interface FileRoutesById {
|
|||||||
'/columns/_layout/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
'/columns/_layout/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||||
'/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/dvm/$id': typeof ColumnsLayoutDvmIdLazyRoute
|
||||||
'/columns/_layout/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
'/columns/_layout/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
'/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
'/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
'/columns/_layout/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute
|
'/columns/_layout/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute
|
||||||
@@ -881,24 +885,23 @@ export interface FileRouteTypes {
|
|||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths:
|
fullPaths:
|
||||||
| ''
|
| ''
|
||||||
| '/bootstrap-relays'
|
|
||||||
| '/new'
|
| '/new'
|
||||||
|
| '/settings'
|
||||||
| '/$id/set-group'
|
| '/$id/set-group'
|
||||||
| '/$id/set-interest'
|
| '/$id/set-interest'
|
||||||
| '/$id/set-profile'
|
| '/$id/set-profile'
|
||||||
| '/columns'
|
| '/columns'
|
||||||
|
| '/settings/general'
|
||||||
|
| '/settings/relays'
|
||||||
|
| '/settings/wallet'
|
||||||
| '/zap/$id'
|
| '/zap/$id'
|
||||||
| '/new-account/connect'
|
| '/new-account/connect'
|
||||||
| '/new-account/import'
|
| '/new-account/import'
|
||||||
| '/new-account/watch'
|
| '/new-account/watch'
|
||||||
| '/settings/$id'
|
|
||||||
| '/'
|
| '/'
|
||||||
| '/new-post'
|
| '/new-post'
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
| '/columns/global'
|
| '/columns/global'
|
||||||
| '/settings/$id/general'
|
|
||||||
| '/settings/$id/relay'
|
|
||||||
| '/settings/$id/wallet'
|
|
||||||
| '/columns/discover-interests'
|
| '/columns/discover-interests'
|
||||||
| '/columns/discover-newsfeeds'
|
| '/columns/discover-newsfeeds'
|
||||||
| '/columns/discover-relays'
|
| '/columns/discover-relays'
|
||||||
@@ -912,6 +915,7 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/interests/$id'
|
| '/columns/interests/$id'
|
||||||
| '/columns/newsfeed/$id'
|
| '/columns/newsfeed/$id'
|
||||||
| '/columns/stories/$id'
|
| '/columns/stories/$id'
|
||||||
|
| '/columns/dvm/$id'
|
||||||
| '/columns/launchpad/$id'
|
| '/columns/launchpad/$id'
|
||||||
| '/columns/notification/$id'
|
| '/columns/notification/$id'
|
||||||
| '/columns/relays/$url'
|
| '/columns/relays/$url'
|
||||||
@@ -919,24 +923,23 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/users/$id'
|
| '/columns/users/$id'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/bootstrap-relays'
|
|
||||||
| '/new'
|
| '/new'
|
||||||
|
| '/settings'
|
||||||
| '/$id/set-group'
|
| '/$id/set-group'
|
||||||
| '/$id/set-interest'
|
| '/$id/set-interest'
|
||||||
| '/$id/set-profile'
|
| '/$id/set-profile'
|
||||||
| '/columns'
|
| '/columns'
|
||||||
|
| '/settings/general'
|
||||||
|
| '/settings/relays'
|
||||||
|
| '/settings/wallet'
|
||||||
| '/zap/$id'
|
| '/zap/$id'
|
||||||
| '/new-account/connect'
|
| '/new-account/connect'
|
||||||
| '/new-account/import'
|
| '/new-account/import'
|
||||||
| '/new-account/watch'
|
| '/new-account/watch'
|
||||||
| '/settings/$id'
|
|
||||||
| '/'
|
| '/'
|
||||||
| '/new-post'
|
| '/new-post'
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
| '/columns/global'
|
| '/columns/global'
|
||||||
| '/settings/$id/general'
|
|
||||||
| '/settings/$id/relay'
|
|
||||||
| '/settings/$id/wallet'
|
|
||||||
| '/columns/discover-interests'
|
| '/columns/discover-interests'
|
||||||
| '/columns/discover-newsfeeds'
|
| '/columns/discover-newsfeeds'
|
||||||
| '/columns/discover-relays'
|
| '/columns/discover-relays'
|
||||||
@@ -950,6 +953,7 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/interests/$id'
|
| '/columns/interests/$id'
|
||||||
| '/columns/newsfeed/$id'
|
| '/columns/newsfeed/$id'
|
||||||
| '/columns/stories/$id'
|
| '/columns/stories/$id'
|
||||||
|
| '/columns/dvm/$id'
|
||||||
| '/columns/launchpad/$id'
|
| '/columns/launchpad/$id'
|
||||||
| '/columns/notification/$id'
|
| '/columns/notification/$id'
|
||||||
| '/columns/relays/$url'
|
| '/columns/relays/$url'
|
||||||
@@ -958,25 +962,24 @@ export interface FileRouteTypes {
|
|||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/_app'
|
| '/_app'
|
||||||
| '/bootstrap-relays'
|
|
||||||
| '/new'
|
| '/new'
|
||||||
|
| '/settings'
|
||||||
| '/$id/set-group'
|
| '/$id/set-group'
|
||||||
| '/$id/set-interest'
|
| '/$id/set-interest'
|
||||||
| '/$id/set-profile'
|
| '/$id/set-profile'
|
||||||
| '/columns'
|
| '/columns'
|
||||||
| '/columns/_layout'
|
| '/columns/_layout'
|
||||||
|
| '/settings/general'
|
||||||
|
| '/settings/relays'
|
||||||
|
| '/settings/wallet'
|
||||||
| '/zap/$id'
|
| '/zap/$id'
|
||||||
| '/new-account/connect'
|
| '/new-account/connect'
|
||||||
| '/new-account/import'
|
| '/new-account/import'
|
||||||
| '/new-account/watch'
|
| '/new-account/watch'
|
||||||
| '/settings/$id'
|
|
||||||
| '/_app/'
|
| '/_app/'
|
||||||
| '/new-post/'
|
| '/new-post/'
|
||||||
| '/columns/_layout/create-newsfeed'
|
| '/columns/_layout/create-newsfeed'
|
||||||
| '/columns/_layout/global'
|
| '/columns/_layout/global'
|
||||||
| '/settings/$id/general'
|
|
||||||
| '/settings/$id/relay'
|
|
||||||
| '/settings/$id/wallet'
|
|
||||||
| '/columns/_layout/discover-interests'
|
| '/columns/_layout/discover-interests'
|
||||||
| '/columns/_layout/discover-newsfeeds'
|
| '/columns/_layout/discover-newsfeeds'
|
||||||
| '/columns/_layout/discover-relays'
|
| '/columns/_layout/discover-relays'
|
||||||
@@ -990,6 +993,7 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/_layout/interests/$id'
|
| '/columns/_layout/interests/$id'
|
||||||
| '/columns/_layout/newsfeed/$id'
|
| '/columns/_layout/newsfeed/$id'
|
||||||
| '/columns/_layout/stories/$id'
|
| '/columns/_layout/stories/$id'
|
||||||
|
| '/columns/_layout/dvm/$id'
|
||||||
| '/columns/_layout/launchpad/$id'
|
| '/columns/_layout/launchpad/$id'
|
||||||
| '/columns/_layout/notification/$id'
|
| '/columns/_layout/notification/$id'
|
||||||
| '/columns/_layout/relays/$url'
|
| '/columns/_layout/relays/$url'
|
||||||
@@ -1000,8 +1004,8 @@ export interface FileRouteTypes {
|
|||||||
|
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
AppRoute: typeof AppRouteWithChildren
|
AppRoute: typeof AppRouteWithChildren
|
||||||
BootstrapRelaysRoute: typeof BootstrapRelaysRoute
|
|
||||||
NewLazyRoute: typeof NewLazyRoute
|
NewLazyRoute: typeof NewLazyRoute
|
||||||
|
SettingsLazyRoute: typeof SettingsLazyRouteWithChildren
|
||||||
IdSetGroupRoute: typeof IdSetGroupRoute
|
IdSetGroupRoute: typeof IdSetGroupRoute
|
||||||
IdSetInterestRoute: typeof IdSetInterestRoute
|
IdSetInterestRoute: typeof IdSetInterestRoute
|
||||||
IdSetProfileRoute: typeof IdSetProfileRoute
|
IdSetProfileRoute: typeof IdSetProfileRoute
|
||||||
@@ -1010,14 +1014,13 @@ export interface RootRouteChildren {
|
|||||||
NewAccountConnectLazyRoute: typeof NewAccountConnectLazyRoute
|
NewAccountConnectLazyRoute: typeof NewAccountConnectLazyRoute
|
||||||
NewAccountImportLazyRoute: typeof NewAccountImportLazyRoute
|
NewAccountImportLazyRoute: typeof NewAccountImportLazyRoute
|
||||||
NewAccountWatchLazyRoute: typeof NewAccountWatchLazyRoute
|
NewAccountWatchLazyRoute: typeof NewAccountWatchLazyRoute
|
||||||
SettingsIdLazyRoute: typeof SettingsIdLazyRouteWithChildren
|
|
||||||
NewPostIndexRoute: typeof NewPostIndexRoute
|
NewPostIndexRoute: typeof NewPostIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
AppRoute: AppRouteWithChildren,
|
AppRoute: AppRouteWithChildren,
|
||||||
BootstrapRelaysRoute: BootstrapRelaysRoute,
|
|
||||||
NewLazyRoute: NewLazyRoute,
|
NewLazyRoute: NewLazyRoute,
|
||||||
|
SettingsLazyRoute: SettingsLazyRouteWithChildren,
|
||||||
IdSetGroupRoute: IdSetGroupRoute,
|
IdSetGroupRoute: IdSetGroupRoute,
|
||||||
IdSetInterestRoute: IdSetInterestRoute,
|
IdSetInterestRoute: IdSetInterestRoute,
|
||||||
IdSetProfileRoute: IdSetProfileRoute,
|
IdSetProfileRoute: IdSetProfileRoute,
|
||||||
@@ -1026,7 +1029,6 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
NewAccountConnectLazyRoute: NewAccountConnectLazyRoute,
|
NewAccountConnectLazyRoute: NewAccountConnectLazyRoute,
|
||||||
NewAccountImportLazyRoute: NewAccountImportLazyRoute,
|
NewAccountImportLazyRoute: NewAccountImportLazyRoute,
|
||||||
NewAccountWatchLazyRoute: NewAccountWatchLazyRoute,
|
NewAccountWatchLazyRoute: NewAccountWatchLazyRoute,
|
||||||
SettingsIdLazyRoute: SettingsIdLazyRouteWithChildren,
|
|
||||||
NewPostIndexRoute: NewPostIndexRoute,
|
NewPostIndexRoute: NewPostIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,8 +1045,8 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "__root.tsx",
|
"filePath": "__root.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/_app",
|
"/_app",
|
||||||
"/bootstrap-relays",
|
|
||||||
"/new",
|
"/new",
|
||||||
|
"/settings",
|
||||||
"/$id/set-group",
|
"/$id/set-group",
|
||||||
"/$id/set-interest",
|
"/$id/set-interest",
|
||||||
"/$id/set-profile",
|
"/$id/set-profile",
|
||||||
@@ -1053,7 +1055,6 @@ export const routeTree = rootRoute
|
|||||||
"/new-account/connect",
|
"/new-account/connect",
|
||||||
"/new-account/import",
|
"/new-account/import",
|
||||||
"/new-account/watch",
|
"/new-account/watch",
|
||||||
"/settings/$id",
|
|
||||||
"/new-post/"
|
"/new-post/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1063,12 +1064,17 @@ export const routeTree = rootRoute
|
|||||||
"/_app/"
|
"/_app/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/bootstrap-relays": {
|
|
||||||
"filePath": "bootstrap-relays.tsx"
|
|
||||||
},
|
|
||||||
"/new": {
|
"/new": {
|
||||||
"filePath": "new.lazy.tsx"
|
"filePath": "new.lazy.tsx"
|
||||||
},
|
},
|
||||||
|
"/settings": {
|
||||||
|
"filePath": "settings.lazy.tsx",
|
||||||
|
"children": [
|
||||||
|
"/settings/general",
|
||||||
|
"/settings/relays",
|
||||||
|
"/settings/wallet"
|
||||||
|
]
|
||||||
|
},
|
||||||
"/$id/set-group": {
|
"/$id/set-group": {
|
||||||
"filePath": "$id.set-group.tsx"
|
"filePath": "$id.set-group.tsx"
|
||||||
},
|
},
|
||||||
@@ -1101,6 +1107,7 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/interests/$id",
|
"/columns/_layout/interests/$id",
|
||||||
"/columns/_layout/newsfeed/$id",
|
"/columns/_layout/newsfeed/$id",
|
||||||
"/columns/_layout/stories/$id",
|
"/columns/_layout/stories/$id",
|
||||||
|
"/columns/_layout/dvm/$id",
|
||||||
"/columns/_layout/launchpad/$id",
|
"/columns/_layout/launchpad/$id",
|
||||||
"/columns/_layout/notification/$id",
|
"/columns/_layout/notification/$id",
|
||||||
"/columns/_layout/relays/$url",
|
"/columns/_layout/relays/$url",
|
||||||
@@ -1108,6 +1115,18 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/users/$id"
|
"/columns/_layout/users/$id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/settings/general": {
|
||||||
|
"filePath": "settings/general.tsx",
|
||||||
|
"parent": "/settings"
|
||||||
|
},
|
||||||
|
"/settings/relays": {
|
||||||
|
"filePath": "settings/relays.tsx",
|
||||||
|
"parent": "/settings"
|
||||||
|
},
|
||||||
|
"/settings/wallet": {
|
||||||
|
"filePath": "settings/wallet.tsx",
|
||||||
|
"parent": "/settings"
|
||||||
|
},
|
||||||
"/zap/$id": {
|
"/zap/$id": {
|
||||||
"filePath": "zap.$id.tsx"
|
"filePath": "zap.$id.tsx"
|
||||||
},
|
},
|
||||||
@@ -1120,14 +1139,6 @@ export const routeTree = rootRoute
|
|||||||
"/new-account/watch": {
|
"/new-account/watch": {
|
||||||
"filePath": "new-account/watch.lazy.tsx"
|
"filePath": "new-account/watch.lazy.tsx"
|
||||||
},
|
},
|
||||||
"/settings/$id": {
|
|
||||||
"filePath": "settings.$id.lazy.tsx",
|
|
||||||
"children": [
|
|
||||||
"/settings/$id/general",
|
|
||||||
"/settings/$id/relay",
|
|
||||||
"/settings/$id/wallet"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/_app/": {
|
"/_app/": {
|
||||||
"filePath": "_app/index.tsx",
|
"filePath": "_app/index.tsx",
|
||||||
"parent": "/_app"
|
"parent": "/_app"
|
||||||
@@ -1147,18 +1158,6 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/global.tsx",
|
"filePath": "columns/_layout/global.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/settings/$id/general": {
|
|
||||||
"filePath": "settings.$id/general.tsx",
|
|
||||||
"parent": "/settings/$id"
|
|
||||||
},
|
|
||||||
"/settings/$id/relay": {
|
|
||||||
"filePath": "settings.$id/relay.tsx",
|
|
||||||
"parent": "/settings/$id"
|
|
||||||
},
|
|
||||||
"/settings/$id/wallet": {
|
|
||||||
"filePath": "settings.$id/wallet.tsx",
|
|
||||||
"parent": "/settings/$id"
|
|
||||||
},
|
|
||||||
"/columns/_layout/discover-interests": {
|
"/columns/_layout/discover-interests": {
|
||||||
"filePath": "columns/_layout/discover-interests.lazy.tsx",
|
"filePath": "columns/_layout/discover-interests.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
@@ -1211,6 +1210,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/stories.$id.tsx",
|
"filePath": "columns/_layout/stories.$id.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
|
"/columns/_layout/dvm/$id": {
|
||||||
|
"filePath": "columns/_layout/dvm.$id.lazy.tsx",
|
||||||
|
"parent": "/columns/_layout"
|
||||||
|
},
|
||||||
"/columns/_layout/launchpad/$id": {
|
"/columns/_layout/launchpad/$id": {
|
||||||
"filePath": "columns/_layout/launchpad.$id.lazy.tsx",
|
"filePath": "columns/_layout/launchpad.$id.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ function Account({ pubkey }: { pubkey: string }) {
|
|||||||
PredefinedMenuItem.new({ item: "Separator" }),
|
PredefinedMenuItem.new({ item: "Separator" }),
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
text: "Settings",
|
text: "Settings",
|
||||||
action: () => LumeWindow.openSettings(pubkey),
|
action: () => LumeWindow.openSettings(),
|
||||||
}),
|
}),
|
||||||
PredefinedMenuItem.new({ item: "Separator" }),
|
PredefinedMenuItem.new({ item: "Separator" }),
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
|
|||||||
@@ -1,14 +1,41 @@
|
|||||||
import { commands } from '@/commands.gen'
|
import { commands } from "@/commands.gen";
|
||||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
import { ask } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
|
import { check } from "@tauri-apps/plugin-updater";
|
||||||
|
|
||||||
export const Route = createFileRoute('/_app')({
|
async function checkForAppUpdates() {
|
||||||
beforeLoad: async () => {
|
const update = await check();
|
||||||
const accounts = await commands.getAccounts()
|
|
||||||
|
|
||||||
if (!accounts.length) {
|
if (update?.available) {
|
||||||
throw redirect({ to: '/new', replace: true })
|
const yes = await ask(
|
||||||
|
`Update to ${update.version} is available!\n\nRelease notes: ${update.body}`,
|
||||||
|
{
|
||||||
|
title: "Update Available",
|
||||||
|
kind: "info",
|
||||||
|
okLabel: "Update",
|
||||||
|
cancelLabel: "Cancel",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (yes) {
|
||||||
|
await update.downloadAndInstall();
|
||||||
|
await relaunch();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { accounts }
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_app")({
|
||||||
|
beforeLoad: async () => {
|
||||||
|
await checkForAppUpdates();
|
||||||
|
const accounts = await commands.getAccounts();
|
||||||
|
|
||||||
|
if (!accounts.length) {
|
||||||
|
throw redirect({ to: "/new", replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { accounts };
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
import { commands } from "@/commands.gen";
|
|
||||||
import { GoBack } from "@/components";
|
|
||||||
import { Frame } from "@/components/frame";
|
|
||||||
import { Spinner } from "@/components/spinner";
|
|
||||||
import { ArrowLeft, Plus, X } from "@phosphor-icons/react";
|
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
|
||||||
import { useEffect, useState, useTransition } from "react";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/bootstrap-relays")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const bootstrapRelays = Route.useLoaderData();
|
|
||||||
|
|
||||||
const [relays, setRelays] = useState<string[]>([]);
|
|
||||||
const [newRelay, setNewRelay] = useState("");
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const add = () => {
|
|
||||||
try {
|
|
||||||
let url = newRelay;
|
|
||||||
|
|
||||||
if (!url.startsWith("wss://")) {
|
|
||||||
url = `wss://${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate URL
|
|
||||||
const relay = new URL(url);
|
|
||||||
|
|
||||||
// Update
|
|
||||||
setRelays((prev) => [...prev, relay.toString()]);
|
|
||||||
setNewRelay("");
|
|
||||||
} catch {
|
|
||||||
message("URL is not valid.", { kind: "error" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const remove = (relay: string) => {
|
|
||||||
setRelays((prev) => prev.filter((item) => item !== relay));
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
startTransition(async () => {
|
|
||||||
if (!relays.length) {
|
|
||||||
await message("You need to add at least 1 relay", {
|
|
||||||
title: "Manage Relays",
|
|
||||||
kind: "info",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const merged = relays.join("\r\n");
|
|
||||||
const res = await commands.setBootstrapRelays(merged);
|
|
||||||
|
|
||||||
if (res.status === "ok") {
|
|
||||||
return await relaunch();
|
|
||||||
} else {
|
|
||||||
await message(res.error, {
|
|
||||||
title: "Manage Relays",
|
|
||||||
kind: "error",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRelays(bootstrapRelays);
|
|
||||||
}, [bootstrapRelays]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="relative size-full flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<div className="w-[320px] flex flex-col gap-8">
|
|
||||||
<div className="flex flex-col gap-1 text-center">
|
|
||||||
<h1 className="leading-tight text-xl font-semibold">Manage Relays</h1>
|
|
||||||
<p className="text-sm text-neutral-700 dark:text-neutral-300">
|
|
||||||
The default relays that Lume will connected.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Frame
|
|
||||||
className="flex flex-col gap-3 p-3 rounded-xl overflow-hidden"
|
|
||||||
shadow
|
|
||||||
>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<input
|
|
||||||
name="relay"
|
|
||||||
type="text"
|
|
||||||
placeholder="ex: relay.nostr.net, ..."
|
|
||||||
value={newRelay}
|
|
||||||
onChange={(e) => setNewRelay(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") add();
|
|
||||||
}}
|
|
||||||
className="flex-1 px-3 rounded-lg h-9 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
onClick={() => add()}
|
|
||||||
className="inline-flex items-center justify-center size-9 rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
|
||||||
>
|
|
||||||
<Plus className="size-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{relays.map((relay) => (
|
|
||||||
<div
|
|
||||||
key={relay}
|
|
||||||
className="flex items-center justify-between h-9 px-2 rounded-lg bg-neutral-100 dark:bg-neutral-900"
|
|
||||||
>
|
|
||||||
<div className="text-sm font-medium">{relay}</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => remove(relay)}
|
|
||||||
className="inline-flex items-center justify-center rounded-md size-7 text-neutral-700 dark:text-white/20 hover:bg-black/10 dark:hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<X className="size-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-neutral-600 dark:text-neutral-400">
|
|
||||||
<p>
|
|
||||||
Lume is heavily depend on Negentropy for syncing data. You need
|
|
||||||
to use at least 1 relay that support Negentropy. If you not
|
|
||||||
sure, you can keep using the default relay list.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Frame>
|
|
||||||
<div className="flex flex-col items-center gap-1">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => submit()}
|
|
||||||
disabled={isPending || !relays.length}
|
|
||||||
className="inline-flex items-center justify-center w-full h-9 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{isPending ? <Spinner /> : "Save & Restart"}
|
|
||||||
</button>
|
|
||||||
<span className="mt-2 w-full text-sm text-neutral-600 dark:text-neutral-400 inline-flex items-center justify-center">
|
|
||||||
Lume will relaunch after saving.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<GoBack className="fixed top-11 left-2 flex items-center gap-1.5 text-sm font-medium">
|
|
||||||
<ArrowLeft className="size-5" />
|
|
||||||
Back
|
|
||||||
</GoBack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { commands } from "@/commands.gen";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/bootstrap-relays")({
|
|
||||||
loader: async () => {
|
|
||||||
const res = await commands.getBootstrapRelays();
|
|
||||||
|
|
||||||
if (res.status === "ok") {
|
|
||||||
return res.data.map((item) => item.replace(",", ""));
|
|
||||||
} else {
|
|
||||||
throw new Error(res.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,16 +1,8 @@
|
|||||||
import { cn } from "@/commons";
|
import { cn } from "@/commons";
|
||||||
import type { ColumnRouteSearch } from "@/types";
|
|
||||||
import { Link, Outlet } from "@tanstack/react-router";
|
import { Link, Outlet } from "@tanstack/react-router";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/create-newsfeed")({
|
export const Route = createFileRoute("/columns/_layout/create-newsfeed")({
|
||||||
validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
|
|
||||||
return {
|
|
||||||
account: search.account,
|
|
||||||
label: search.label,
|
|
||||||
name: search.name,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -143,12 +143,14 @@ function Screen() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<div className="mb-3 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="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
<p className="text-sm text-center">{error?.message ?? "Error"}</p>
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
|
{error?.message ?? "Error"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : !data?.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 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="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
<p className="text-sm text-center">
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
Nothing to show yet, you can use Lume more and comeback lack to
|
Nothing to show yet, you can use Lume more and comeback lack to
|
||||||
see new events.
|
see new events.
|
||||||
</p>
|
</p>
|
||||||
@@ -156,6 +158,13 @@ function Screen() {
|
|||||||
) : (
|
) : (
|
||||||
data?.map((item) => renderItem(item))
|
data?.map((item) => renderItem(item))
|
||||||
)}
|
)}
|
||||||
|
<div className="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
|
Lume running sync in the background,
|
||||||
|
<br />
|
||||||
|
the more you use the more event you see.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{hasNextPage ? (
|
{hasNextPage ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -137,12 +137,14 @@ function Screen() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<div className="mb-3 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="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
<p className="text-sm text-center">{error?.message ?? "Error"}</p>
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
|
{error?.message ?? "Error"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : !data?.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 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="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
<p className="text-sm text-center">
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
Nothing to show yet, you can use Lume more and comeback lack to
|
Nothing to show yet, you can use Lume more and comeback lack to
|
||||||
see new events.
|
see new events.
|
||||||
</p>
|
</p>
|
||||||
@@ -150,6 +152,13 @@ function Screen() {
|
|||||||
) : (
|
) : (
|
||||||
data?.map((item) => renderItem(item))
|
data?.map((item) => renderItem(item))
|
||||||
)}
|
)}
|
||||||
|
<div className="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
|
Lume running sync in the background,
|
||||||
|
<br />
|
||||||
|
the more you use the more event you see.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{hasNextPage ? (
|
{hasNextPage ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function Screen() {
|
|||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const until = pageParam > 0 ? pageParam.toString() : null;
|
const until = pageParam > 0 ? pageParam.toString() : null;
|
||||||
const res = await commands.getAllRelays(until);
|
const res = await commands.getAllRelayLists(until);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const data: NostrEvent[] = res.data.map((item) => JSON.parse(item));
|
const data: NostrEvent[] = res.data.map((item) => JSON.parse(item));
|
||||||
@@ -115,12 +115,14 @@ function Screen() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<div className="mb-3 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="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
<p className="text-sm text-center">{error?.message ?? "Error"}</p>
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
|
{error?.message ?? "Error"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : !data?.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 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="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
<p className="text-sm text-center">
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
Nothing to show yet, you can use Lume more and comeback lack to
|
Nothing to show yet, you can use Lume more and comeback lack to
|
||||||
see new events.
|
see new events.
|
||||||
</p>
|
</p>
|
||||||
@@ -128,6 +130,13 @@ function Screen() {
|
|||||||
) : (
|
) : (
|
||||||
data?.map((item) => renderItem(item))
|
data?.map((item) => renderItem(item))
|
||||||
)}
|
)}
|
||||||
|
<div className="mb-3 flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/20">
|
||||||
|
<p className="text-xs text-center px-4 text-neutral-500 dark:text-neutral-400">
|
||||||
|
Lume running sync in the background,
|
||||||
|
<br />
|
||||||
|
the more you use the more event you see.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{hasNextPage ? (
|
{hasNextPage ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
108
src/routes/columns/_layout/dvm.$id.lazy.tsx
Normal file
108
src/routes/columns/_layout/dvm.$id.lazy.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
|
import { toLumeEvents } from "@/commons";
|
||||||
|
import { RepostNote, Spinner, TextNote } from "@/components";
|
||||||
|
import type { LumeEvent } from "@/system";
|
||||||
|
import { Kind } from "@/types";
|
||||||
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { type RefObject, useCallback, useRef } from "react";
|
||||||
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/columns/_layout/dvm/$id")({
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { id } = Route.useParams();
|
||||||
|
const { account } = Route.useSearch();
|
||||||
|
const { isLoading, isError, error, data } = useQuery({
|
||||||
|
queryKey: ["job-result", id],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!account) {
|
||||||
|
throw new Error("Account is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await commands.getAllEventsByRequest(account, id);
|
||||||
|
|
||||||
|
if (res.status === "error") {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toLumeEvents(res.data);
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(event: LumeEvent) => {
|
||||||
|
if (!event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.kind) {
|
||||||
|
case Kind.Repost: {
|
||||||
|
const repostId = event.repostId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RepostNote
|
||||||
|
key={repostId + event.id}
|
||||||
|
event={event}
|
||||||
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<TextNote
|
||||||
|
key={event.id}
|
||||||
|
event={event}
|
||||||
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea.Root
|
||||||
|
type={"scroll"}
|
||||||
|
scrollHideDelay={300}
|
||||||
|
className="overflow-hidden size-full px-3"
|
||||||
|
>
|
||||||
|
<ScrollArea.Viewport
|
||||||
|
ref={ref}
|
||||||
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
|
>
|
||||||
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-16 gap-2">
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
<span className="text-sm font-medium">Requesting events...</span>
|
||||||
|
</div>
|
||||||
|
) : isError ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-16 gap-2">
|
||||||
|
<span className="text-sm font-medium">{error?.message}</span>
|
||||||
|
</div>
|
||||||
|
) : !data?.length ? (
|
||||||
|
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
||||||
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data.map((item) => renderItem(item))
|
||||||
|
)}
|
||||||
|
</Virtualizer>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar
|
||||||
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
|
orientation="vertical"
|
||||||
|
>
|
||||||
|
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
<ScrollArea.Corner className="bg-transparent" />
|
||||||
|
</ScrollArea.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -175,7 +175,6 @@ function ReplyList() {
|
|||||||
|
|
||||||
return events.filter((ev) => !removeQueues.has(ev.id));
|
return events.filter((ev) => !removeQueues.has(ev.id));
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{isFetching && !isLoading && !isFetchingNextPage ? (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import { cn, toLumeEvents } from "@/commons";
|
import { cn, isValidRelayUrl, toLumeEvents } from "@/commons";
|
||||||
import { Spinner, User } from "@/components";
|
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";
|
||||||
@@ -8,9 +8,11 @@ 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 { resolveResource } from "@tauri-apps/api/path";
|
import { resolveResource } from "@tauri-apps/api/path";
|
||||||
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
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 { memo, useCallback, useState, useTransition } from "react";
|
||||||
|
import { minidenticon } from "minidenticons";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/launchpad/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/launchpad/$id")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
@@ -25,7 +27,9 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
||||||
<Newsfeeds />
|
<Newsfeeds />
|
||||||
|
<Relayfeeds />
|
||||||
<Interests />
|
<Interests />
|
||||||
|
<ContentDiscovery />
|
||||||
<Core />
|
<Core />
|
||||||
</ScrollArea.Viewport>
|
</ScrollArea.Viewport>
|
||||||
<ScrollArea.Scrollbar
|
<ScrollArea.Scrollbar
|
||||||
@@ -69,7 +73,7 @@ function Newsfeeds() {
|
|||||||
: item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
|
: item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
|
||||||
const label =
|
const label =
|
||||||
item.kind === 3
|
item.kind === 3
|
||||||
? `newsfeed-${id.slice(0, 5)}`
|
? `newsfeed-${item.pubkey.slice(0, 5)}`
|
||||||
: item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
|
: item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -178,14 +182,14 @@ function Newsfeeds() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
LumeWindow.openColumn({
|
LumeWindow.openColumn({
|
||||||
name: "Newsfeeds",
|
name: "Browse Newsfeeds",
|
||||||
url: "/columns/discover-newsfeeds",
|
url: "/columns/discover-newsfeeds",
|
||||||
label: "discover_newsfeeds",
|
label: "discover_newsfeeds",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="h-9 w-full px-3 flex items-center justify-between bg-neutral-200/50 hover:bg-neutral-200 rounded-lg dark:bg-neutral-800/50 dark:hover:bg-neutral-800"
|
className="h-9 w-full px-3 flex items-center justify-between bg-neutral-200/50 hover:bg-neutral-200 rounded-lg dark:bg-neutral-800/50 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
<span className="text-xs font-medium">Discover newsfeeds</span>
|
<span className="text-xs font-medium">Browse newsfeeds</span>
|
||||||
<ArrowRight className="size-4" weight="bold" />
|
<ArrowRight className="size-4" weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,6 +197,179 @@ function Newsfeeds() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Relayfeeds() {
|
||||||
|
const { id } = Route.useParams();
|
||||||
|
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
||||||
|
queryKey: ["relays", id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await commands.getRelayList(id);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
const event: NostrEvent = JSON.parse(res.data);
|
||||||
|
return event;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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">Relayfeeds</h3>
|
||||||
|
<div className="inline-flex items-center justify-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
className={cn(
|
||||||
|
"size-7 inline-flex items-center justify-center rounded-full",
|
||||||
|
isRefetching ? "animate-spin" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ArrowClockwise className="size-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => LumeWindow.openPopup(`${id}/set-group`, "New group")}
|
||||||
|
className="h-7 w-max px-2 inline-flex items-center justify-center gap-1 text-sm font-medium rounded-full bg-neutral-300 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" weight="bold" />
|
||||||
|
New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : 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 ? (
|
||||||
|
<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 relay list yet.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="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">
|
||||||
|
{data?.tags.map((tag) =>
|
||||||
|
tag[1]?.startsWith("wss://") ? (
|
||||||
|
<div
|
||||||
|
key={tag[1]}
|
||||||
|
className="group px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800"
|
||||||
|
>
|
||||||
|
<div className="flex-1 truncate select-text text-sm font-medium">
|
||||||
|
{tag[1]}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
LumeWindow.openColumn({
|
||||||
|
name: tag[1],
|
||||||
|
label: `relays_${tag[1].replace(/[^\w\s]/gi, "")}`,
|
||||||
|
url: `/columns/relays/${encodeURIComponent(tag[1])}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="h-6 w-16 hidden group-hover: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>
|
||||||
|
) : null,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="p-2 flex items-center">
|
||||||
|
<User.Provider pubkey={data?.pubkey}>
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
<div className="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">
|
||||||
|
<RelayForm />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
LumeWindow.openColumn({
|
||||||
|
name: "Browse Relays",
|
||||||
|
url: "/columns/discover-relays",
|
||||||
|
label: "discover_relays",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="h-9 w-full px-3 flex items-center justify-between bg-neutral-200/50 hover:bg-neutral-200 rounded-lg dark:bg-neutral-800/50 dark:hover:bg-neutral-800"
|
||||||
|
>
|
||||||
|
<span className="text-xs font-medium">Browse relays</span>
|
||||||
|
<ArrowRight className="size-4" weight="bold" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RelayForm() {
|
||||||
|
const [url, setUrl] = useState("");
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
startTransition(async () => {
|
||||||
|
if (!isValidRelayUrl(url)) {
|
||||||
|
await message("Relay URL is not valid", { kind: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await LumeWindow.openColumn({
|
||||||
|
name: url,
|
||||||
|
label: `relays_${url.replace(/[^\w\s]/gi, "")}`,
|
||||||
|
url: `/columns/relays/${encodeURIComponent(url)}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUrl("");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2 p-2">
|
||||||
|
<label
|
||||||
|
htmlFor="url"
|
||||||
|
className="text-xs font-semibold text-neutral-700 dark:text-neutral-300"
|
||||||
|
>
|
||||||
|
Add custom relay
|
||||||
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
name="url"
|
||||||
|
type="url"
|
||||||
|
onChange={(e) => setUrl(e.currentTarget.value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter") submit();
|
||||||
|
}}
|
||||||
|
value={url}
|
||||||
|
disabled={isPending}
|
||||||
|
placeholder="wss://..."
|
||||||
|
spellCheck={false}
|
||||||
|
className="flex-1 px-3 bg-neutral-100 border-transparent rounded-lg h-9 dark:bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => submit()}
|
||||||
|
className="shrink-0 h-9 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-lg bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Interests() {
|
function Interests() {
|
||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
||||||
@@ -261,7 +438,6 @@ function Interests() {
|
|||||||
</User.Provider>
|
</User.Provider>
|
||||||
<h5 className="text-xs font-medium">{name}</h5>
|
<h5 className="text-xs font-medium">{name}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -278,7 +454,6 @@ function Interests() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[data],
|
[data],
|
||||||
@@ -332,14 +507,14 @@ function Interests() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
LumeWindow.openColumn({
|
LumeWindow.openColumn({
|
||||||
name: "Interests",
|
name: "Browse Interests",
|
||||||
url: "/columns/discover-interests",
|
url: "/columns/discover-interests",
|
||||||
label: "discover_interests",
|
label: "discover_interests",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="h-9 w-full px-3 flex items-center justify-between bg-neutral-200/50 hover:bg-neutral-200 rounded-lg dark:bg-neutral-800/50 dark:hover:bg-neutral-800"
|
className="h-9 w-full px-3 flex items-center justify-between bg-neutral-200/50 hover:bg-neutral-200 rounded-lg dark:bg-neutral-800/50 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
<span className="text-xs font-medium">Discover interests</span>
|
<span className="text-xs font-medium">Browse interests</span>
|
||||||
<ArrowRight className="size-4" weight="bold" />
|
<ArrowRight className="size-4" weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,6 +522,132 @@ function Interests() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContentDiscovery() {
|
||||||
|
const { isLoading, isError, error, data } = useQuery({
|
||||||
|
queryKey: ["content-discovery"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await commands.getAllProviders();
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
const events: NostrEvent[] = res.data.map((item) => JSON.parse(item));
|
||||||
|
return events;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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">Content Discovery</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : 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 ? (
|
||||||
|
<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">Empty.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="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">
|
||||||
|
{data?.map((item) => (
|
||||||
|
<Provider key={item.id} event={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Provider = memo(function Provider({ event }: { event: NostrEvent }) {
|
||||||
|
const { id } = Route.useParams();
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const metadata: { [key: string]: string } = JSON.parse(event.content);
|
||||||
|
const fallback = `data:image/svg+xml;utf8,${encodeURIComponent(
|
||||||
|
minidenticon(event.id, 60, 50),
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
const request = (name: string | undefined, provider: string) => {
|
||||||
|
startTransition(async () => {
|
||||||
|
// Ensure signer
|
||||||
|
const signer = await commands.hasSigner(id);
|
||||||
|
|
||||||
|
if (signer.status === "ok") {
|
||||||
|
if (!signer.data) {
|
||||||
|
const res = await commands.setSigner(id);
|
||||||
|
|
||||||
|
if (res.status === "error") {
|
||||||
|
await message(res.error, { kind: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request event to provider
|
||||||
|
const res = await commands.requestEventsFromProvider(provider);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
// Open column
|
||||||
|
await LumeWindow.openColumn({
|
||||||
|
label: `dvm_${provider.slice(0, 6)}`,
|
||||||
|
name: name || "Content Discovery",
|
||||||
|
account: id,
|
||||||
|
url: `/columns/dvm/${provider}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
await message(res.error, { kind: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await message(signer.error, { kind: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group px-3 flex gap-2 items-center justify-between h-16 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||||
|
<div className="shrink-0 size-10 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={metadata.picture || fallback}
|
||||||
|
alt={event.id}
|
||||||
|
className="size-10 object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 flex flex-col truncate">
|
||||||
|
<h5 className="text-sm font-medium">{metadata.name}</h5>
|
||||||
|
<p className="w-full text-sm truncate text-neutral-600 dark:text-neutral-400">
|
||||||
|
{metadata.about}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => request(metadata.name, event.pubkey)}
|
||||||
|
disabled={isPending}
|
||||||
|
className={cn(
|
||||||
|
"h-6 w-16 group-hover:visible 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",
|
||||||
|
isPending ? "" : "invisible",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner className="size-3" /> : "Add"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
function Core() {
|
function Core() {
|
||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
@@ -391,22 +692,6 @@ function Core() {
|
|||||||
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">Relays</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
LumeWindow.openColumn({
|
|
||||||
name: "Relays",
|
|
||||||
label: "relays",
|
|
||||||
url: "/columns/discover-relays",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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) => (
|
{data?.map((column) => (
|
||||||
<div
|
<div
|
||||||
key={column.label}
|
key={column.label}
|
||||||
|
|||||||
@@ -120,19 +120,19 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<Tabs.List className="h-11 shrink-0 flex items-center">
|
<Tabs.List className="h-11 shrink-0 flex items-center">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50"
|
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
|
||||||
value="replies"
|
value="replies"
|
||||||
>
|
>
|
||||||
Replies
|
Replies
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50"
|
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
|
||||||
value="reactions"
|
value="reactions"
|
||||||
>
|
>
|
||||||
Reactions
|
Reactions
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50"
|
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
|
||||||
value="zaps"
|
value="zaps"
|
||||||
>
|
>
|
||||||
Zaps
|
Zaps
|
||||||
|
|||||||
@@ -27,32 +27,32 @@ function Screen() {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<a
|
<a
|
||||||
href="/new-account/connect"
|
href="/new-account/connect"
|
||||||
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-800 ring-1 ring-black/5 dark:ring-white/5"
|
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-600 ring-1 ring-black/5 dark:ring-white/5"
|
||||||
>
|
>
|
||||||
<h3 className="mb-1 font-medium">Continue with Nostr Connect</h3>
|
<h3 className="mb-1 font-medium">Continue with Nostr Connect</h3>
|
||||||
<p className="text-xs text-neutral-500 dark:text-neutral-500">
|
<p className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
Your account will be handled by a remote signer. Lume will not
|
Your account will be handled by a remote signer. Lume will not
|
||||||
store your account keys.
|
store your account keys.
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="/new-account/import"
|
href="/new-account/import"
|
||||||
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-800 ring-1 ring-black/5 dark:ring-white/5"
|
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-600 ring-1 ring-black/5 dark:ring-white/5"
|
||||||
>
|
>
|
||||||
<h3 className="mb-1 font-medium">Continue with Secret Key</h3>
|
<h3 className="mb-1 font-medium">Continue with Secret Key</h3>
|
||||||
<p className="text-xs text-neutral-500 dark:text-neutral-500">
|
<p className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
Lume will store your keys in secure storage. You can provide a
|
Lume will store your keys in secure storage. You can provide a
|
||||||
password to add extra security.
|
password to add extra security.
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="/new-account/watch"
|
href="/new-account/watch"
|
||||||
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-800 ring-1 ring-black/5 dark:ring-white/5"
|
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-600 ring-1 ring-black/5 dark:ring-white/5"
|
||||||
>
|
>
|
||||||
<h3 className="mb-1 font-medium">
|
<h3 className="mb-1 font-medium">
|
||||||
Continue with Public Key (Watch Mode)
|
Continue with Public Key (Watch Mode)
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-neutral-500 dark:text-neutral-500">
|
<p className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
Use for experience without provide your private key, you can add
|
Use for experience without provide your private key, you can add
|
||||||
it later to publish new note.
|
it later to publish new note.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import { cn } from "@/commons";
|
|
||||||
import { CurrencyBtc, GearSix, HardDrives, User } from "@phosphor-icons/react";
|
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
|
||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
import { Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/settings/$id")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const { id } = Route.useParams();
|
|
||||||
const { platform } = Route.useRouteContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex size-full">
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className={cn(
|
|
||||||
"w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2",
|
|
||||||
platform === "macos" ? "pt-11" : "",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="h-8 px-1.5">
|
|
||||||
<h1 className="text-lg font-semibold">Settings</h1>
|
|
||||||
</div>
|
|
||||||
<Link to="/settings/$id/general" params={{ id }}>
|
|
||||||
{({ isActive }) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
|
||||||
isActive
|
|
||||||
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
|
||||||
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<GearSix className="size-5 shrink-0" />
|
|
||||||
<p className="text-sm font-medium">General</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Link>
|
|
||||||
<Link to="/settings/$id/profile" params={{ id }}>
|
|
||||||
{({ isActive }) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
|
||||||
isActive
|
|
||||||
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
|
||||||
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<User className="size-5 shrink-0" />
|
|
||||||
<p className="text-sm font-medium">Profile</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Link>
|
|
||||||
<Link to="/settings/$id/relay" params={{ id }}>
|
|
||||||
{({ isActive }) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
|
||||||
isActive
|
|
||||||
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
|
||||||
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<HardDrives className="size-5 shrink-0" />
|
|
||||||
<p className="text-sm font-medium">Relay</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Link>
|
|
||||||
<Link to="/settings/$id/wallet" params={{ id }}>
|
|
||||||
{({ isActive }) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
|
||||||
isActive
|
|
||||||
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
|
||||||
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CurrencyBtc className="size-5 shrink-0" />
|
|
||||||
<p className="text-sm font-medium">Wallet</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<ScrollArea.Root
|
|
||||||
type={"scroll"}
|
|
||||||
scrollHideDelay={300}
|
|
||||||
className="flex-1 overflow-hidden size-full"
|
|
||||||
>
|
|
||||||
<ScrollArea.Viewport className="relative h-full pt-12">
|
|
||||||
<Outlet />
|
|
||||||
</ScrollArea.Viewport>
|
|
||||||
<ScrollArea.Scrollbar
|
|
||||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
|
||||||
orientation="vertical"
|
|
||||||
>
|
|
||||||
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
|
||||||
</ScrollArea.Scrollbar>
|
|
||||||
<ScrollArea.Corner className="bg-transparent" />
|
|
||||||
</ScrollArea.Root>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/settings/$id/general")();
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import { commands } from "@/commands.gen";
|
|
||||||
import { Plus, X } from "@phosphor-icons/react";
|
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { useEffect, useState, useTransition } from "react";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/settings/$id/relay")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const { relayList } = Route.useRouteContext();
|
|
||||||
|
|
||||||
const [relays, setRelays] = useState<string[]>([]);
|
|
||||||
const [newRelay, setNewRelay] = useState<string>("");
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const removeRelay = async (relay: string) => {
|
|
||||||
const res = await commands.removeRelay(relay);
|
|
||||||
|
|
||||||
if (res.status === "ok") {
|
|
||||||
return res.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(res.error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addNewRelay = () => {
|
|
||||||
startTransition(async () => {
|
|
||||||
try {
|
|
||||||
let url = newRelay;
|
|
||||||
|
|
||||||
if (!url.startsWith("wss://")) {
|
|
||||||
url = `wss://${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const relay = new URL(url);
|
|
||||||
const res = await commands.connectRelay(relay.toString());
|
|
||||||
|
|
||||||
if (res.status === "ok") {
|
|
||||||
setRelays((prev) => [...prev, newRelay]);
|
|
||||||
setNewRelay("");
|
|
||||||
} else {
|
|
||||||
await message(res.error, { title: "Relay", kind: "error" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
await message("URL is not valid.", { kind: "error" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRelays(relayList.connected);
|
|
||||||
}, [relayList]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full px-3 pb-3">
|
|
||||||
<div className="flex flex-col gap-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
|
||||||
Connected Relays
|
|
||||||
</h2>
|
|
||||||
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
|
||||||
{relays.map((relay) => (
|
|
||||||
<div
|
|
||||||
key={relay}
|
|
||||||
className="flex items-center justify-between h-11"
|
|
||||||
>
|
|
||||||
<div className="inline-flex items-center gap-2 text-sm font-medium">
|
|
||||||
<span className="relative flex size-2">
|
|
||||||
<span className="absolute inline-flex w-full h-full bg-teal-400 rounded-full opacity-75 animate-ping" />
|
|
||||||
<span className="relative inline-flex bg-teal-500 rounded-full size-2" />
|
|
||||||
</span>
|
|
||||||
{relay}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => removeRelay(relay)}
|
|
||||||
className="inline-flex items-center justify-center rounded-md size-7 hover:bg-black/10 dark:hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<X className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="flex items-center h-14">
|
|
||||||
<div className="flex items-center w-full gap-2 mb-0">
|
|
||||||
<input
|
|
||||||
value={newRelay}
|
|
||||||
onChange={(e) => setNewRelay(e.target.value)}
|
|
||||||
name="url"
|
|
||||||
placeholder="wss://..."
|
|
||||||
disabled={isPending}
|
|
||||||
spellCheck={false}
|
|
||||||
className="flex-1 px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring-0 dark:border-neutral-700 dark:placeholder:text-neutral-400"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={() => addNewRelay()}
|
|
||||||
className="inline-flex items-center justify-center w-16 px-2 text-sm font-medium text-white rounded-lg shrink-0 h-9 bg-black/20 dark:bg-white/20 hover:bg-blue-500 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
<Plus className="size-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
|
||||||
User Relays (NIP-65)
|
|
||||||
</h2>
|
|
||||||
<div className="flex flex-col px-3 py-2 bg-black/5 dark:bg-white/5 rounded-xl">
|
|
||||||
<p className="text-sm text-yellow-500">
|
|
||||||
Lume will automatically connect to the user's relay list, but the
|
|
||||||
manager function (like adding, removing, changing relay purpose)
|
|
||||||
is not yet available.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
|
||||||
{relayList.read?.map((relay) => (
|
|
||||||
<div
|
|
||||||
key={relay}
|
|
||||||
className="flex items-center justify-between h-11"
|
|
||||||
>
|
|
||||||
<div className="text-sm font-medium">{relay}</div>
|
|
||||||
<div className="text-xs font-semibold">READ</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{relayList.write?.map((relay) => (
|
|
||||||
<div
|
|
||||||
key={relay}
|
|
||||||
className="flex items-center justify-between h-11"
|
|
||||||
>
|
|
||||||
<div className="text-sm font-medium">{relay}</div>
|
|
||||||
<div className="text-xs font-semibold">WRITE</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{relayList.both?.map((relay) => (
|
|
||||||
<div
|
|
||||||
key={relay}
|
|
||||||
className="flex items-center justify-between h-11"
|
|
||||||
>
|
|
||||||
<div className="text-sm font-medium">{relay}</div>
|
|
||||||
<div className="text-xs font-semibold">READ + WRITE</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
96
src/routes/settings.lazy.tsx
Normal file
96
src/routes/settings.lazy.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { cn } from '@/commons'
|
||||||
|
import { CurrencyBtc, GearSix, HardDrives } from '@phosphor-icons/react'
|
||||||
|
import * as ScrollArea from '@radix-ui/react-scroll-area'
|
||||||
|
import { Link } from '@tanstack/react-router'
|
||||||
|
import { Outlet, createLazyFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute('/settings')({
|
||||||
|
component: Screen,
|
||||||
|
})
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { platform } = Route.useRouteContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex size-full">
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className={cn(
|
||||||
|
'w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2',
|
||||||
|
platform === 'macos' ? 'pt-11' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="h-8 px-1.5">
|
||||||
|
<h1 className="text-lg font-semibold">Settings</h1>
|
||||||
|
</div>
|
||||||
|
<Link to="/settings/general">
|
||||||
|
{({ isActive }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
|
||||||
|
isActive
|
||||||
|
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
|
||||||
|
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<GearSix className="size-5 shrink-0" />
|
||||||
|
<p className="text-sm font-medium">General</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Link>
|
||||||
|
<Link to="/settings/relays">
|
||||||
|
{({ isActive }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
|
||||||
|
isActive
|
||||||
|
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
|
||||||
|
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HardDrives className="size-5 shrink-0" />
|
||||||
|
<p className="text-sm font-medium">Relays</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Link>
|
||||||
|
<Link to="/settings/wallet">
|
||||||
|
{({ isActive }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
|
||||||
|
isActive
|
||||||
|
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
|
||||||
|
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CurrencyBtc className="size-5 shrink-0" />
|
||||||
|
<p className="text-sm font-medium">Wallet</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<ScrollArea.Root
|
||||||
|
type={'scroll'}
|
||||||
|
scrollHideDelay={300}
|
||||||
|
className="flex-1 overflow-hidden size-full"
|
||||||
|
>
|
||||||
|
<ScrollArea.Viewport className="relative h-full pt-12">
|
||||||
|
<Outlet />
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar
|
||||||
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
|
orientation="vertical"
|
||||||
|
>
|
||||||
|
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
<ScrollArea.Corner className="bg-transparent" />
|
||||||
|
</ScrollArea.Root>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import { settingsQueryOptions } from "../__root";
|
|||||||
|
|
||||||
type Theme = "auto" | "light" | "dark";
|
type Theme = "auto" | "light" | "dark";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/settings/$id/general")({
|
export const Route = createLazyFileRoute("/settings/general")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ function Screen() {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await message(res.error, { kind: "error" });
|
await message(res.error, { kind: "error" });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
3
src/routes/settings/general.tsx
Normal file
3
src/routes/settings/general.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/settings/general")();
|
||||||
107
src/routes/settings/relays.lazy.tsx
Normal file
107
src/routes/settings/relays.lazy.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
|
import { isValidRelayUrl } from "@/commons";
|
||||||
|
import { Plus, X } from "@phosphor-icons/react";
|
||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { useEffect, useState, useTransition } from "react";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/settings/relays")({
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { allRelays } = Route.useRouteContext();
|
||||||
|
|
||||||
|
const [relays, setRelays] = useState<string[]>([]);
|
||||||
|
const [newRelay, setNewRelay] = useState<string>("");
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const removeRelay = async (relay: string) => {
|
||||||
|
const res = await commands.removeRelay(relay);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addNewRelay = () => {
|
||||||
|
startTransition(async () => {
|
||||||
|
if (!isValidRelayUrl(newRelay)) {
|
||||||
|
await message("Relay URL is not valid", { kind: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await commands.connectRelay(newRelay);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
setRelays((prev) => [...prev, newRelay]);
|
||||||
|
setNewRelay("");
|
||||||
|
} else {
|
||||||
|
await message(res.error, { title: "Relay", kind: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (allRelays) setRelays(allRelays);
|
||||||
|
}, [allRelays]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full px-3 pb-3">
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
||||||
|
Connected Relays
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
|
<div className="flex items-center h-14">
|
||||||
|
<div className="flex items-center w-full gap-2 mb-0">
|
||||||
|
<input
|
||||||
|
value={newRelay}
|
||||||
|
onChange={(e) => setNewRelay(e.target.value)}
|
||||||
|
name="url"
|
||||||
|
placeholder="wss://..."
|
||||||
|
disabled={isPending}
|
||||||
|
spellCheck={false}
|
||||||
|
className="flex-1 px-3 bg-transparent rounded-lg h-9 border-neutral-400/50 placeholder:text-neutral-500 focus:border-blue-500 focus:ring-0 dark:border-neutral-800/50 dark:placeholder:text-neutral-400"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => addNewRelay()}
|
||||||
|
className="inline-flex items-center justify-center w-16 px-2 text-sm font-medium text-white rounded-lg shrink-0 h-9 bg-black/20 dark:bg-white/20 hover:bg-blue-500 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<Plus className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{relays.map((relay) => (
|
||||||
|
<div
|
||||||
|
key={relay}
|
||||||
|
className="flex items-center justify-between h-11"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-2 text-sm font-medium truncate">
|
||||||
|
<span className="relative flex size-2">
|
||||||
|
<span className="absolute inline-flex w-full h-full bg-teal-400 rounded-full opacity-75 animate-ping" />
|
||||||
|
<span className="relative inline-flex bg-teal-500 rounded-full size-2" />
|
||||||
|
</span>
|
||||||
|
<span className="truncate">{relay}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeRelay(relay)}
|
||||||
|
className="inline-flex items-center justify-center rounded-md size-7 text-neutral-500 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<X className="size-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/settings/$id/relay")({
|
export const Route = createFileRoute("/settings/relays")({
|
||||||
beforeLoad: async ({ params }) => {
|
beforeLoad: async () => {
|
||||||
const res = await commands.getRelays(params.id);
|
const res = await commands.getAllRelays();
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
return { relayList: res.data };
|
return { allRelays: res.data };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ import { Button } from "@getalby/bitcoin-connect-react";
|
|||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/settings/$id/wallet")({
|
export const Route = createLazyFileRoute("/settings/wallet")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { init } from "@getalby/bitcoin-connect-react";
|
import { init } from "@getalby/bitcoin-connect-react";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/settings/$id/wallet")({
|
export const Route = createFileRoute("/settings/wallet")({
|
||||||
beforeLoad: async () => {
|
beforeLoad: async () => {
|
||||||
init({
|
init({
|
||||||
appName: "Lume",
|
appName: "Lume",
|
||||||
@@ -121,7 +121,7 @@ export const LumeWindow = {
|
|||||||
throw new Error(query.error);
|
throw new Error(query.error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openZap: async (id: string, account?: string) => {
|
openZap: async (id: string) => {
|
||||||
const wallet = await commands.loadWallet();
|
const wallet = await commands.loadWallet();
|
||||||
|
|
||||||
if (wallet.status === "ok") {
|
if (wallet.status === "ok") {
|
||||||
@@ -136,16 +136,14 @@ export const LumeWindow = {
|
|||||||
hidden_title: true,
|
hidden_title: true,
|
||||||
closable: true,
|
closable: true,
|
||||||
});
|
});
|
||||||
} else if (account) {
|
} else {
|
||||||
await LumeWindow.openSettings(account, "wallet");
|
await LumeWindow.openSettings("wallet");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openSettings: async (account: string, path?: string) => {
|
openSettings: async (path?: string) => {
|
||||||
const query = await commands.openWindow({
|
const query = await commands.openWindow({
|
||||||
label: "settings",
|
label: "settings",
|
||||||
url: path
|
url: path ? `/settings/${path}` : "/settings/general",
|
||||||
? `/settings/${account}/${path}`
|
|
||||||
: `/settings/${account}/general`,
|
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
width: 700,
|
width: 700,
|
||||||
height: 500,
|
height: 500,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: ["./src/**/*.{js,ts,jsx,tsx}", "index.html"],
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
|
||||||
"index.html",
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
keyframes: {
|
keyframes: {
|
||||||
|
|||||||
@@ -2,19 +2,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": [
|
"lib": ["ESNext", "ES2020", "DOM", "DOM.Iterable"],
|
||||||
"ESNext",
|
|
||||||
"ES2020",
|
|
||||||
"DOM",
|
|
||||||
"DOM.Iterable"
|
|
||||||
],
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./src/*"]
|
||||||
"./src/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
@@ -26,11 +19,9 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"],
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.node.json"
|
"path": "./tsconfig.node.json"
|
||||||
|
|||||||
@@ -6,7 +6,5 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["vite.config.ts"]
|
||||||
"vite.config.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user