Compare commits

...
7 Commits
Author SHA1 Message Date
reya fab34de1ee chore: update readme 2025-04-01 17:52:13 +07:00
reya 9730837e00 chore: update deps 2025-01-11 20:18:30 +07:00
reya ec34255df1 chore: update deps 2024-11-30 07:39:23 +07:00
reya a262217ab2 chore: release 2024-11-11 10:02:44 +07:00
reya c5d06a2492 feat: add negentropy sync to settings 2024-11-11 10:02:19 +07:00
reya c93edde7d2 chore: use upstream rust nostr 2024-11-11 07:56:31 +07:00
reya 5103126001 fix: build on windows 2024-11-10 14:28:14 +07:00
19 changed files with 2184 additions and 1772 deletions
+1 -33
View File
@@ -1,33 +1 @@
## Introduction ### Lume will be rewritten in Rust
Lume is a Nostr client for macOS and Windows 11. It is free and open source, you can look at source code on Github. Lume is actively improving the app and adding new features, you can expect new update every month.
## Installation and Usage
- *Microsoft Windows*: See the releases area for a file named something like Lume_VERSION_x64-setup.exe or Lume_VERSION_x64_en-US.msi
- *macOS*: See the releases area for a file named something like Lume_VERSION_PLATFORM.dmg
Lume only supported macOS and Windows 11. Linux user can consider using [Gossip client](https://github.com/mikedilger/gossip)
## Screenshots
![Login Screen](https://image.nostr.build/d7a59ada0ed107e9556b0c8e547803f41f99e7973da4e52eab1b0b0a7dbdfadf.png)
![Welcome Screen](https://image.nostr.build/b6f63e5bda01a37de06e59bd2cebc7be47fb6a8b01ce3155b7269d5235e6db0c.png)
![Newsfeed](https://image.nostr.build/66fdcd96c6008794a02fa282e70a4538393c2a0041b1ee52aaf09893c17dba96.png)
![Thread](https://image.nostr.build/11538fae77da1e8b00099b92642f2d9e40f6fbf7fde49459c93a9d99c97e4cfc.png)
![Dark Mode](https://image.nostr.build/6b6c024a029a61d96d507dd7d1d8f7c48332cc77aad1bb87c6a952b8d9175348.png)
## Building from Source
See [Developing](docs/DEVELOPING.md)
## License
Copyright (C) 2023-2024 Ren Amamiya & other Lume contributors
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
+83 -83
View File
@@ -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.3",
"@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.2",
"@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/query-broadcast-client-experimental": "^5.59.20", "@tanstack/query-broadcast-client-experimental": "^5.62.16",
"@tanstack/query-persist-client-core": "^5.59.20", "@tanstack/query-persist-client-core": "^5.62.16",
"@tanstack/react-query": "^5.59.20", "@tanstack/react-query": "^5.63.0",
"@tanstack/react-router": "^1.81.0", "@tanstack/react-router": "^1.95.3",
"@tauri-apps/api": "^2.1.0", "@tauri-apps/api": "^2.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0", "@tauri-apps/plugin-clipboard-manager": "^2.2.0",
"@tauri-apps/plugin-dialog": "^2.0.1", "@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.0.2", "@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-http": "^2.0.1", "@tauri-apps/plugin-http": "^2.2.0",
"@tauri-apps/plugin-os": "^2.0.0", "@tauri-apps/plugin-os": "^2.2.0",
"@tauri-apps/plugin-process": "^2.0.0", "@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "^2.0.1", "@tauri-apps/plugin-shell": "^2.2.0",
"@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.3.1",
"@tauri-apps/plugin-upload": "^2.1.0", "@tauri-apps/plugin-upload": "^2.2.1",
"@tauri-apps/plugin-window-state": "^2.0.0", "@tauri-apps/plugin-window-state": "^2.2.0",
"bitcoin-units": "^1.0.0", "bitcoin-units": "^1.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"embla-carousel-react": "^8.3.1", "embla-carousel-react": "^8.5.2",
"i18next": "^23.16.5", "i18next": "^23.16.8",
"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.8", "nanoid": "^5.0.9",
"nostr-tools": "^2.10.1", "nostr-tools": "^2.10.4",
"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.9.0",
"react-dom": "19.0.0-rc-cae764ce-20241025", "react-dom": "19.0.0-rc-cae764ce-20241025",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.54.2",
"react-i18next": "^15.1.1", "react-i18next": "^15.4.0",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"rich-textarea": "^0.26.4", "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.10",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.16",
"@tanstack/router-devtools": "^1.81.0", "@tanstack/router-devtools": "^1.95.3",
"@tanstack/router-plugin": "^1.79.0", "@tanstack/router-plugin": "^1.95.3",
"@tauri-apps/cli": "^2.1.0", "@tauri-apps/cli": "^2.2.3",
"@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.4",
"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.49",
"tailwind-gradient-mask-image": "^1.2.0", "tailwind-gradient-mask-image": "^1.2.0",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.6.0",
"tailwind-scrollbar": "^3.1.0", "tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.14", "tailwindcss": "^3.4.17",
"tailwindcss-content-visibility": "^1.0.0", "tailwindcss-content-visibility": "^1.0.2",
"typescript": "^5.6.3", "typescript": "^5.7.3",
"vite": "^5.4.10", "vite": "^5.4.11",
"vite-tsconfig-paths": "^5.1.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"
} }
} }
+808 -841
View File
File diff suppressed because it is too large Load Diff
+898 -663
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -52,10 +52,6 @@ webbrowser = "1.0.2"
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] } nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" } nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
[patch.'https://github.com/rust-nostr/nostr']
nostr-sdk = { git = "https://github.com/reyamir/nostr", branch = "feat/nip-22", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/reyamir/nostr", branch = "feat/nip-22" }
[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" }
share-picker = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" } share-picker = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
+1
View File
@@ -2,4 +2,5 @@ pub mod account;
pub mod event; pub mod event;
pub mod metadata; pub mod metadata;
pub mod relay; pub mod relay;
pub mod sync;
pub mod window; pub mod window;
+71
View File
@@ -0,0 +1,71 @@
use nostr_sdk::prelude::*;
use std::collections::HashSet;
use tauri::State;
use crate::Nostr;
#[tauri::command]
#[specta::specta]
pub async fn sync_all(
state: State<'_, Nostr>,
reader: tauri::ipc::Channel<f64>,
) -> Result<(), String> {
let client = &state.client;
// Create a filter for get all public keys
let filter = Filter::new().kinds(vec![
Kind::TextNote,
Kind::Repost,
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 opts = SyncOptions::default().progress(tx);
tauri::async_runtime::spawn(async move {
while rx.changed().await.is_ok() {
let progress = *rx.borrow_and_update();
if progress.total > 0 {
reader.send(progress.percentage() * 100.0).unwrap();
}
}
});
for chunk in public_keys.chunks(200) {
let authors = chunk.to_owned();
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 _ = client
.sync(filter, &opts)
.await
.map_err(|err| err.to_string())?;
}
Ok(())
}
+1 -1
View File
@@ -104,7 +104,7 @@ 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 relay_url = event.relays.first().map(UncheckedUrl::from); let relay_url = event.relays.first().and_then(|i| Url::parse(i).ok());
let tag = Tag::from_standardized(TagStandard::Quote { let tag = Tag::from_standardized(TagStandard::Quote {
event_id: event.event_id, event_id: event.event_id,
relay_url, relay_url,
+8 -5
View File
@@ -5,7 +5,7 @@
#[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};
@@ -76,6 +76,7 @@ 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![
sync_all,
get_all_relays, get_all_relays,
get_all_relay_lists, get_all_relay_lists,
is_relay_connected, is_relay_connected,
@@ -365,6 +366,8 @@ fn main() {
// Set interval // Set interval
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(600)); let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(600));
// Skip the first tick
interval.tick().await;
loop { loop {
interval.tick().await; interval.tick().await;
@@ -475,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());
@@ -603,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()
{ {
@@ -614,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()
{ {
@@ -625,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 -1
View File
@@ -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.7", "version": "24.11.8",
"identifier": "nu.lume.Lume", "identifier": "nu.lume.Lume",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",
+9
View File
@@ -5,6 +5,14 @@
export const commands = { export const commands = {
async syncAll(reader: TAURI_CHANNEL<number>) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("sync_all", { reader }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getAllRelays() : Promise<Result<string[], string>> { async getAllRelays() : Promise<Result<string[], string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("get_all_relays") }; return { status: "ok", data: await TAURI_INVOKE("get_all_relays") };
@@ -554,6 +562,7 @@ export type Meta = { content: string; images: string[]; events: string[]; mentio
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 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 **/
+57 -30
View File
@@ -19,7 +19,6 @@ 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 SettingsWalletImport } from './routes/settings/wallet'
import { Route as SettingsRelaysImport } from './routes/settings/relays' import { Route as SettingsRelaysImport } from './routes/settings/relays'
import { Route as SettingsGeneralImport } from './routes/settings/general'
import { Route as ColumnsLayoutImport } from './routes/columns/_layout' import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
import { Route as IdSetProfileImport } from './routes/$id.set-profile' import { Route as IdSetProfileImport } from './routes/$id.set-profile'
import { Route as IdSetInterestImport } from './routes/$id.set-interest' import { Route as IdSetInterestImport } from './routes/$id.set-interest'
@@ -39,6 +38,8 @@ import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/
const ColumnsImport = createFileRoute('/columns')() const ColumnsImport = createFileRoute('/columns')()
const SettingsLazyImport = createFileRoute('/settings')() const SettingsLazyImport = createFileRoute('/settings')()
const NewLazyImport = createFileRoute('/new')() const NewLazyImport = createFileRoute('/new')()
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')()
@@ -118,6 +119,20 @@ 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 SettingsSyncLazyRoute = SettingsSyncLazyImport.update({
id: '/sync',
path: '/sync',
getParentRoute: () => SettingsLazyRoute,
} 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',
path: '/new-account/watch', path: '/new-account/watch',
@@ -164,14 +179,6 @@ const SettingsRelaysRoute = SettingsRelaysImport.update({
import('./routes/settings/relays.lazy').then((d) => d.Route), import('./routes/settings/relays.lazy').then((d) => d.Route),
) )
const SettingsGeneralRoute = SettingsGeneralImport.update({
id: '/general',
path: '/general',
getParentRoute: () => SettingsLazyRoute,
} as any).lazy(() =>
import('./routes/settings/general.lazy').then((d) => d.Route),
)
const ColumnsLayoutRoute = ColumnsLayoutImport.update({ const ColumnsLayoutRoute = ColumnsLayoutImport.update({
id: '/_layout', id: '/_layout',
getParentRoute: () => ColumnsRoute, getParentRoute: () => ColumnsRoute,
@@ -440,13 +447,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ColumnsLayoutImport preLoaderRoute: typeof ColumnsLayoutImport
parentRoute: typeof ColumnsRoute parentRoute: typeof ColumnsRoute
} }
'/settings/general': {
id: '/settings/general'
path: '/general'
fullPath: '/settings/general'
preLoaderRoute: typeof SettingsGeneralImport
parentRoute: typeof SettingsLazyImport
}
'/settings/relays': { '/settings/relays': {
id: '/settings/relays' id: '/settings/relays'
path: '/relays' path: '/relays'
@@ -489,6 +489,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof NewAccountWatchLazyImport preLoaderRoute: typeof NewAccountWatchLazyImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/settings/general': {
id: '/settings/general'
path: '/general'
fullPath: '/settings/general'
preLoaderRoute: typeof SettingsGeneralLazyImport
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/'
path: '/' path: '/'
@@ -666,15 +680,17 @@ const AppRouteChildren: AppRouteChildren = {
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren) const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
interface SettingsLazyRouteChildren { interface SettingsLazyRouteChildren {
SettingsGeneralRoute: typeof SettingsGeneralRoute
SettingsRelaysRoute: typeof SettingsRelaysRoute SettingsRelaysRoute: typeof SettingsRelaysRoute
SettingsWalletRoute: typeof SettingsWalletRoute SettingsWalletRoute: typeof SettingsWalletRoute
SettingsGeneralLazyRoute: typeof SettingsGeneralLazyRoute
SettingsSyncLazyRoute: typeof SettingsSyncLazyRoute
} }
const SettingsLazyRouteChildren: SettingsLazyRouteChildren = { const SettingsLazyRouteChildren: SettingsLazyRouteChildren = {
SettingsGeneralRoute: SettingsGeneralRoute,
SettingsRelaysRoute: SettingsRelaysRoute, SettingsRelaysRoute: SettingsRelaysRoute,
SettingsWalletRoute: SettingsWalletRoute, SettingsWalletRoute: SettingsWalletRoute,
SettingsGeneralLazyRoute: SettingsGeneralLazyRoute,
SettingsSyncLazyRoute: SettingsSyncLazyRoute,
} }
const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren( const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren(
@@ -768,13 +784,14 @@ export interface FileRoutesByFullPath {
'/$id/set-interest': typeof IdSetInterestRoute '/$id/set-interest': typeof IdSetInterestRoute
'/$id/set-profile': typeof IdSetProfileRoute '/$id/set-profile': typeof IdSetProfileRoute
'/columns': typeof ColumnsLayoutRouteWithChildren '/columns': typeof ColumnsLayoutRouteWithChildren
'/settings/general': typeof SettingsGeneralRoute
'/settings/relays': typeof SettingsRelaysRoute '/settings/relays': typeof SettingsRelaysRoute
'/settings/wallet': typeof SettingsWalletRoute '/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/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
@@ -807,13 +824,14 @@ export interface FileRoutesByTo {
'/$id/set-interest': typeof IdSetInterestRoute '/$id/set-interest': typeof IdSetInterestRoute
'/$id/set-profile': typeof IdSetProfileRoute '/$id/set-profile': typeof IdSetProfileRoute
'/columns': typeof ColumnsLayoutRouteWithChildren '/columns': typeof ColumnsLayoutRouteWithChildren
'/settings/general': typeof SettingsGeneralRoute
'/settings/relays': typeof SettingsRelaysRoute '/settings/relays': typeof SettingsRelaysRoute
'/settings/wallet': typeof SettingsWalletRoute '/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/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
@@ -849,13 +867,14 @@ export interface FileRoutesById {
'/$id/set-profile': typeof IdSetProfileRoute '/$id/set-profile': typeof IdSetProfileRoute
'/columns': typeof ColumnsRouteWithChildren '/columns': typeof ColumnsRouteWithChildren
'/columns/_layout': typeof ColumnsLayoutRouteWithChildren '/columns/_layout': typeof ColumnsLayoutRouteWithChildren
'/settings/general': typeof SettingsGeneralRoute
'/settings/relays': typeof SettingsRelaysRoute '/settings/relays': typeof SettingsRelaysRoute
'/settings/wallet': typeof SettingsWalletRoute '/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/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
@@ -891,13 +910,14 @@ export interface FileRouteTypes {
| '/$id/set-interest' | '/$id/set-interest'
| '/$id/set-profile' | '/$id/set-profile'
| '/columns' | '/columns'
| '/settings/general'
| '/settings/relays' | '/settings/relays'
| '/settings/wallet' | '/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/general'
| '/settings/sync'
| '/' | '/'
| '/new-post' | '/new-post'
| '/columns/create-newsfeed' | '/columns/create-newsfeed'
@@ -929,13 +949,14 @@ export interface FileRouteTypes {
| '/$id/set-interest' | '/$id/set-interest'
| '/$id/set-profile' | '/$id/set-profile'
| '/columns' | '/columns'
| '/settings/general'
| '/settings/relays' | '/settings/relays'
| '/settings/wallet' | '/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/general'
| '/settings/sync'
| '/' | '/'
| '/new-post' | '/new-post'
| '/columns/create-newsfeed' | '/columns/create-newsfeed'
@@ -969,13 +990,14 @@ export interface FileRouteTypes {
| '/$id/set-profile' | '/$id/set-profile'
| '/columns' | '/columns'
| '/columns/_layout' | '/columns/_layout'
| '/settings/general'
| '/settings/relays' | '/settings/relays'
| '/settings/wallet' | '/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/general'
| '/settings/sync'
| '/_app/' | '/_app/'
| '/new-post/' | '/new-post/'
| '/columns/_layout/create-newsfeed' | '/columns/_layout/create-newsfeed'
@@ -1068,9 +1090,10 @@ export const routeTree = rootRoute
"/settings": { "/settings": {
"filePath": "settings.lazy.tsx", "filePath": "settings.lazy.tsx",
"children": [ "children": [
"/settings/general",
"/settings/relays", "/settings/relays",
"/settings/wallet" "/settings/wallet",
"/settings/general",
"/settings/sync"
] ]
}, },
"/$id/set-group": { "/$id/set-group": {
@@ -1113,10 +1136,6 @@ export const routeTree = rootRoute
"/columns/_layout/users/$id" "/columns/_layout/users/$id"
] ]
}, },
"/settings/general": {
"filePath": "settings/general.tsx",
"parent": "/settings"
},
"/settings/relays": { "/settings/relays": {
"filePath": "settings/relays.tsx", "filePath": "settings/relays.tsx",
"parent": "/settings" "parent": "/settings"
@@ -1137,6 +1156,14 @@ export const routeTree = rootRoute
"/new-account/watch": { "/new-account/watch": {
"filePath": "new-account/watch.lazy.tsx" "filePath": "new-account/watch.lazy.tsx"
}, },
"/settings/general": {
"filePath": "settings/general.lazy.tsx",
"parent": "/settings"
},
"/settings/sync": {
"filePath": "settings/sync.lazy.tsx",
"parent": "/settings"
},
"/_app/": { "/_app/": {
"filePath": "_app/index.tsx", "filePath": "_app/index.tsx",
"parent": "/_app" "parent": "/_app"
+2 -2
View File
@@ -118,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" }),
@@ -183,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(() => {
+113 -91
View File
@@ -1,96 +1,118 @@
import { cn } from '@/commons' import { cn } from "@/commons";
import { CurrencyBtc, GearSix, HardDrives } from '@phosphor-icons/react' import {
import * as ScrollArea from '@radix-ui/react-scroll-area' CloudArrowDown,
import { Link } from '@tanstack/react-router' CurrencyBtc,
import { Outlet, createLazyFileRoute } from '@tanstack/react-router' GearSix,
HardDrives,
} from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { Link } from "@tanstack/react-router";
import { Outlet, createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute('/settings')({ export const Route = createLazyFileRoute("/settings")({
component: Screen, component: Screen,
}) });
function Screen() { function Screen() {
const { platform } = Route.useRouteContext() const { platform } = Route.useRouteContext();
return ( return (
<div className="flex size-full"> <div className="flex size-full">
<div <div
data-tauri-drag-region data-tauri-drag-region
className={cn( className={cn(
'w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2', "w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2",
platform === 'macos' ? 'pt-11' : '', platform === "macos" ? "pt-11" : "",
)} )}
> >
<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/general"> <Link to="/settings/general">
{({ isActive }) => { {({ isActive }) => {
return ( return (
<div <div
className={cn( className={cn(
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2', "h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive isActive
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20' ? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10', : "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)} )}
> >
<GearSix className="size-5 shrink-0" /> <GearSix className="size-5 shrink-0" />
<p className="text-sm font-medium">General</p> <p className="text-sm font-medium">General</p>
</div> </div>
) );
}} }}
</Link> </Link>
<Link to="/settings/relays"> <Link to="/settings/sync">
{({ isActive }) => { {({ isActive }) => {
return ( return (
<div <div
className={cn( className={cn(
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2', "h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive isActive
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20' ? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10', : "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)} )}
> >
<HardDrives className="size-5 shrink-0" /> <CloudArrowDown className="size-5 shrink-0" />
<p className="text-sm font-medium">Relays</p> <p className="text-sm font-medium">Sync</p>
</div> </div>
) );
}} }}
</Link> </Link>
<Link to="/settings/wallet"> <Link to="/settings/relays">
{({ isActive }) => { {({ isActive }) => {
return ( return (
<div <div
className={cn( className={cn(
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2', "h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive isActive
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20' ? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10', : "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)} )}
> >
<CurrencyBtc className="size-5 shrink-0" /> <HardDrives className="size-5 shrink-0" />
<p className="text-sm font-medium">Wallet</p> <p className="text-sm font-medium">Relays</p>
</div> </div>
) );
}} }}
</Link> </Link>
</div> <Link to="/settings/wallet">
<ScrollArea.Root {({ isActive }) => {
type={'scroll'} return (
scrollHideDelay={300} <div
className="flex-1 overflow-hidden size-full" className={cn(
> "h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
<ScrollArea.Viewport className="relative h-full pt-12"> isActive
<Outlet /> ? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
</ScrollArea.Viewport> : "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
<ScrollArea.Scrollbar )}
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2" >
orientation="vertical" <CurrencyBtc className="size-5 shrink-0" />
> <p className="text-sm font-medium">Wallet</p>
<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]" /> </div>
</ScrollArea.Scrollbar> );
<ScrollArea.Corner className="bg-transparent" /> }}
</ScrollArea.Root> </Link>
</div> </div>
) <ScrollArea.Root
type={"scroll"}
scrollHideDelay={300}
className="flex-1 overflow-hidden size-full"
>
<ScrollArea.Viewport className="relative h-full pt-12">
<Outlet />
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
orientation="vertical"
>
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
</ScrollArea.Scrollbar>
<ScrollArea.Corner className="bg-transparent" />
</ScrollArea.Root>
</div>
);
} }
+3 -9
View File
@@ -69,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"
@@ -92,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">
@@ -140,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"
-3
View File
@@ -1,3 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/settings/general")();
+14 -3
View File
@@ -53,9 +53,20 @@ function Screen() {
<div className="w-full px-3 pb-3"> <div className="w-full px-3 pb-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<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"> <div>
Connected Relays <h2 className="text-sm font-semibold">Connected Relays</h2>
</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 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 h-14">
<div className="flex items-center w-full gap-2 mb-0"> <div className="flex items-center w-full gap-2 mb-0">
+100
View File
@@ -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>
);
}
+14 -3
View File
@@ -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) =>