feat: add negentropy sync to settings

This commit is contained in:
reya 2024-11-11 10:02:19 +07:00
parent c93edde7d2
commit c5d06a2492
12 changed files with 388 additions and 142 deletions

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;

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(())
}

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;

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 **/

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"

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(() => {

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>
);
} }

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"

View File

@ -1,3 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/settings/general")();

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">

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>
);
}

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) =>