Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a262217ab2 | ||
|
|
c5d06a2492 | ||
|
|
c93edde7d2 | ||
|
|
5103126001 | ||
|
|
b0c49c5141 | ||
|
|
2ed2cb3afd | ||
|
|
2bcda1f2ef | ||
|
|
ca20bbd298 | ||
|
|
c6da06cd4d | ||
|
|
50bf6c04c1 | ||
|
|
490417771c | ||
|
|
0cb491eaf9 | ||
|
|
ece6bcc125 | ||
|
|
4b79e559d2 | ||
|
|
322e510db2 | ||
|
|
4e279f127d | ||
|
|
5655a8136d | ||
|
|
d80534c51f | ||
|
|
0b97248fb8 | ||
|
|
f54f448ecb | ||
|
|
bd1f2b899d | ||
|
|
efd3c83193 | ||
|
|
85fa1e2359 | ||
|
|
cd6ba5884f | ||
|
|
bfed56ba13 | ||
|
|
d1018ba8d1 | ||
|
|
a42542c16e | ||
|
|
26a6ec954c |
+1
-4
@@ -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,
|
||||||
|
|||||||
+83
-83
@@ -1,85 +1,85 @@
|
|||||||
{
|
{
|
||||||
"name": "lume",
|
"name": "lume",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@getalby/bitcoin-connect-react": "^3.6.2",
|
"@getalby/bitcoin-connect-react": "^3.6.2",
|
||||||
"@phosphor-icons/react": "^2.1.7",
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@radix-ui/react-avatar": "^1.1.1",
|
"@radix-ui/react-avatar": "^1.1.1",
|
||||||
"@radix-ui/react-checkbox": "^1.1.2",
|
"@radix-ui/react-checkbox": "^1.1.2",
|
||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||||
"@radix-ui/react-switch": "^1.1.1",
|
"@radix-ui/react-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.3",
|
"@radix-ui/react-tooltip": "^1.1.3",
|
||||||
"@tanstack/query-broadcast-client-experimental": "^5.59.16",
|
"@tanstack/query-broadcast-client-experimental": "^5.59.20",
|
||||||
"@tanstack/query-persist-client-core": "^5.59.16",
|
"@tanstack/query-persist-client-core": "^5.59.20",
|
||||||
"@tanstack/react-query": "^5.59.16",
|
"@tanstack/react-query": "^5.59.20",
|
||||||
"@tanstack/react-router": "^1.77.5",
|
"@tanstack/react-router": "^1.81.0",
|
||||||
"@tauri-apps/api": "^2.0.3",
|
"@tauri-apps/api": "^2.1.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||||
"@tauri-apps/plugin-fs": "^2.0.1",
|
"@tauri-apps/plugin-fs": "^2.0.2",
|
||||||
"@tauri-apps/plugin-http": "^2.0.1",
|
"@tauri-apps/plugin-http": "^2.0.1",
|
||||||
"@tauri-apps/plugin-os": "^2.0.0",
|
"@tauri-apps/plugin-os": "^2.0.0",
|
||||||
"@tauri-apps/plugin-process": "^2.0.0",
|
"@tauri-apps/plugin-process": "^2.0.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||||
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
|
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||||
"@tauri-apps/plugin-upload": "^2.0.0",
|
"@tauri-apps/plugin-upload": "^2.1.0",
|
||||||
"@tauri-apps/plugin-window-state": "^2.0.0",
|
"@tauri-apps/plugin-window-state": "^2.0.0",
|
||||||
"bitcoin-units": "^1.0.0",
|
"bitcoin-units": "^1.0.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.1",
|
||||||
"i18next": "^23.16.4",
|
"i18next": "^23.16.5",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"light-bolt11-decoder": "^3.2.0",
|
"light-bolt11-decoder": "^3.2.0",
|
||||||
"minidenticons": "^4.2.1",
|
"minidenticons": "^4.2.1",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.8",
|
||||||
"nostr-tools": "^2.9.4",
|
"nostr-tools": "^2.10.1",
|
||||||
"react": "19.0.0-rc-cae764ce-20241025",
|
"react": "19.0.0-rc-cae764ce-20241025",
|
||||||
"react-currency-input-field": "^3.8.0",
|
"react-currency-input-field": "^3.8.0",
|
||||||
"react-dom": "19.0.0-rc-cae764ce-20241025",
|
"react-dom": "19.0.0-rc-cae764ce-20241025",
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.2",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.1",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"rich-textarea": "^0.26.3",
|
"rich-textarea": "^0.26.4",
|
||||||
"use-debounce": "^10.0.4",
|
"use-debounce": "^10.0.4",
|
||||||
"virtua": "^0.34.2"
|
"virtua": "^0.34.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@tanstack/router-devtools": "^1.77.5",
|
"@tanstack/router-devtools": "^1.81.0",
|
||||||
"@tanstack/router-plugin": "^1.76.4",
|
"@tanstack/router-plugin": "^1.79.0",
|
||||||
"@tauri-apps/cli": "^2.0.4",
|
"@tauri-apps/cli": "^2.1.0",
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"babel-plugin-react-compiler": "0.0.0-experimental-b4db8c3-20241001",
|
"babel-plugin-react-compiler": "0.0.0-experimental-b4db8c3-20241001",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwind-gradient-mask-image": "^1.2.0",
|
"tailwind-gradient-mask-image": "^1.2.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"tailwindcss-content-visibility": "^1.0.0",
|
"tailwindcss-content-visibility": "^1.0.0",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-tsconfig-paths": "^5.0.1"
|
"vite-tsconfig-paths": "5.0.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/react": "npm:types-react@rc",
|
"@types/react": "npm:types-react@rc",
|
||||||
"@types/react-dom": "npm:types-react-dom@rc"
|
"@types/react-dom": "npm:types-react-dom@rc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+300
-300
File diff suppressed because it is too large
Load Diff
Generated
+813
-777
File diff suppressed because it is too large
Load Diff
@@ -33,9 +33,6 @@ 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-connect = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
|
|
||||||
specta = "^2.0.0-rc.20"
|
specta = "^2.0.0-rc.20"
|
||||||
specta-typescript = "0.0.7"
|
specta-typescript = "0.0.7"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -47,8 +44,13 @@ futures = "0.3.30"
|
|||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
||||||
keyring-search = "1.2.0"
|
keyring-search = { 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"
|
||||||
|
|
||||||
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
|
||||||
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
|
|
||||||
[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"]}}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
],
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Capability": {
|
"Capability": {
|
||||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
|
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"identifier",
|
"identifier",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
|
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PermissionEntry"
|
"$ref": "#/definitions/PermissionEntry"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
],
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Capability": {
|
"Capability": {
|
||||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
|
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"identifier",
|
"identifier",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
|
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PermissionEntry"
|
"$ref": "#/definitions/PermissionEntry"
|
||||||
|
|||||||
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
|
||||||
|
|||||||
+189
-30
@@ -71,11 +71,11 @@ pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
|
|||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
|
||||||
|
|
||||||
|
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
.kind(Kind::Comment)
|
||||||
.event(event_id);
|
.custom_tag(SingleLetterTag::uppercase(Alphabet::E), [event_id]);
|
||||||
|
|
||||||
let mut events = Events::new(&[filter.clone()]);
|
let mut events = Events::new(&[filter.clone()]);
|
||||||
|
|
||||||
@@ -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(
|
||||||
@@ -356,39 +523,31 @@ pub async fn reply(content: String, to: String, state: State<'_, Nostr>) -> Resu
|
|||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect root event from reply
|
// Find root event from reply
|
||||||
let root_ids: Vec<&EventId> = reply_to
|
let root_tag = reply_to
|
||||||
.tags
|
.tags
|
||||||
.filter_standardized(TagKind::e())
|
.find(TagKind::SingleLetter(SingleLetterTag::uppercase(
|
||||||
.filter_map(|t| match t {
|
Alphabet::E,
|
||||||
TagStandard::Event {
|
)));
|
||||||
event_id, marker, ..
|
|
||||||
} => {
|
|
||||||
if let Some(mkr) = marker {
|
|
||||||
match mkr {
|
|
||||||
Marker::Root => Some(event_id),
|
|
||||||
Marker::Reply => Some(event_id),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(event_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Get root event if exist
|
// Get root event if exist
|
||||||
let root = match root_ids.first() {
|
let root = match root_tag {
|
||||||
Some(&id) => client
|
Some(tag) => match tag.content() {
|
||||||
.database()
|
Some(content) => {
|
||||||
.event_by_id(id)
|
let id = EventId::parse(content).map_err(|err| err.to_string())?;
|
||||||
.await
|
|
||||||
.map_err(|err| err.to_string())?,
|
client
|
||||||
|
.database()
|
||||||
|
.event_by_id(&id)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let builder = EventBuilder::text_note_reply(content, &reply_to, root.as_ref(), None)
|
let builder = EventBuilder::comment(content, &reply_to, root.as_ref(), None)
|
||||||
.add_tags(tags)
|
.add_tags(tags)
|
||||||
.pow(DEFAULT_DIFFICULTY);
|
.pow(DEFAULT_DIFFICULTY);
|
||||||
|
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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 +1,71 @@
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use std::fs::{self, File};
|
use std::collections::HashSet;
|
||||||
use tauri::{ipc::Channel, Manager, State};
|
use tauri::State;
|
||||||
|
|
||||||
use crate::Nostr;
|
use crate::Nostr;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn is_account_sync(id: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
|
pub async fn sync_all(
|
||||||
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>,
|
state: State<'_, Nostr>,
|
||||||
reader: Channel<f64>,
|
reader: tauri::ipc::Channel<f64>,
|
||||||
app_handle: tauri::AppHandle,
|
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = &state.client;
|
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![
|
// Create a filter for get all public keys
|
||||||
Kind::Metadata,
|
let filter = Filter::new().kinds(vec![
|
||||||
Kind::ContactList,
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::RelayList,
|
|
||||||
Kind::MuteList,
|
|
||||||
Kind::EventDeletion,
|
|
||||||
Kind::Bookmarks,
|
|
||||||
Kind::BookmarkSet,
|
|
||||||
Kind::TextNote,
|
Kind::TextNote,
|
||||||
Kind::Repost,
|
Kind::Repost,
|
||||||
Kind::Custom(30315),
|
Kind::FollowSet,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::MuteList,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
let events = client
|
||||||
|
.database()
|
||||||
|
.query(vec![filter])
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let public_keys: Vec<PublicKey> = events
|
||||||
|
.iter()
|
||||||
|
.flat_map(|ev| ev.tags.public_keys().copied())
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
let (tx, mut rx) = SyncProgress::channel();
|
let (tx, mut rx) = SyncProgress::channel();
|
||||||
let opts = SyncOptions::default().progress(tx);
|
let opts = SyncOptions::default().progress(tx);
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
while (rx.changed().await).is_ok() {
|
while rx.changed().await.is_ok() {
|
||||||
let SyncProgress { total, current } = *rx.borrow_and_update();
|
let progress = *rx.borrow_and_update();
|
||||||
|
|
||||||
if total > 0 {
|
if progress.total > 0 {
|
||||||
reader
|
reader.send(progress.percentage() * 100.0).unwrap();
|
||||||
.send((current as f64 / total as f64) * 100.0)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(output) = client.sync(filter, &opts).await {
|
for chunk in public_keys.chunks(200) {
|
||||||
println!("Success: {:?}", output.success);
|
let authors = chunk.to_owned();
|
||||||
println!("Failed: {:?}", output.failed);
|
let filter = Filter::new().authors(authors).kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
Kind::EventDeletion,
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Comment,
|
||||||
|
]);
|
||||||
|
|
||||||
let event_pubkeys = client
|
let _ = client
|
||||||
.database()
|
.sync(filter, &opts)
|
||||||
.query(vec![Filter::new().kinds(vec![
|
|
||||||
Kind::ContactList,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::MuteList,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::TextNote,
|
|
||||||
])])
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|err| err.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(())
|
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");
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
if is_newsfeed {
|
||||||
let state = webview.state::<Nostr>();
|
tauri::async_runtime::spawn(async move {
|
||||||
let client = &state.client;
|
let state = webview.state::<Nostr>();
|
||||||
|
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,8 +102,8 @@ 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) {
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = webview.state::<Nostr>();
|
let state = webview.state::<Nostr>();
|
||||||
@@ -112,8 +112,11 @@ pub async fn create_column(
|
|||||||
let subscription_id = SubscriptionId::new(webview.label());
|
let subscription_id = SubscriptionId::new(webview.label());
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.event(event_id)
|
.custom_tag(
|
||||||
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
SingleLetterTag::uppercase(Alphabet::E),
|
||||||
|
[event_id],
|
||||||
|
)
|
||||||
|
.kind(Kind::Comment)
|
||||||
.since(Timestamp::now());
|
.since(Timestamp::now());
|
||||||
|
|
||||||
if let Err(e) = client
|
if let Err(e) = client
|
||||||
@@ -228,12 +231,6 @@ pub fn open_window(window: NewWindow, app_handle: tauri::AppHandle) -> Result<St
|
|||||||
.transparent(true)
|
.transparent(true)
|
||||||
.decorations(false)
|
.decorations(false)
|
||||||
.closable(window.closable)
|
.closable(window.closable)
|
||||||
.effects(WindowEffectsConfig {
|
|
||||||
state: None,
|
|
||||||
effects: vec![Effect::Mica],
|
|
||||||
radius: None,
|
|
||||||
color: None,
|
|
||||||
})
|
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
+18
-17
@@ -56,13 +56,6 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
// Get words
|
// Get words
|
||||||
let words: Vec<_> = content.split_whitespace().collect();
|
let words: Vec<_> = content.split_whitespace().collect();
|
||||||
|
|
||||||
// Get mentions
|
|
||||||
let mentions = words
|
|
||||||
.iter()
|
|
||||||
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
|
|
||||||
.map(|&s| s.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Get hashtags
|
// Get hashtags
|
||||||
let hashtags = words
|
let hashtags = words
|
||||||
.iter()
|
.iter()
|
||||||
@@ -70,6 +63,13 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
.map(|&s| s.to_string().replace("#", "").to_lowercase())
|
.map(|&s| s.to_string().replace("#", "").to_lowercase())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Get mentions
|
||||||
|
let mentions = words
|
||||||
|
.iter()
|
||||||
|
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
|
||||||
|
.map(|&s| s.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for mention in mentions {
|
for mention in mentions {
|
||||||
let entity = mention.replace("nostr:", "").replace('@', "");
|
let entity = mention.replace("nostr:", "").replace('@', "");
|
||||||
|
|
||||||
@@ -92,8 +92,11 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
}
|
}
|
||||||
if entity.starts_with("note") {
|
if entity.starts_with("note") {
|
||||||
if let Ok(event_id) = EventId::from_bech32(&entity) {
|
if let Ok(event_id) = EventId::from_bech32(&entity) {
|
||||||
let hex = event_id.to_hex();
|
let tag = Tag::from_standardized(TagStandard::Quote {
|
||||||
let tag = Tag::parse(&["e", &hex, "", "mention"]).unwrap();
|
event_id,
|
||||||
|
relay_url: None,
|
||||||
|
public_key: None,
|
||||||
|
});
|
||||||
tags.push(tag);
|
tags.push(tag);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@@ -101,14 +104,12 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
}
|
}
|
||||||
if entity.starts_with("nevent") {
|
if entity.starts_with("nevent") {
|
||||||
if let Ok(event) = Nip19Event::from_bech32(&entity) {
|
if let Ok(event) = Nip19Event::from_bech32(&entity) {
|
||||||
let hex = event.event_id.to_hex();
|
let relay_url = event.relays.first().and_then(|i| Url::parse(i).ok());
|
||||||
let relay = event.clone().relays.into_iter().next().unwrap_or("".into());
|
let tag = Tag::from_standardized(TagStandard::Quote {
|
||||||
let tag = Tag::parse(&["e", &hex, &relay, "mention"]).unwrap();
|
event_id: event.event_id,
|
||||||
|
relay_url,
|
||||||
if let Some(author) = event.author {
|
public_key: event.author,
|
||||||
let tag = Tag::public_key(author);
|
});
|
||||||
tags.push(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
tags.push(tag);
|
tags.push(tag);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+212
-129
@@ -5,22 +5,20 @@
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
||||||
use commands::{account::*, event::*, metadata::*, relay::*, window::*};
|
use commands::{account::*, event::*, metadata::*, relay::*, sync::*, window::*};
|
||||||
use common::{get_all_accounts, parse_event};
|
use common::{get_all_accounts, parse_event};
|
||||||
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
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,27 @@ 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,
|
sync_all,
|
||||||
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 +106,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 +122,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 +182,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,9 +237,8 @@ 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_secs(2))
|
||||||
.automatic_authentication(true)
|
.timeout(Duration::from_secs(10));
|
||||||
.timeout(Duration::from_secs(5));
|
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
let client = ClientBuilder::default()
|
let client = ClientBuilder::default()
|
||||||
@@ -191,7 +246,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 +273,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 +287,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 +356,108 @@ 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));
|
||||||
|
// Skip the first tick
|
||||||
|
interval.tick().await;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -400,7 +478,7 @@ fn main() {
|
|||||||
Kind::Repost,
|
Kind::Repost,
|
||||||
Kind::Reaction,
|
Kind::Reaction,
|
||||||
Kind::ZapReceipt,
|
Kind::ZapReceipt,
|
||||||
Kind::Custom(1111),
|
Kind::Comment,
|
||||||
])
|
])
|
||||||
.since(Timestamp::now());
|
.since(Timestamp::now());
|
||||||
|
|
||||||
@@ -457,7 +535,7 @@ fn main() {
|
|||||||
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
||||||
println!("Emit error: {}", e)
|
println!("Emit error: {}", e)
|
||||||
}
|
}
|
||||||
} else if event.kind == Kind::TextNote {
|
} else if event.kind == Kind::Comment {
|
||||||
let payload = RichEvent {
|
let payload = RichEvent {
|
||||||
raw: event.as_json(),
|
raw: event.as_json(),
|
||||||
parsed: if event.kind == Kind::TextNote {
|
parsed: if event.kind == Kind::TextNote {
|
||||||
@@ -469,7 +547,7 @@ fn main() {
|
|||||||
|
|
||||||
if let Err(e) = handle_clone.emit_to(
|
if let Err(e) = handle_clone.emit_to(
|
||||||
EventTarget::labeled(subscription_id.to_string()),
|
EventTarget::labeled(subscription_id.to_string()),
|
||||||
"event",
|
"comment",
|
||||||
payload,
|
payload,
|
||||||
) {
|
) {
|
||||||
println!("Emit error: {}", e)
|
println!("Emit error: {}", e)
|
||||||
@@ -499,8 +577,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)]
|
||||||
@@ -523,7 +606,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
|
|||||||
if let Err(e) = handle
|
if let Err(e) = handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
.body("Mentioned you in a thread.")
|
.body("You're mentioned in a thread.")
|
||||||
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
||||||
.show()
|
.show()
|
||||||
{
|
{
|
||||||
@@ -534,7 +617,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
|
|||||||
if let Err(e) = handle
|
if let Err(e) = handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
.body("Reposted your note.")
|
.body("Your note has been reposted.")
|
||||||
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
||||||
.show()
|
.show()
|
||||||
{
|
{
|
||||||
@@ -545,7 +628,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
|
|||||||
if let Err(e) = handle
|
if let Err(e) = handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
.body("Zapped you.")
|
.body("You've received zap.")
|
||||||
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
||||||
.show()
|
.show()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.0",
|
"version": "24.11.8",
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,12 +10,7 @@
|
|||||||
"minWidth": 480,
|
"minWidth": 480,
|
||||||
"minHeight": 700,
|
"minHeight": 700,
|
||||||
"transparent": true,
|
"transparent": true,
|
||||||
"decorations": false,
|
"decorations": false
|
||||||
"windowEffects": {
|
|
||||||
"effects": [
|
|
||||||
"mica"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-21
@@ -5,17 +5,25 @@
|
|||||||
|
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
async getRelays(id: string) : Promise<Result<Relays, string>> {
|
async syncAll(reader: TAURI_CHANNEL<number>) : Promise<Result<null, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) };
|
return { status: "ok", data: await TAURI_INVOKE("sync_all", { reader }) };
|
||||||
} 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 getAllRelays() : 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_relays") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAllRelayLists(until: string | null) : Promise<Result<string[], string>> {
|
||||||
|
try {
|
||||||
|
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 +53,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 +240,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 +368,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,9 +560,9 @@ 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 }
|
||||||
|
export type TAURI_CHANNEL<TSend> = null
|
||||||
|
|
||||||
/** tauri-specta globals **/
|
/** tauri-specta globals **/
|
||||||
|
|
||||||
|
|||||||
+10
-38
@@ -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
|
||||||
|
|||||||
@@ -105,13 +105,11 @@ export function NoteRepost({
|
|||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
await message(res.error, { kind: "error" });
|
await message(res.error, { kind: "error" });
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,28 +32,89 @@ 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 {
|
||||||
await message(res.error, { kind: "error" });
|
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" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFollow.mutate();
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -57,8 +123,8 @@ export function UserButton({ className }: { className?: string }) {
|
|||||||
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}
|
||||||
|
|||||||
+203
-177
@@ -1,44 +1,45 @@
|
|||||||
/* prettier-ignore-start */
|
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
// This file is auto-generated by TanStack Router
|
// This file was automatically generated by TanStack Router.
|
||||||
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
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 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'
|
||||||
import { Route as ColumnsLayoutNewsfeedIdImport } from './routes/columns/_layout/newsfeed.$id'
|
import { Route as ColumnsLayoutNewsfeedIdImport } from './routes/columns/_layout/newsfeed.$id'
|
||||||
import { Route as ColumnsLayoutInterestsIdImport } from './routes/columns/_layout/interests.$id'
|
import { Route as ColumnsLayoutInterestsIdImport } from './routes/columns/_layout/interests.$id'
|
||||||
import { Route as ColumnsLayoutGroupsIdImport } from './routes/columns/_layout/groups.$id'
|
import { Route as ColumnsLayoutGroupsIdImport } from './routes/columns/_layout/groups.$id'
|
||||||
|
import { Route as ColumnsLayoutEventsIdImport } from './routes/columns/_layout/events.$id'
|
||||||
import { Route as ColumnsLayoutCreateNewsfeedUsersImport } from './routes/columns/_layout/create-newsfeed.users'
|
import { Route as ColumnsLayoutCreateNewsfeedUsersImport } from './routes/columns/_layout/create-newsfeed.users'
|
||||||
import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/_layout/create-newsfeed.f2f'
|
import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/_layout/create-newsfeed.f2f'
|
||||||
|
|
||||||
// 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 SettingsSyncLazyImport = createFileRoute('/settings/sync')()
|
||||||
|
const SettingsGeneralLazyImport = createFileRoute('/settings/general')()
|
||||||
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')()
|
||||||
@@ -75,8 +76,8 @@ const ColumnsLayoutNotificationIdLazyImport = createFileRoute(
|
|||||||
const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute(
|
const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/launchpad/$id',
|
'/columns/_layout/launchpad/$id',
|
||||||
)()
|
)()
|
||||||
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
const ColumnsLayoutDvmIdLazyImport = createFileRoute(
|
||||||
'/columns/_layout/events/$id',
|
'/columns/_layout/dvm/$id',
|
||||||
)()
|
)()
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
@@ -87,20 +88,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,
|
||||||
@@ -120,11 +119,19 @@ 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({
|
const SettingsSyncLazyRoute = SettingsSyncLazyImport.update({
|
||||||
id: '/settings/$id',
|
id: '/sync',
|
||||||
path: '/settings/$id',
|
path: '/sync',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
} as any).lazy(() => import('./routes/settings.$id.lazy').then((d) => d.Route))
|
} as any).lazy(() => import('./routes/settings/sync.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
|
const SettingsGeneralLazyRoute = SettingsGeneralLazyImport.update({
|
||||||
|
id: '/general',
|
||||||
|
path: '/general',
|
||||||
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/settings/general.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const NewAccountWatchLazyRoute = NewAccountWatchLazyImport.update({
|
const NewAccountWatchLazyRoute = NewAccountWatchLazyImport.update({
|
||||||
id: '/new-account/watch',
|
id: '/new-account/watch',
|
||||||
@@ -156,6 +163,22 @@ 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 ColumnsLayoutRoute = ColumnsLayoutImport.update({
|
const ColumnsLayoutRoute = ColumnsLayoutImport.update({
|
||||||
id: '/_layout',
|
id: '/_layout',
|
||||||
getParentRoute: () => ColumnsRoute,
|
getParentRoute: () => ColumnsRoute,
|
||||||
@@ -241,30 +264,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',
|
||||||
@@ -326,12 +325,12 @@ 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 ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
const ColumnsLayoutDvmIdLazyRoute = ColumnsLayoutDvmIdLazyImport.update({
|
||||||
id: '/events/$id',
|
id: '/dvm/$id',
|
||||||
path: '/events/$id',
|
path: '/dvm/$id',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
} as any).lazy(() =>
|
} as any).lazy(() =>
|
||||||
import('./routes/columns/_layout/events.$id.lazy').then((d) => d.Route),
|
import('./routes/columns/_layout/dvm.$id.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
const ColumnsLayoutStoriesIdRoute = ColumnsLayoutStoriesIdImport.update({
|
const ColumnsLayoutStoriesIdRoute = ColumnsLayoutStoriesIdImport.update({
|
||||||
@@ -366,6 +365,14 @@ const ColumnsLayoutGroupsIdRoute = ColumnsLayoutGroupsIdImport.update({
|
|||||||
import('./routes/columns/_layout/groups.$id.lazy').then((d) => d.Route),
|
import('./routes/columns/_layout/groups.$id.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ColumnsLayoutEventsIdRoute = ColumnsLayoutEventsIdImport.update({
|
||||||
|
id: '/events/$id',
|
||||||
|
path: '/events/$id',
|
||||||
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/columns/_layout/events.$id.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const ColumnsLayoutCreateNewsfeedUsersRoute =
|
const ColumnsLayoutCreateNewsfeedUsersRoute =
|
||||||
ColumnsLayoutCreateNewsfeedUsersImport.update({
|
ColumnsLayoutCreateNewsfeedUsersImport.update({
|
||||||
id: '/users',
|
id: '/users',
|
||||||
@@ -391,13 +398,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'
|
||||||
@@ -405,6 +405,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'
|
||||||
@@ -440,6 +447,20 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutImport
|
preLoaderRoute: typeof ColumnsLayoutImport
|
||||||
parentRoute: typeof ColumnsRoute
|
parentRoute: typeof ColumnsRoute
|
||||||
}
|
}
|
||||||
|
'/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'
|
||||||
@@ -468,12 +489,19 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof NewAccountWatchLazyImport
|
preLoaderRoute: typeof NewAccountWatchLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/settings/$id': {
|
'/settings/general': {
|
||||||
id: '/settings/$id'
|
id: '/settings/general'
|
||||||
path: '/settings/$id'
|
path: '/general'
|
||||||
fullPath: '/settings/$id'
|
fullPath: '/settings/general'
|
||||||
preLoaderRoute: typeof SettingsIdLazyImport
|
preLoaderRoute: typeof SettingsGeneralLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof SettingsLazyImport
|
||||||
|
}
|
||||||
|
'/settings/sync': {
|
||||||
|
id: '/settings/sync'
|
||||||
|
path: '/sync'
|
||||||
|
fullPath: '/settings/sync'
|
||||||
|
preLoaderRoute: typeof SettingsSyncLazyImport
|
||||||
|
parentRoute: typeof SettingsLazyImport
|
||||||
}
|
}
|
||||||
'/_app/': {
|
'/_app/': {
|
||||||
id: '/_app/'
|
id: '/_app/'
|
||||||
@@ -503,27 +531,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'
|
||||||
@@ -580,6 +587,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedUsersImport
|
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedUsersImport
|
||||||
parentRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
parentRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
||||||
}
|
}
|
||||||
|
'/columns/_layout/events/$id': {
|
||||||
|
id: '/columns/_layout/events/$id'
|
||||||
|
path: '/events/$id'
|
||||||
|
fullPath: '/columns/events/$id'
|
||||||
|
preLoaderRoute: typeof ColumnsLayoutEventsIdImport
|
||||||
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
|
}
|
||||||
'/columns/_layout/groups/$id': {
|
'/columns/_layout/groups/$id': {
|
||||||
id: '/columns/_layout/groups/$id'
|
id: '/columns/_layout/groups/$id'
|
||||||
path: '/groups/$id'
|
path: '/groups/$id'
|
||||||
@@ -608,11 +622,11 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutStoriesIdImport
|
preLoaderRoute: typeof ColumnsLayoutStoriesIdImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/events/$id': {
|
'/columns/_layout/dvm/$id': {
|
||||||
id: '/columns/_layout/events/$id'
|
id: '/columns/_layout/dvm/$id'
|
||||||
path: '/events/$id'
|
path: '/dvm/$id'
|
||||||
fullPath: '/columns/events/$id'
|
fullPath: '/columns/dvm/$id'
|
||||||
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
preLoaderRoute: typeof ColumnsLayoutDvmIdLazyImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
parentRoute: typeof ColumnsLayoutImport
|
||||||
}
|
}
|
||||||
'/columns/_layout/launchpad/$id': {
|
'/columns/_layout/launchpad/$id': {
|
||||||
@@ -665,6 +679,24 @@ const AppRouteChildren: AppRouteChildren = {
|
|||||||
|
|
||||||
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
||||||
|
|
||||||
|
interface SettingsLazyRouteChildren {
|
||||||
|
SettingsRelaysRoute: typeof SettingsRelaysRoute
|
||||||
|
SettingsWalletRoute: typeof SettingsWalletRoute
|
||||||
|
SettingsGeneralLazyRoute: typeof SettingsGeneralLazyRoute
|
||||||
|
SettingsSyncLazyRoute: typeof SettingsSyncLazyRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsLazyRouteChildren: SettingsLazyRouteChildren = {
|
||||||
|
SettingsRelaysRoute: SettingsRelaysRoute,
|
||||||
|
SettingsWalletRoute: SettingsWalletRoute,
|
||||||
|
SettingsGeneralLazyRoute: SettingsGeneralLazyRoute,
|
||||||
|
SettingsSyncLazyRoute: SettingsSyncLazyRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren(
|
||||||
|
SettingsLazyRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
interface ColumnsLayoutCreateNewsfeedRouteChildren {
|
interface ColumnsLayoutCreateNewsfeedRouteChildren {
|
||||||
ColumnsLayoutCreateNewsfeedF2fRoute: typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
ColumnsLayoutCreateNewsfeedF2fRoute: typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
ColumnsLayoutCreateNewsfeedUsersRoute: typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
ColumnsLayoutCreateNewsfeedUsersRoute: typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
@@ -691,11 +723,12 @@ interface ColumnsLayoutRouteChildren {
|
|||||||
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
||||||
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
||||||
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
||||||
|
ColumnsLayoutEventsIdRoute: typeof ColumnsLayoutEventsIdRoute
|
||||||
ColumnsLayoutGroupsIdRoute: typeof ColumnsLayoutGroupsIdRoute
|
ColumnsLayoutGroupsIdRoute: typeof ColumnsLayoutGroupsIdRoute
|
||||||
ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute
|
ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute
|
||||||
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
||||||
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
||||||
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
ColumnsLayoutDvmIdLazyRoute: typeof ColumnsLayoutDvmIdLazyRoute
|
||||||
ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute
|
ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||||
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
||||||
ColumnsLayoutRelaysUrlLazyRoute: typeof ColumnsLayoutRelaysUrlLazyRoute
|
ColumnsLayoutRelaysUrlLazyRoute: typeof ColumnsLayoutRelaysUrlLazyRoute
|
||||||
@@ -715,11 +748,12 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
|||||||
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
||||||
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
||||||
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
||||||
|
ColumnsLayoutEventsIdRoute: ColumnsLayoutEventsIdRoute,
|
||||||
ColumnsLayoutGroupsIdRoute: ColumnsLayoutGroupsIdRoute,
|
ColumnsLayoutGroupsIdRoute: ColumnsLayoutGroupsIdRoute,
|
||||||
ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute,
|
ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute,
|
||||||
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
||||||
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
||||||
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
ColumnsLayoutDvmIdLazyRoute: ColumnsLayoutDvmIdLazyRoute,
|
||||||
ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute,
|
ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute,
|
||||||
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
||||||
ColumnsLayoutRelaysUrlLazyRoute: ColumnsLayoutRelaysUrlLazyRoute,
|
ColumnsLayoutRelaysUrlLazyRoute: ColumnsLayoutRelaysUrlLazyRoute,
|
||||||
@@ -742,42 +776,26 @@ 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/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
|
'/settings/general': typeof SettingsGeneralLazyRoute
|
||||||
|
'/settings/sync': typeof SettingsSyncLazyRoute
|
||||||
'/': 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
|
||||||
@@ -786,11 +804,12 @@ export interface FileRoutesByFullPath {
|
|||||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
|
'/columns/events/$id': typeof ColumnsLayoutEventsIdRoute
|
||||||
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||||
'/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/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/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
|
||||||
@@ -799,24 +818,24 @@ 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/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
|
'/settings/general': typeof SettingsGeneralLazyRoute
|
||||||
|
'/settings/sync': typeof SettingsSyncLazyRoute
|
||||||
'/': 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
|
||||||
@@ -825,11 +844,12 @@ export interface FileRoutesByTo {
|
|||||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
|
'/columns/events/$id': typeof ColumnsLayoutEventsIdRoute
|
||||||
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||||
'/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/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/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
|
||||||
@@ -840,25 +860,25 @@ 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/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
|
'/settings/general': typeof SettingsGeneralLazyRoute
|
||||||
|
'/settings/sync': typeof SettingsSyncLazyRoute
|
||||||
'/_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
|
||||||
@@ -867,11 +887,12 @@ export interface FileRoutesById {
|
|||||||
'/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute
|
'/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||||
'/columns/_layout/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
'/columns/_layout/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||||
'/columns/_layout/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
'/columns/_layout/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||||
|
'/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdRoute
|
||||||
'/columns/_layout/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
'/columns/_layout/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||||
'/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/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
'/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
|
||||||
@@ -883,24 +904,24 @@ 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/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'
|
| '/settings/general'
|
||||||
|
| '/settings/sync'
|
||||||
| '/'
|
| '/'
|
||||||
| '/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'
|
||||||
@@ -909,11 +930,12 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/trending'
|
| '/columns/trending'
|
||||||
| '/columns/create-newsfeed/f2f'
|
| '/columns/create-newsfeed/f2f'
|
||||||
| '/columns/create-newsfeed/users'
|
| '/columns/create-newsfeed/users'
|
||||||
|
| '/columns/events/$id'
|
||||||
| '/columns/groups/$id'
|
| '/columns/groups/$id'
|
||||||
| '/columns/interests/$id'
|
| '/columns/interests/$id'
|
||||||
| '/columns/newsfeed/$id'
|
| '/columns/newsfeed/$id'
|
||||||
| '/columns/stories/$id'
|
| '/columns/stories/$id'
|
||||||
| '/columns/events/$id'
|
| '/columns/dvm/$id'
|
||||||
| '/columns/launchpad/$id'
|
| '/columns/launchpad/$id'
|
||||||
| '/columns/notification/$id'
|
| '/columns/notification/$id'
|
||||||
| '/columns/relays/$url'
|
| '/columns/relays/$url'
|
||||||
@@ -921,24 +943,24 @@ 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/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'
|
| '/settings/general'
|
||||||
|
| '/settings/sync'
|
||||||
| '/'
|
| '/'
|
||||||
| '/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'
|
||||||
@@ -947,11 +969,12 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/trending'
|
| '/columns/trending'
|
||||||
| '/columns/create-newsfeed/f2f'
|
| '/columns/create-newsfeed/f2f'
|
||||||
| '/columns/create-newsfeed/users'
|
| '/columns/create-newsfeed/users'
|
||||||
|
| '/columns/events/$id'
|
||||||
| '/columns/groups/$id'
|
| '/columns/groups/$id'
|
||||||
| '/columns/interests/$id'
|
| '/columns/interests/$id'
|
||||||
| '/columns/newsfeed/$id'
|
| '/columns/newsfeed/$id'
|
||||||
| '/columns/stories/$id'
|
| '/columns/stories/$id'
|
||||||
| '/columns/events/$id'
|
| '/columns/dvm/$id'
|
||||||
| '/columns/launchpad/$id'
|
| '/columns/launchpad/$id'
|
||||||
| '/columns/notification/$id'
|
| '/columns/notification/$id'
|
||||||
| '/columns/relays/$url'
|
| '/columns/relays/$url'
|
||||||
@@ -960,25 +983,25 @@ 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/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'
|
| '/settings/general'
|
||||||
|
| '/settings/sync'
|
||||||
| '/_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'
|
||||||
@@ -987,11 +1010,12 @@ export interface FileRouteTypes {
|
|||||||
| '/columns/_layout/trending'
|
| '/columns/_layout/trending'
|
||||||
| '/columns/_layout/create-newsfeed/f2f'
|
| '/columns/_layout/create-newsfeed/f2f'
|
||||||
| '/columns/_layout/create-newsfeed/users'
|
| '/columns/_layout/create-newsfeed/users'
|
||||||
|
| '/columns/_layout/events/$id'
|
||||||
| '/columns/_layout/groups/$id'
|
| '/columns/_layout/groups/$id'
|
||||||
| '/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/events/$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'
|
||||||
@@ -1002,8 +1026,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
|
||||||
@@ -1012,14 +1036,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,
|
||||||
@@ -1028,7 +1051,6 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
NewAccountConnectLazyRoute: NewAccountConnectLazyRoute,
|
NewAccountConnectLazyRoute: NewAccountConnectLazyRoute,
|
||||||
NewAccountImportLazyRoute: NewAccountImportLazyRoute,
|
NewAccountImportLazyRoute: NewAccountImportLazyRoute,
|
||||||
NewAccountWatchLazyRoute: NewAccountWatchLazyRoute,
|
NewAccountWatchLazyRoute: NewAccountWatchLazyRoute,
|
||||||
SettingsIdLazyRoute: SettingsIdLazyRouteWithChildren,
|
|
||||||
NewPostIndexRoute: NewPostIndexRoute,
|
NewPostIndexRoute: NewPostIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1036,8 +1058,6 @@ export const routeTree = rootRoute
|
|||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
._addFileTypes<FileRouteTypes>()
|
._addFileTypes<FileRouteTypes>()
|
||||||
|
|
||||||
/* prettier-ignore-end */
|
|
||||||
|
|
||||||
/* ROUTE_MANIFEST_START
|
/* ROUTE_MANIFEST_START
|
||||||
{
|
{
|
||||||
"routes": {
|
"routes": {
|
||||||
@@ -1045,8 +1065,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",
|
||||||
@@ -1055,7 +1075,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/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1065,12 +1084,18 @@ 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/relays",
|
||||||
|
"/settings/wallet",
|
||||||
|
"/settings/general",
|
||||||
|
"/settings/sync"
|
||||||
|
]
|
||||||
|
},
|
||||||
"/$id/set-group": {
|
"/$id/set-group": {
|
||||||
"filePath": "$id.set-group.tsx"
|
"filePath": "$id.set-group.tsx"
|
||||||
},
|
},
|
||||||
@@ -1098,11 +1123,12 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/onboarding",
|
"/columns/_layout/onboarding",
|
||||||
"/columns/_layout/search",
|
"/columns/_layout/search",
|
||||||
"/columns/_layout/trending",
|
"/columns/_layout/trending",
|
||||||
|
"/columns/_layout/events/$id",
|
||||||
"/columns/_layout/groups/$id",
|
"/columns/_layout/groups/$id",
|
||||||
"/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/events/$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",
|
||||||
@@ -1110,6 +1136,14 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/users/$id"
|
"/columns/_layout/users/$id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/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"
|
||||||
},
|
},
|
||||||
@@ -1122,13 +1156,13 @@ export const routeTree = rootRoute
|
|||||||
"/new-account/watch": {
|
"/new-account/watch": {
|
||||||
"filePath": "new-account/watch.lazy.tsx"
|
"filePath": "new-account/watch.lazy.tsx"
|
||||||
},
|
},
|
||||||
"/settings/$id": {
|
"/settings/general": {
|
||||||
"filePath": "settings.$id.lazy.tsx",
|
"filePath": "settings/general.lazy.tsx",
|
||||||
"children": [
|
"parent": "/settings"
|
||||||
"/settings/$id/general",
|
},
|
||||||
"/settings/$id/relay",
|
"/settings/sync": {
|
||||||
"/settings/$id/wallet"
|
"filePath": "settings/sync.lazy.tsx",
|
||||||
]
|
"parent": "/settings"
|
||||||
},
|
},
|
||||||
"/_app/": {
|
"/_app/": {
|
||||||
"filePath": "_app/index.tsx",
|
"filePath": "_app/index.tsx",
|
||||||
@@ -1149,18 +1183,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"
|
||||||
@@ -1193,6 +1215,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/create-newsfeed.users.tsx",
|
"filePath": "columns/_layout/create-newsfeed.users.tsx",
|
||||||
"parent": "/columns/_layout/create-newsfeed"
|
"parent": "/columns/_layout/create-newsfeed"
|
||||||
},
|
},
|
||||||
|
"/columns/_layout/events/$id": {
|
||||||
|
"filePath": "columns/_layout/events.$id.tsx",
|
||||||
|
"parent": "/columns/_layout"
|
||||||
|
},
|
||||||
"/columns/_layout/groups/$id": {
|
"/columns/_layout/groups/$id": {
|
||||||
"filePath": "columns/_layout/groups.$id.tsx",
|
"filePath": "columns/_layout/groups.$id.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
@@ -1209,8 +1235,8 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/stories.$id.tsx",
|
"filePath": "columns/_layout/stories.$id.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/events/$id": {
|
"/columns/_layout/dvm/$id": {
|
||||||
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
"filePath": "columns/_layout/dvm.$id.lazy.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/columns/_layout/launchpad/$id": {
|
"/columns/_layout/launchpad/$id": {
|
||||||
|
|||||||
+12
-2
@@ -1,4 +1,5 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { commands } from "@/commands.gen";
|
||||||
|
import { cn } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import type { Metadata, NostrEvent } from "@/types";
|
import type { Metadata, NostrEvent } from "@/types";
|
||||||
import { type QueryClient, queryOptions } from "@tanstack/react-query";
|
import { type QueryClient, queryOptions } from "@tanstack/react-query";
|
||||||
@@ -36,7 +37,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { queryClient } = Route.useRouteContext();
|
const { queryClient, platform } = Route.useRouteContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = getCurrentWindow().listen<string>(
|
const unlisten = getCurrentWindow().listen<string>(
|
||||||
@@ -60,7 +61,16 @@ function Screen() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <Outlet />;
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"size-full",
|
||||||
|
platform === "windows" ? "bg-neutral-100 dark:bg-neutral-900" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Pending() {
|
function Pending() {
|
||||||
|
|||||||
@@ -22,10 +22,19 @@ export const Route = createLazyFileRoute("/_app")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Layout() {
|
function Layout() {
|
||||||
|
const { platform } = Route.useRouteContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-screen h-screen">
|
<div className="flex flex-col w-screen h-screen">
|
||||||
<Topbar />
|
<Topbar />
|
||||||
<div className="flex-1 bg-neutral-100 dark:bg-neutral-900 border-t-[.5px] border-black/20 dark:border-white/30">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex-1 bg-neutral-100 dark:bg-neutral-900 border-t-[.5px]",
|
||||||
|
platform === "windows"
|
||||||
|
? "border-black/10 dark:border-white/10"
|
||||||
|
: "border-black/20 dark:border-white/30",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +118,7 @@ function Account({ pubkey }: { pubkey: string }) {
|
|||||||
const items = await Promise.all([
|
const items = await Promise.all([
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
text: "Unlock",
|
text: "Unlock",
|
||||||
enabled: !isActive || true,
|
enabled: !isActive,
|
||||||
action: async () => await commands.setSigner(pubkey),
|
action: async () => await commands.setSigner(pubkey),
|
||||||
}),
|
}),
|
||||||
PredefinedMenuItem.new({ item: "Separator" }),
|
PredefinedMenuItem.new({ item: "Separator" }),
|
||||||
@@ -145,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({
|
||||||
@@ -174,7 +183,7 @@ function Account({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
await menu.popup().catch((e) => console.error(e));
|
await menu.popup().catch((e) => console.error(e));
|
||||||
},
|
},
|
||||||
[pubkey],
|
[isActive, pubkey],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
+38
-11
@@ -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",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return { accounts }
|
if (yes) {
|
||||||
},
|
await update.downloadAndInstall();
|
||||||
})
|
await relaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function Screen() {
|
|||||||
data?.map((item) => (
|
data?.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item}
|
key={item}
|
||||||
className="w-full p-2 mb-2 overflow-hidden bg-white rounded-lg h-max dark:bg-black/20shadow-primary dark:ring-1 ring-neutral-800/50"
|
className="w-full p-2 mb-2 overflow-hidden bg-white rounded-lg h-max dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||||
>
|
>
|
||||||
<User.Provider pubkey={item}>
|
<User.Provider pubkey={item}>
|
||||||
<User.Root>
|
<User.Root>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ function Screen() {
|
|||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(item: NostrEvent) => {
|
(item: NostrEvent) => {
|
||||||
|
const users = item.tags.filter((tag) => tag[0] === "p");
|
||||||
const name =
|
const name =
|
||||||
item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
|
item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
|
||||||
|
|
||||||
const label = item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
|
const label = item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -71,32 +71,24 @@ function Screen() {
|
|||||||
className="mb-3 group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
className="mb-3 group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||||
>
|
>
|
||||||
<div className="px-2 pt-2">
|
<div className="px-2 pt-2">
|
||||||
<ScrollArea.Root
|
<div className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg">
|
||||||
type={"scroll"}
|
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||||
scrollHideDelay={300}
|
{users.slice(0, 5).map((tag) => (
|
||||||
className="overflow-hidden size-full"
|
<User.Provider key={tag[1]} pubkey={tag[1]}>
|
||||||
>
|
<User.Root>
|
||||||
<ScrollArea.Viewport className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg">
|
<User.Avatar className="size-8 rounded-full" />
|
||||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
</User.Root>
|
||||||
{item.tags
|
</User.Provider>
|
||||||
.filter((tag) => tag[0] === "p")
|
))}
|
||||||
.map((tag) => (
|
{users.length > 5 ? (
|
||||||
<User.Provider key={tag[1]} pubkey={tag[1]}>
|
<div className="size-8 rounded-full inline-flex items-center justify-center bg-neutral-300 dark:bg-neutral-700">
|
||||||
<User.Root>
|
<p className="truncate leading-tight text-[8px] font-medium">
|
||||||
<User.Avatar className="size-8 rounded-full" />
|
+{users.length - 5}
|
||||||
</User.Root>
|
</p>
|
||||||
</User.Provider>
|
</div>
|
||||||
))}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea.Viewport>
|
</div>
|
||||||
<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>
|
</div>
|
||||||
<div className="p-2 flex items-center justify-between">
|
<div className="p-2 flex items-center justify-between">
|
||||||
<div className="inline-flex items-center gap-2">
|
<div className="inline-flex items-center gap-2">
|
||||||
@@ -145,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>
|
||||||
@@ -158,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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -99,14 +99,9 @@ function ReplyList() {
|
|||||||
const res = await commands.getReplies(id);
|
const res = await commands.getReplies(id);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const events = res.data
|
const events = res.data.map((item) =>
|
||||||
// Create Lume Events
|
LumeEvent.from(item.raw, item.parsed),
|
||||||
.map((item) => LumeEvent.from(item.raw, item.parsed))
|
);
|
||||||
// Filter quote
|
|
||||||
.filter(
|
|
||||||
(ev) =>
|
|
||||||
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
|
|
||||||
);
|
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
} else {
|
} else {
|
||||||
@@ -175,15 +170,13 @@ function ReplyList() {
|
|||||||
|
|
||||||
return events.filter((ev) => !removeQueues.has(ev.id));
|
return events.filter((ev) => !removeQueues.has(ev.id));
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = getCurrentWindow().listen<EventPayload>(
|
const unlisten = getCurrentWindow().listen<EventPayload>(
|
||||||
"event",
|
"comment",
|
||||||
async (data) => {
|
async (data) => {
|
||||||
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
||||||
console.log(event);
|
|
||||||
|
|
||||||
await queryClient.setQueryData(
|
await queryClient.setQueryData(
|
||||||
["replies", id],
|
["replies", id],
|
||||||
@@ -200,6 +193,16 @@ function ReplyList() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = getCurrentWindow().listen(id, async () => {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ["replies", id] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3">
|
<div className="px-3">
|
||||||
<div className="flex items-center text-sm font-semibold h-14 text-neutral-600 dark:text-white/30">
|
<div className="flex items-center text-sm font-semibold h-14 text-neutral-600 dark:text-white/30">
|
||||||
@@ -208,7 +211,7 @@ function ReplyList() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Getting replies...</span>
|
<span className="text-sm font-medium">Loading replies...</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/columns/_layout/events/$id")();
|
||||||
@@ -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
|
||||||
@@ -62,14 +66,14 @@ function Newsfeeds() {
|
|||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(item: NostrEvent) => {
|
(item: NostrEvent) => {
|
||||||
|
const users = item.tags.filter((tag) => tag[0] === "p");
|
||||||
const name =
|
const name =
|
||||||
item.kind === 3
|
item.kind === 3
|
||||||
? "Contacts"
|
? "Contacts"
|
||||||
: 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 (
|
||||||
@@ -78,32 +82,24 @@ function Newsfeeds() {
|
|||||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||||
>
|
>
|
||||||
<div className="px-2 pt-2">
|
<div className="px-2 pt-2">
|
||||||
<ScrollArea.Root
|
<div className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg">
|
||||||
type={"scroll"}
|
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||||
scrollHideDelay={300}
|
{users.slice(0, 5).map((tag) => (
|
||||||
className="overflow-hidden size-full"
|
<User.Provider key={tag[1]} pubkey={tag[1]}>
|
||||||
>
|
<User.Root>
|
||||||
<ScrollArea.Viewport className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg">
|
<User.Avatar className="size-8 rounded-full" />
|
||||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
</User.Root>
|
||||||
{item.tags
|
</User.Provider>
|
||||||
.filter((tag) => tag[0] === "p")
|
))}
|
||||||
.map((tag) => (
|
{users.length > 5 ? (
|
||||||
<User.Provider key={tag[1]} pubkey={tag[1]}>
|
<div className="size-8 rounded-full inline-flex items-center justify-center bg-neutral-300 dark:bg-neutral-700">
|
||||||
<User.Root>
|
<p className="truncate leading-tight text-[8px] font-medium">
|
||||||
<User.Avatar className="size-8 rounded-full" />
|
+{users.length - 5}
|
||||||
</User.Root>
|
</p>
|
||||||
</User.Provider>
|
</div>
|
||||||
))}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea.Viewport>
|
</div>
|
||||||
<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>
|
</div>
|
||||||
<div className="p-2 flex items-center justify-between">
|
<div className="p-2 flex items-center justify-between">
|
||||||
<div className="inline-flex items-center gap-2">
|
<div className="inline-flex items-center gap-2">
|
||||||
@@ -186,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>
|
||||||
@@ -201,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({
|
||||||
@@ -269,22 +438,20 @@ 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={() =>
|
LumeWindow.openColumn({
|
||||||
LumeWindow.openColumn({
|
label,
|
||||||
label,
|
name,
|
||||||
name,
|
account: id,
|
||||||
account: id,
|
url: `/columns/interests/${item.id}`,
|
||||||
url: `/columns/interests/${item.id}`,
|
})
|
||||||
})
|
}
|
||||||
}
|
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
>
|
||||||
>
|
Add
|
||||||
Add
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -340,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>
|
||||||
@@ -355,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({
|
||||||
@@ -399,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
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { Metadata } from "@/types";
|
|||||||
import { CaretDown } from "@phosphor-icons/react";
|
import { CaretDown } from "@phosphor-icons/react";
|
||||||
import { createLazyFileRoute, useAwaited } from "@tanstack/react-router";
|
import { createLazyFileRoute, useAwaited } from "@tanstack/react-router";
|
||||||
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -69,7 +70,7 @@ export const Route = createLazyFileRoute("/new-post/")({
|
|||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { reply_to } = Route.useSearch();
|
const { reply_to } = Route.useSearch();
|
||||||
const { accounts, initialValue, queryClient } = Route.useRouteContext();
|
const { accounts, initialValue } = Route.useRouteContext();
|
||||||
const { deferMentionList } = Route.useLoaderData();
|
const { deferMentionList } = Route.useLoaderData();
|
||||||
const users = useAwaited({ promise: deferMentionList })[0];
|
const users = useAwaited({ promise: deferMentionList })[0];
|
||||||
|
|
||||||
@@ -170,9 +171,7 @@ function Screen() {
|
|||||||
setText("");
|
setText("");
|
||||||
setIsPublish(true);
|
setIsPublish(true);
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
await getCurrentWindow().emit(reply_to, {});
|
||||||
queryKey: ["replies", reply_to],
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setError(res.error);
|
setError(res.error);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -1,3 +1,4 @@
|
|||||||
|
import { cn } from "@/commons";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/new")({
|
export const Route = createLazyFileRoute("/new")({
|
||||||
@@ -5,10 +6,17 @@ export const Route = createLazyFileRoute("/new")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
|
const { platform } = Route.useRouteContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="bg-white/50 dark:bg-black/50 relative size-full flex items-center justify-center"
|
className={cn(
|
||||||
|
"relative size-full flex items-center justify-center",
|
||||||
|
platform === "windows"
|
||||||
|
? "bg-neutral-200 dark:bg-neutral-900"
|
||||||
|
: "bg-white/20 dark:bg-black/20",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-[350px] flex flex-col gap-8">
|
<div className="w-[350px] flex flex-col gap-8">
|
||||||
<div className="flex flex-col gap-1 text-center">
|
<div className="flex flex-col gap-1 text-center">
|
||||||
@@ -19,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,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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
import { cn } from "@/commons";
|
import { cn } from "@/commons";
|
||||||
import { CurrencyBtc, GearSix, HardDrives, User } from "@phosphor-icons/react";
|
import {
|
||||||
|
CloudArrowDown,
|
||||||
|
CurrencyBtc,
|
||||||
|
GearSix,
|
||||||
|
HardDrives,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
import { Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/settings/$id")({
|
export const Route = createLazyFileRoute("/settings")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { id } = Route.useParams();
|
|
||||||
const { platform } = Route.useRouteContext();
|
const { platform } = Route.useRouteContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -24,7 +28,7 @@ function Screen() {
|
|||||||
<div className="h-8 px-1.5">
|
<div className="h-8 px-1.5">
|
||||||
<h1 className="text-lg font-semibold">Settings</h1>
|
<h1 className="text-lg font-semibold">Settings</h1>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/settings/$id/general" params={{ id }}>
|
<Link to="/settings/general">
|
||||||
{({ isActive }) => {
|
{({ isActive }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -41,7 +45,7 @@ function Screen() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/settings/$id/profile" params={{ id }}>
|
<Link to="/settings/sync">
|
||||||
{({ isActive }) => {
|
{({ isActive }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -52,13 +56,13 @@ function Screen() {
|
|||||||
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<User className="size-5 shrink-0" />
|
<CloudArrowDown className="size-5 shrink-0" />
|
||||||
<p className="text-sm font-medium">Profile</p>
|
<p className="text-sm font-medium">Sync</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/settings/$id/relay" params={{ id }}>
|
<Link to="/settings/relays">
|
||||||
{({ isActive }) => {
|
{({ isActive }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -70,12 +74,12 @@ function Screen() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HardDrives className="size-5 shrink-0" />
|
<HardDrives className="size-5 shrink-0" />
|
||||||
<p className="text-sm font-medium">Relay</p>
|
<p className="text-sm font-medium">Relays</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/settings/$id/wallet" params={{ id }}>
|
<Link to="/settings/wallet">
|
||||||
{({ isActive }) => {
|
{({ isActive }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -68,9 +69,7 @@ function Screen() {
|
|||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<div className="flex flex-col gap-6 px-3 pb-3">
|
<div className="flex flex-col gap-6 px-3 pb-3">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<h2 className="text-sm font-semibold">General</h2>
|
||||||
General
|
|
||||||
</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 flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<Setting
|
<Setting
|
||||||
name="Content Warning"
|
name="Content Warning"
|
||||||
@@ -91,9 +90,7 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<h2 className="text-sm font-semibold">Appearance</h2>
|
||||||
Appearance
|
|
||||||
</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 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-start justify-between w-full gap-4 py-3">
|
<div className="flex items-start justify-between w-full gap-4 py-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -139,9 +136,7 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<h2 className="text-sm font-semibold">Privacy & Performance</h2>
|
||||||
Privacy & Performance
|
|
||||||
</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 flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<Setting
|
<Setting
|
||||||
name="Resize Service"
|
name="Resize Service"
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
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">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-sm font-semibold">Connected Relays</h2>
|
||||||
|
<p className="text-sm text-neutral-500">
|
||||||
|
Learn more about Relays{" "}
|
||||||
|
<a
|
||||||
|
href="https://nostr.how/en/relays"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</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">
|
||||||
|
<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);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useEffect, useState, useTransition } from "react";
|
||||||
|
import * as Progress from "@radix-ui/react-progress";
|
||||||
|
import { Channel } from "@tauri-apps/api/core";
|
||||||
|
import { commands } from "@/commands.gen";
|
||||||
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { Spinner } from "@/components";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/settings/sync")({
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const [channel, _setChannel] = useState<Channel<number>>(
|
||||||
|
() => new Channel<number>(),
|
||||||
|
);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const runSync = () => {
|
||||||
|
startTransition(async () => {
|
||||||
|
const res = await commands.syncAll(channel);
|
||||||
|
|
||||||
|
if (res.status === "error") {
|
||||||
|
await message(res.error, { kind: "error" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
channel.onmessage = (message) => {
|
||||||
|
setProgress(message);
|
||||||
|
};
|
||||||
|
}, [channel]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full px-3 pb-3">
|
||||||
|
<div className="h-full flex flex-col w-full gap-2">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-sm font-semibold">Sync events with Negentropy</h2>
|
||||||
|
<p className="text-sm text-neutral-500">
|
||||||
|
Learn more about negentropy{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/hoytech/strfry/blob/nextneg/docs/negentropy.md"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm flex flex-col gap-2">
|
||||||
|
<h5 className="font-semibold">Data will be sync:</h5>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
Metadata of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
Contact list of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
Follow and interest sets of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
All notes and reposts of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
All comments all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-auto flex items-center gap-4 justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Progress.Root
|
||||||
|
className="relative overflow-hidden bg-black/20 dark:bg-white/20 rounded-full w-full h-1"
|
||||||
|
style={{
|
||||||
|
transform: "translateZ(0)",
|
||||||
|
}}
|
||||||
|
value={progress}
|
||||||
|
>
|
||||||
|
<Progress.Indicator
|
||||||
|
className="bg-blue-500 size-full rounded-full transition-transform duration-[660ms] ease-[cubic-bezier(0.65, 0, 0.35, 1)]"
|
||||||
|
style={{ transform: `translateX(-${100 - progress}%)` }}
|
||||||
|
/>
|
||||||
|
</Progress.Root>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => runSync()}
|
||||||
|
className="shrink-0 w-20 h-8 rounded-lg inline-flex items-center justify-center bg-blue-500 hover:bg-blue-600 text-white text-sm font-semibold"
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner className="size-4" /> : "Sync"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,9 +33,20 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full px-3 pb-3">
|
<div className="w-full px-3 pb-3">
|
||||||
<div className="flex flex-col w-full gap-2">
|
<div className="flex flex-col w-full gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<div>
|
||||||
Wallet
|
<h2 className="text-sm font-semibold">Bitcoin Wallet</h2>
|
||||||
</h2>
|
<p className="text-sm text-neutral-500">
|
||||||
|
Learn more about Zap{" "}
|
||||||
|
<a
|
||||||
|
href="https://nostr.how/en/zaps"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="w-full h-44 flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-xl">
|
<div className="w-full h-44 flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<Button
|
<Button
|
||||||
onConnected={(provider) =>
|
onConnected={(provider) =>
|
||||||
@@ -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
-4
@@ -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: {
|
||||||
|
|||||||
+4
-13
@@ -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"
|
||||||
|
|||||||
+1
-3
@@ -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