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 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/.
### Lume will be rewritten in Rust
+83 -83
View File
@@ -1,85 +1,85 @@
{
"name": "lume",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@getalby/bitcoin-connect-react": "^3.6.2",
"@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.3",
"@tanstack/query-broadcast-client-experimental": "^5.59.20",
"@tanstack/query-persist-client-core": "^5.59.20",
"@tanstack/react-query": "^5.59.20",
"@tanstack/react-router": "^1.81.0",
"@tauri-apps/api": "^2.1.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.1",
"@tauri-apps/plugin-fs": "^2.0.2",
"@tauri-apps/plugin-http": "^2.0.1",
"@tauri-apps/plugin-os": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.1",
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
"@tauri-apps/plugin-updater": "^2.0.0",
"@tauri-apps/plugin-upload": "^2.1.0",
"@tauri-apps/plugin-window-state": "^2.0.0",
"bitcoin-units": "^1.0.0",
"dayjs": "^1.11.13",
"embla-carousel-react": "^8.3.1",
"i18next": "^23.16.5",
"i18next-resources-to-backend": "^1.2.1",
"light-bolt11-decoder": "^3.2.0",
"minidenticons": "^4.2.1",
"nanoid": "^5.0.8",
"nostr-tools": "^2.10.1",
"react": "19.0.0-rc-cae764ce-20241025",
"react-currency-input-field": "^3.8.0",
"react-dom": "19.0.0-rc-cae764ce-20241025",
"react-hook-form": "^7.53.2",
"react-i18next": "^15.1.1",
"react-string-replace": "^1.1.1",
"rich-textarea": "^0.26.4",
"use-debounce": "^10.0.4",
"virtua": "^0.34.2"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/router-devtools": "^1.81.0",
"@tanstack/router-plugin": "^1.79.0",
"@tauri-apps/cli": "^2.1.0",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "0.0.0-experimental-b4db8c3-20241001",
"clsx": "^2.1.1",
"postcss": "^8.4.47",
"tailwind-gradient-mask-image": "^1.2.0",
"tailwind-merge": "^2.5.4",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.14",
"tailwindcss-content-visibility": "^1.0.0",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vite-tsconfig-paths": "^5.1.1"
},
"overrides": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
}
"name": "lume",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@getalby/bitcoin-connect-react": "^3.6.3",
"@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/query-broadcast-client-experimental": "^5.62.16",
"@tanstack/query-persist-client-core": "^5.62.16",
"@tanstack/react-query": "^5.63.0",
"@tanstack/react-router": "^1.95.3",
"@tauri-apps/api": "^2.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.0",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-http": "^2.2.0",
"@tauri-apps/plugin-os": "^2.2.0",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "^2.2.0",
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
"@tauri-apps/plugin-updater": "^2.3.1",
"@tauri-apps/plugin-upload": "^2.2.1",
"@tauri-apps/plugin-window-state": "^2.2.0",
"bitcoin-units": "^1.0.0",
"dayjs": "^1.11.13",
"embla-carousel-react": "^8.5.2",
"i18next": "^23.16.8",
"i18next-resources-to-backend": "^1.2.1",
"light-bolt11-decoder": "^3.2.0",
"minidenticons": "^4.2.1",
"nanoid": "^5.0.9",
"nostr-tools": "^2.10.4",
"react": "19.0.0-rc-cae764ce-20241025",
"react-currency-input-field": "^3.9.0",
"react-dom": "19.0.0-rc-cae764ce-20241025",
"react-hook-form": "^7.54.2",
"react-i18next": "^15.4.0",
"react-string-replace": "^1.1.1",
"rich-textarea": "^0.26.4",
"use-debounce": "^10.0.4",
"virtua": "^0.34.2"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/router-devtools": "^1.95.3",
"@tanstack/router-plugin": "^1.95.3",
"@tauri-apps/cli": "^2.2.3",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "0.0.0-experimental-b4db8c3-20241001",
"clsx": "^2.1.1",
"postcss": "^8.4.49",
"tailwind-gradient-mask-image": "^1.2.0",
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.17",
"tailwindcss-content-visibility": "^1.0.2",
"typescript": "^5.7.3",
"vite": "^5.4.11",
"vite-tsconfig-paths": "5.0.0"
},
"overrides": {
"@types/react": "npm:types-react@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-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]
border = { 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 metadata;
pub mod relay;
pub mod sync;
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 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 {
event_id: event.event_id,
relay_url,
+8 -5
View File
@@ -5,7 +5,7 @@
#[cfg(target_os = "macos")]
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 nostr_sdk::prelude::{Profile as DatabaseProfile, *};
use serde::{Deserialize, Serialize};
@@ -76,6 +76,7 @@ fn main() {
tracing_subscriber::fmt::init();
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
sync_all,
get_all_relays,
get_all_relay_lists,
is_relay_connected,
@@ -365,6 +366,8 @@ fn main() {
// 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;
@@ -475,7 +478,7 @@ fn main() {
Kind::Repost,
Kind::Reaction,
Kind::ZapReceipt,
Kind::Custom(1111),
Kind::Comment,
])
.since(Timestamp::now());
@@ -603,7 +606,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
if let Err(e) = handle
.notification()
.builder()
.body("Mentioned you in a thread.")
.body("You're mentioned in a thread.")
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
@@ -614,7 +617,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
if let Err(e) = handle
.notification()
.builder()
.body("Reposted your note.")
.body("Your note has been reposted.")
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
@@ -625,7 +628,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
if let Err(e) = handle
.notification()
.builder()
.body("Zapped you.")
.body("You've received zap.")
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Lume",
"version": "24.11.7",
"version": "24.11.8",
"identifier": "nu.lume.Lume",
"build": {
"beforeDevCommand": "pnpm dev",
+9
View File
@@ -5,6 +5,14 @@
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>> {
try {
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 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 TAURI_CHANNEL<TSend> = null
/** 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 SettingsWalletImport } from './routes/settings/wallet'
import { Route as SettingsRelaysImport } from './routes/settings/relays'
import { Route as SettingsGeneralImport } from './routes/settings/general'
import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
import { Route as IdSetProfileImport } from './routes/$id.set-profile'
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 SettingsLazyImport = createFileRoute('/settings')()
const NewLazyImport = createFileRoute('/new')()
const SettingsSyncLazyImport = createFileRoute('/settings/sync')()
const SettingsGeneralLazyImport = createFileRoute('/settings/general')()
const NewAccountWatchLazyImport = createFileRoute('/new-account/watch')()
const NewAccountImportLazyImport = createFileRoute('/new-account/import')()
const NewAccountConnectLazyImport = createFileRoute('/new-account/connect')()
@@ -118,6 +119,20 @@ const AppIndexRoute = AppIndexImport.update({
getParentRoute: () => AppRoute,
} 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({
id: '/new-account/watch',
path: '/new-account/watch',
@@ -164,14 +179,6 @@ const SettingsRelaysRoute = SettingsRelaysImport.update({
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({
id: '/_layout',
getParentRoute: () => ColumnsRoute,
@@ -440,13 +447,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ColumnsLayoutImport
parentRoute: typeof ColumnsRoute
}
'/settings/general': {
id: '/settings/general'
path: '/general'
fullPath: '/settings/general'
preLoaderRoute: typeof SettingsGeneralImport
parentRoute: typeof SettingsLazyImport
}
'/settings/relays': {
id: '/settings/relays'
path: '/relays'
@@ -489,6 +489,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof NewAccountWatchLazyImport
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/': {
id: '/_app/'
path: '/'
@@ -666,15 +680,17 @@ const AppRouteChildren: AppRouteChildren = {
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
interface SettingsLazyRouteChildren {
SettingsGeneralRoute: typeof SettingsGeneralRoute
SettingsRelaysRoute: typeof SettingsRelaysRoute
SettingsWalletRoute: typeof SettingsWalletRoute
SettingsGeneralLazyRoute: typeof SettingsGeneralLazyRoute
SettingsSyncLazyRoute: typeof SettingsSyncLazyRoute
}
const SettingsLazyRouteChildren: SettingsLazyRouteChildren = {
SettingsGeneralRoute: SettingsGeneralRoute,
SettingsRelaysRoute: SettingsRelaysRoute,
SettingsWalletRoute: SettingsWalletRoute,
SettingsGeneralLazyRoute: SettingsGeneralLazyRoute,
SettingsSyncLazyRoute: SettingsSyncLazyRoute,
}
const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren(
@@ -768,13 +784,14 @@ export interface FileRoutesByFullPath {
'/$id/set-interest': typeof IdSetInterestRoute
'/$id/set-profile': typeof IdSetProfileRoute
'/columns': typeof ColumnsLayoutRouteWithChildren
'/settings/general': typeof SettingsGeneralRoute
'/settings/relays': typeof SettingsRelaysRoute
'/settings/wallet': typeof SettingsWalletRoute
'/zap/$id': typeof ZapIdRoute
'/new-account/connect': typeof NewAccountConnectLazyRoute
'/new-account/import': typeof NewAccountImportLazyRoute
'/new-account/watch': typeof NewAccountWatchLazyRoute
'/settings/general': typeof SettingsGeneralLazyRoute
'/settings/sync': typeof SettingsSyncLazyRoute
'/': typeof AppIndexRoute
'/new-post': typeof NewPostIndexRoute
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
@@ -807,13 +824,14 @@ export interface FileRoutesByTo {
'/$id/set-interest': typeof IdSetInterestRoute
'/$id/set-profile': typeof IdSetProfileRoute
'/columns': typeof ColumnsLayoutRouteWithChildren
'/settings/general': typeof SettingsGeneralRoute
'/settings/relays': typeof SettingsRelaysRoute
'/settings/wallet': typeof SettingsWalletRoute
'/zap/$id': typeof ZapIdRoute
'/new-account/connect': typeof NewAccountConnectLazyRoute
'/new-account/import': typeof NewAccountImportLazyRoute
'/new-account/watch': typeof NewAccountWatchLazyRoute
'/settings/general': typeof SettingsGeneralLazyRoute
'/settings/sync': typeof SettingsSyncLazyRoute
'/': typeof AppIndexRoute
'/new-post': typeof NewPostIndexRoute
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
@@ -849,13 +867,14 @@ export interface FileRoutesById {
'/$id/set-profile': typeof IdSetProfileRoute
'/columns': typeof ColumnsRouteWithChildren
'/columns/_layout': typeof ColumnsLayoutRouteWithChildren
'/settings/general': typeof SettingsGeneralRoute
'/settings/relays': typeof SettingsRelaysRoute
'/settings/wallet': typeof SettingsWalletRoute
'/zap/$id': typeof ZapIdRoute
'/new-account/connect': typeof NewAccountConnectLazyRoute
'/new-account/import': typeof NewAccountImportLazyRoute
'/new-account/watch': typeof NewAccountWatchLazyRoute
'/settings/general': typeof SettingsGeneralLazyRoute
'/settings/sync': typeof SettingsSyncLazyRoute
'/_app/': typeof AppIndexRoute
'/new-post/': typeof NewPostIndexRoute
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
@@ -891,13 +910,14 @@ export interface FileRouteTypes {
| '/$id/set-interest'
| '/$id/set-profile'
| '/columns'
| '/settings/general'
| '/settings/relays'
| '/settings/wallet'
| '/zap/$id'
| '/new-account/connect'
| '/new-account/import'
| '/new-account/watch'
| '/settings/general'
| '/settings/sync'
| '/'
| '/new-post'
| '/columns/create-newsfeed'
@@ -929,13 +949,14 @@ export interface FileRouteTypes {
| '/$id/set-interest'
| '/$id/set-profile'
| '/columns'
| '/settings/general'
| '/settings/relays'
| '/settings/wallet'
| '/zap/$id'
| '/new-account/connect'
| '/new-account/import'
| '/new-account/watch'
| '/settings/general'
| '/settings/sync'
| '/'
| '/new-post'
| '/columns/create-newsfeed'
@@ -969,13 +990,14 @@ export interface FileRouteTypes {
| '/$id/set-profile'
| '/columns'
| '/columns/_layout'
| '/settings/general'
| '/settings/relays'
| '/settings/wallet'
| '/zap/$id'
| '/new-account/connect'
| '/new-account/import'
| '/new-account/watch'
| '/settings/general'
| '/settings/sync'
| '/_app/'
| '/new-post/'
| '/columns/_layout/create-newsfeed'
@@ -1068,9 +1090,10 @@ export const routeTree = rootRoute
"/settings": {
"filePath": "settings.lazy.tsx",
"children": [
"/settings/general",
"/settings/relays",
"/settings/wallet"
"/settings/wallet",
"/settings/general",
"/settings/sync"
]
},
"/$id/set-group": {
@@ -1113,10 +1136,6 @@ export const routeTree = rootRoute
"/columns/_layout/users/$id"
]
},
"/settings/general": {
"filePath": "settings/general.tsx",
"parent": "/settings"
},
"/settings/relays": {
"filePath": "settings/relays.tsx",
"parent": "/settings"
@@ -1137,6 +1156,14 @@ export const routeTree = rootRoute
"/new-account/watch": {
"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/": {
"filePath": "_app/index.tsx",
"parent": "/_app"
+2 -2
View File
@@ -118,7 +118,7 @@ function Account({ pubkey }: { pubkey: string }) {
const items = await Promise.all([
MenuItem.new({
text: "Unlock",
enabled: !isActive || true,
enabled: !isActive,
action: async () => await commands.setSigner(pubkey),
}),
PredefinedMenuItem.new({ item: "Separator" }),
@@ -183,7 +183,7 @@ function Account({ pubkey }: { pubkey: string }) {
await menu.popup().catch((e) => console.error(e));
},
[pubkey],
[isActive, pubkey],
);
useEffect(() => {
+113 -91
View File
@@ -1,96 +1,118 @@
import { cn } from '@/commons'
import { CurrencyBtc, GearSix, HardDrives } from '@phosphor-icons/react'
import * as ScrollArea from '@radix-ui/react-scroll-area'
import { Link } from '@tanstack/react-router'
import { Outlet, createLazyFileRoute } from '@tanstack/react-router'
import { cn } from "@/commons";
import {
CloudArrowDown,
CurrencyBtc,
GearSix,
HardDrives,
} from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { Link } from "@tanstack/react-router";
import { Outlet, createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute('/settings')({
component: Screen,
})
export const Route = createLazyFileRoute("/settings")({
component: Screen,
});
function Screen() {
const { platform } = Route.useRouteContext()
const { platform } = Route.useRouteContext();
return (
<div className="flex size-full">
<div
data-tauri-drag-region
className={cn(
'w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2',
platform === 'macos' ? 'pt-11' : '',
)}
>
<div className="h-8 px-1.5">
<h1 className="text-lg font-semibold">Settings</h1>
</div>
<Link to="/settings/general">
{({ isActive }) => {
return (
<div
className={cn(
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
isActive
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
)}
>
<GearSix className="size-5 shrink-0" />
<p className="text-sm font-medium">General</p>
</div>
)
}}
</Link>
<Link to="/settings/relays">
{({ isActive }) => {
return (
<div
className={cn(
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
isActive
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
)}
>
<HardDrives className="size-5 shrink-0" />
<p className="text-sm font-medium">Relays</p>
</div>
)
}}
</Link>
<Link to="/settings/wallet">
{({ isActive }) => {
return (
<div
className={cn(
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
isActive
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
)}
>
<CurrencyBtc className="size-5 shrink-0" />
<p className="text-sm font-medium">Wallet</p>
</div>
)
}}
</Link>
</div>
<ScrollArea.Root
type={'scroll'}
scrollHideDelay={300}
className="flex-1 overflow-hidden size-full"
>
<ScrollArea.Viewport className="relative h-full pt-12">
<Outlet />
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
orientation="vertical"
>
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
</ScrollArea.Scrollbar>
<ScrollArea.Corner className="bg-transparent" />
</ScrollArea.Root>
</div>
)
return (
<div className="flex size-full">
<div
data-tauri-drag-region
className={cn(
"w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2",
platform === "macos" ? "pt-11" : "",
)}
>
<div className="h-8 px-1.5">
<h1 className="text-lg font-semibold">Settings</h1>
</div>
<Link to="/settings/general">
{({ isActive }) => {
return (
<div
className={cn(
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)}
>
<GearSix className="size-5 shrink-0" />
<p className="text-sm font-medium">General</p>
</div>
);
}}
</Link>
<Link to="/settings/sync">
{({ isActive }) => {
return (
<div
className={cn(
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)}
>
<CloudArrowDown className="size-5 shrink-0" />
<p className="text-sm font-medium">Sync</p>
</div>
);
}}
</Link>
<Link to="/settings/relays">
{({ isActive }) => {
return (
<div
className={cn(
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)}
>
<HardDrives className="size-5 shrink-0" />
<p className="text-sm font-medium">Relays</p>
</div>
);
}}
</Link>
<Link to="/settings/wallet">
{({ isActive }) => {
return (
<div
className={cn(
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
isActive
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
)}
>
<CurrencyBtc className="size-5 shrink-0" />
<p className="text-sm font-medium">Wallet</p>
</div>
);
}}
</Link>
</div>
<ScrollArea.Root
type={"scroll"}
scrollHideDelay={300}
className="flex-1 overflow-hidden size-full"
>
<ScrollArea.Viewport className="relative h-full pt-12">
<Outlet />
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
orientation="vertical"
>
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
</ScrollArea.Scrollbar>
<ScrollArea.Corner className="bg-transparent" />
</ScrollArea.Root>
</div>
);
}
+3 -9
View File
@@ -69,9 +69,7 @@ function Screen() {
<div className="relative w-full">
<div className="flex flex-col gap-6 px-3 pb-3">
<div className="flex flex-col gap-2">
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
General
</h2>
<h2 className="text-sm font-semibold">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">
<Setting
name="Content Warning"
@@ -92,9 +90,7 @@ function Screen() {
</div>
</div>
<div className="flex flex-col gap-2">
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
Appearance
</h2>
<h2 className="text-sm font-semibold">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 items-start justify-between w-full gap-4 py-3">
<div className="flex-1">
@@ -140,9 +136,7 @@ function Screen() {
</div>
</div>
<div className="flex flex-col gap-2">
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
Privacy & Performance
</h2>
<h2 className="text-sm font-semibold">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">
<Setting
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="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>
<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">
+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 (
<div className="w-full px-3 pb-3">
<div className="flex flex-col w-full gap-2">
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
Wallet
</h2>
<div>
<h2 className="text-sm font-semibold">Bitcoin Wallet</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">
<Button
onConnected={(provider) =>