chore: add some improvements and remove linux support

This commit is contained in:
reya
2024-08-19 09:58:29 +07:00
parent beac1a189e
commit ce7828310b
17 changed files with 256 additions and 456 deletions

View File

@@ -1,72 +0,0 @@
name: Flatpak
on: workflow_dispatch
jobs:
prepare-repo:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: cache of container
id: cache-container
uses: actions/cache@v3
with:
path: prepare-dist
key: ${{ runner.os }}-container-${{ hashFiles('prepare-dist') }}
- name: Run latest-tag
id: latest-tag
uses: oprypin/find-latest-tag@v1
with:
repository:
lumehq/lume
#FIXME: lumehq after merged fix, now it just won't find tags
# repository: ${{ github.repository }}
- name: Build container
# if: steps.cache-container.outputs.cache-hit != 'true'
run: |
docker buildx build -t flatpak-prepare-lume --build-arg=${{steps.latest-tag.outputs.tag}} --rm --output=. --target=final -f flatpak/Containerfile .
- name: Copy flatpak files content
run: |
cp -r flatpak/*.xml flatpak/*.desktop flatpak/*.yml prepare-dist
- uses: actions/upload-artifact@v4
with:
name: repo-dist
path: prepare-dist
flatpak:
name: flatpak-bundle
needs: prepare-repo
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-45
options: --privileged
steps:
- uses: actions/download-artifact@v4
with:
name: repo-dist
- uses: actions/checkout@v4
with:
repository: flathub/shared-modules
path: shared-modules
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: lume.flatpak
manifest-path: nu.lume.Lume.yml
restore-cache: false
# cache-key: flatpak-builder-${{ github.sha }}
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
append_body: true
files: lume.flatpak
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: geekyeggo/delete-artifact@v4
with:
name: repo-dist
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,12 +16,12 @@ jobs:
args: "--target aarch64-apple-darwin" args: "--target aarch64-apple-darwin"
- platform: "macos-latest" # for Intel based macs. - platform: "macos-latest" # for Intel based macs.
args: "--target x86_64-apple-darwin" args: "--target x86_64-apple-darwin"
- platform: "macos-latest" # for Intel based macs. - platform: "macos-latest" # for Intel & Arm based macs.
args: "--target universal-apple-darwin" args: "--target universal-apple-darwin"
- platform: 'ubuntu-22.04' #- platform: 'ubuntu-22.04'
args: '' # args: ''
- platform: 'windows-latest' #- platform: 'windows-latest'
args: '--target x86_64-pc-windows-msvc' # args: '--target x86_64-pc-windows-msvc'
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

34
src-tauri/Cargo.lock generated
View File

@@ -1236,30 +1236,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "dbus"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b"
dependencies = [
"libc",
"libdbus-sys",
"winapi",
]
[[package]]
name = "dbus-secret-service"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1caa0c241c01ad8d99a78d553567d38f873dd3ac16eca33a5370d650ab25584e"
dependencies = [
"dbus",
"futures-util",
"num",
"once_cell",
"rand 0.8.5",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.3.11"
@@ -2762,7 +2738,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b9af47ded4df3067484d7d45758ca2b36bd083bf6d024c2952bbd8af1cdaa4" checksum = "73b9af47ded4df3067484d7d45758ca2b36bd083bf6d024c2952bbd8af1cdaa4"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"dbus-secret-service",
"security-framework", "security-framework",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -2837,15 +2812,6 @@ version = "0.2.157"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86"
[[package]]
name = "libdbus-sys"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72"
dependencies = [
"pkg-config",
]
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.7.4" version = "0.7.4"

View File

@@ -44,11 +44,7 @@ url = "2.5.0"
futures = "0.3.30" futures = "0.3.30"
linkify = "0.10.0" linkify = "0.10.0"
regex = "1.10.4" regex = "1.10.4"
keyring = { version = "3", features = [ keyring = { version = "3", features = ["apple-native", "windows-native"] }
"apple-native",
"windows-native",
"sync-secret-service",
] }
keyring-search = "1.2.0" keyring-search = "1.2.0"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]

View File

@@ -1,25 +1,15 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use border::WebviewWindowExt as BorderWebviewWindowExt; use border::WebviewWindowExt as BorderWebviewWindowExt;
#[cfg(target_os = "macos")]
use cocoa::{appkit::NSApp, base::nil, foundation::NSString};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
#[cfg(not(target_os = "linux"))]
use tauri::utils::config::WindowEffectsConfig; use tauri::utils::config::WindowEffectsConfig;
#[cfg(not(target_os = "linux"))]
use tauri::window::Effect; use tauri::window::Effect;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use tauri::TitleBarStyle; use tauri::TitleBarStyle;
use tauri::WebviewWindowBuilder; use tauri::WebviewWindowBuilder;
use tauri::{LogicalPosition, LogicalSize, Manager, State, WebviewUrl}; use tauri::{LogicalPosition, LogicalSize, Manager, WebviewUrl};
#[cfg(not(target_os = "linux"))]
use tauri_plugin_decorum::WebviewWindowExt;
use url::Url;
use crate::Nostr;
#[derive(Serialize, Deserialize, Type)] #[derive(Serialize, Deserialize, Type)]
pub struct Window { pub struct Window {
@@ -43,44 +33,28 @@ pub struct Column {
height: f32, height: f32,
} }
#[tauri::command] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
pub fn create_column( pub fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result<String, String> {
column: Column,
app_handle: tauri::AppHandle,
state: State<'_, Nostr>,
) -> Result<String, String> {
let settings = state.settings.lock().unwrap().clone();
match app_handle.get_window("main") { match app_handle.get_window("main") {
Some(main_window) => match app_handle.get_webview(&column.label) { Some(main_window) => match app_handle.get_webview(&column.label) {
Some(_) => Ok(column.label), Some(_) => Ok(column.label),
None => { None => {
let path = PathBuf::from(column.url); let path = PathBuf::from(column.url);
let webview_url = WebviewUrl::App(path); let webview_url = WebviewUrl::App(path);
let builder = match settings.proxy {
Some(url) => { let builder = tauri::webview::WebviewBuilder::new(column.label, webview_url)
let proxy = Url::from_str(&url).unwrap(); .incognito(true)
tauri::webview::WebviewBuilder::new(column.label, webview_url) .transparent(true);
.user_agent("Lume/4.0")
.zoom_hotkeys_enabled(true) if let Ok(webview) = main_window.add_child(
.enable_clipboard_access()
.transparent(true)
.proxy_url(proxy)
}
None => tauri::webview::WebviewBuilder::new(column.label, webview_url)
.user_agent("Lume/4.0")
.zoom_hotkeys_enabled(true)
.enable_clipboard_access()
.transparent(true),
};
match main_window.add_child(
builder, builder,
LogicalPosition::new(column.x, column.y), LogicalPosition::new(column.x, column.y),
LogicalSize::new(column.width, column.height), LogicalSize::new(column.width, column.height),
) { ) {
Ok(webview) => Ok(webview.label().into()), Ok(webview.label().into())
Err(_) => Err("Create webview failed".into()), } else {
Err("Create webview failed".into())
} }
} }
}, },
@@ -88,10 +62,10 @@ pub fn create_column(
} }
} }
#[tauri::command] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> Result<bool, String> { pub fn close_column(label: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
match app_handle.get_webview(label) { match app_handle.get_webview(&label) {
Some(webview) => { Some(webview) => {
if webview.close().is_ok() { if webview.close().is_ok() {
Ok(true) Ok(true)
@@ -103,15 +77,15 @@ pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> Result<bool, S
} }
} }
#[tauri::command] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
pub fn reposition_column( pub fn reposition_column(
label: &str, label: String,
x: f32, x: f32,
y: f32, y: f32,
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
) -> Result<(), String> { ) -> Result<(), String> {
match app_handle.get_webview(label) { match app_handle.get_webview(&label) {
Some(webview) => { Some(webview) => {
if webview.set_position(LogicalPosition::new(x, y)).is_ok() { if webview.set_position(LogicalPosition::new(x, y)).is_ok() {
Ok(()) Ok(())
@@ -123,15 +97,15 @@ pub fn reposition_column(
} }
} }
#[tauri::command] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
pub fn resize_column( pub fn resize_column(
label: &str, label: String,
width: f32, width: f32,
height: f32, height: f32,
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
) -> Result<(), String> { ) -> Result<(), String> {
match app_handle.get_webview(label) { match app_handle.get_webview(&label) {
Some(webview) => { Some(webview) => {
if webview.set_size(LogicalSize::new(width, height)).is_ok() { if webview.set_size(LogicalSize::new(width, height)).is_ok() {
Ok(()) Ok(())
@@ -143,10 +117,10 @@ pub fn resize_column(
} }
} }
#[tauri::command] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
pub fn reload_column(label: &str, app_handle: tauri::AppHandle) -> Result<(), String> { pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<(), String> {
match app_handle.get_webview(label) { match app_handle.get_webview(&label) {
Some(webview) => { Some(webview) => {
if webview.eval("window.location.reload()").is_ok() { if webview.eval("window.location.reload()").is_ok() {
Ok(()) Ok(())
@@ -203,6 +177,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.inner_size(window.width, window.height) .inner_size(window.width, window.height)
.minimizable(window.minimizable) .minimizable(window.minimizable)
.maximizable(window.maximizable) .maximizable(window.maximizable)
.transparent(true)
.effects(WindowEffectsConfig { .effects(WindowEffectsConfig {
state: None, state: None,
effects: vec![Effect::Mica], effects: vec![Effect::Mica],
@@ -212,22 +187,8 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.build() .build()
.unwrap(); .unwrap();
#[cfg(target_os = "linux")]
let window = WebviewWindowBuilder::new(
&app_handle,
&window.label,
WebviewUrl::App(PathBuf::from(window.url)),
)
.title(&window.title)
.min_inner_size(window.width, window.height)
.inner_size(window.width, window.height)
.minimizable(window.minimizable)
.maximizable(window.maximizable)
.build()
.unwrap();
// Set decoration // Set decoration
#[cfg(not(target_os = "linux"))] #[cfg(target_os = "windows")]
window.create_overlay_titlebar().unwrap(); window.create_overlay_titlebar().unwrap();
// Restore native border // Restore native border
@@ -266,18 +227,3 @@ pub fn open_main_window(app: tauri::AppHandle) {
pub fn force_quit() { pub fn force_quit() {
std::process::exit(0) std::process::exit(0)
} }
#[tauri::command]
#[specta::specta]
pub fn set_badge(count: i32) {
#[cfg(target_os = "macos")]
unsafe {
let label = if count == 0 {
nil
} else {
NSString::alloc(nil).init_str(&format!("{}", count))
};
let dock_tile: cocoa::base::id = msg_send![NSApp(), dockTile];
let _: cocoa::base::id = msg_send![dock_tile, setBadgeLabel: label];
}
}

View File

@@ -5,9 +5,6 @@
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
extern crate cocoa; extern crate cocoa;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use border::WebviewWindowExt as BorderWebviewWindowExt; use border::WebviewWindowExt as BorderWebviewWindowExt;
@@ -24,7 +21,6 @@ use std::{
time::Duration, time::Duration,
}; };
use tauri::{path::BaseDirectory, Manager}; use tauri::{path::BaseDirectory, Manager};
#[cfg(not(target_os = "linux"))]
use tauri_plugin_decorum::WebviewWindowExt; use tauri_plugin_decorum::WebviewWindowExt;
use tauri_specta::{collect_commands, Builder}; use tauri_specta::{collect_commands, Builder};
@@ -132,8 +128,7 @@ fn main() {
reload_column, reload_column,
open_window, open_window,
open_main_window, open_main_window,
force_quit, force_quit
set_badge
]); ]);
builder builder
@@ -156,7 +151,7 @@ fn main() {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
app.handle().plugin(tauri_nspanel::init()).unwrap(); app.handle().plugin(tauri_nspanel::init()).unwrap();
#[cfg(not(target_os = "linux"))] let handle = app.handle();
let main_window = app.get_webview_window("main").unwrap(); let main_window = app.get_webview_window("main").unwrap();
// Set custom decoration for Windows // Set custom decoration for Windows
@@ -188,9 +183,11 @@ fn main() {
let home_dir = app.path().home_dir().unwrap(); let home_dir = app.path().home_dir().unwrap();
let _ = fs::create_dir_all(home_dir.join("Lume/")); let _ = fs::create_dir_all(home_dir.join("Lume/"));
tauri::async_runtime::block_on(async move { let client = tauri::async_runtime::block_on(async move {
// Setup database // Setup database
let database = SQLiteDatabase::open(home_dir.join("Lume/lume.db")).await; let database = SQLiteDatabase::open(home_dir.join("Lume/lume.db"))
.await
.expect("Error.");
// Config // Config
let opts = Options::new() let opts = Options::new()
@@ -199,13 +196,13 @@ fn main() {
.timeout(Duration::from_secs(30)); .timeout(Duration::from_secs(30));
// Setup nostr client // Setup nostr client
let client = match database { let client = ClientBuilder::default()
Ok(db) => ClientBuilder::default().database(db).opts(opts).build(), .database(database)
Err(_) => ClientBuilder::default().opts(opts).build(), .opts(opts)
}; .build();
// Get bootstrap relays // Get bootstrap relays
if let Ok(path) = app if let Ok(path) = handle
.path() .path()
.resolve("resources/relays.txt", BaseDirectory::Resource) .resolve("resources/relays.txt", BaseDirectory::Resource)
{ {
@@ -240,12 +237,14 @@ fn main() {
// Connect // Connect
client.connect().await; client.connect().await;
// Update global state client
app.handle().manage(Nostr { });
client,
contact_list: Mutex::new(vec![]), // Create global state
settings: Mutex::new(Settings::default()), app.manage(Nostr {
}) client,
contact_list: Mutex::new(vec![]),
settings: Mutex::new(Settings::default()),
}); });
Ok(()) Ok(())

View File

@@ -51,37 +51,16 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"linux": { "windows": {
"appimage": { "allowDowngrades": true,
"bundleMediaFramework": true, "webviewInstallMode": {
"files": {} "silent": true,
}, "type": "downloadBootstrapper"
"deb": {
"files": {}
},
"rpm": {
"epoch": 0,
"files": {},
"release": "1"
} }
}, },
"macOS": { "macOS": {
"minimumSystemVersion": "10.15" "minimumSystemVersion": "10.15"
}, },
"windows": {
"allowDowngrades": true,
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"nsis": null,
"timestampUrl": null,
"tsp": false,
"webviewFixedRuntimePath": null,
"webviewInstallMode": {
"silent": true,
"type": "downloadBootstrapper"
},
"wix": null
},
"fileAssociations": [ "fileAssociations": [
{ {
"name": "bech32", "name": "bech32",

View File

@@ -1,15 +0,0 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"app": {
"windows": [
{
"title": "Lume",
"label": "main",
"width": 1045,
"height": 800,
"minWidth": 480,
"minHeight": 760
}
]
}
}

View File

@@ -1,30 +1,23 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"app": { "app": {
"trayIcon": { "windows": [
"id": "main", {
"iconPath": "./icons/tray.png", "title": "Lume",
"iconAsTemplate": true, "label": "main",
"menuOnLeftClick": false "titleBarStyle": "Overlay",
}, "width": 900,
"windows": [ "height": 700,
{ "minWidth": 480,
"title": "Lume", "minHeight": 700,
"label": "main", "hiddenTitle": true,
"titleBarStyle": "Overlay", "transparent": true,
"width": 1045, "windowEffects": {
"height": 800, "effects": [
"minWidth": 480, "underWindowBackground"
"minHeight": 760, ]
"hiddenTitle": true, }
"transparent": true, }
"windowEffects": { ]
"state": "followsWindowActiveState", }
"effects": [
"underWindowBackground"
]
}
}
]
}
} }

View File

@@ -1,21 +1,22 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"app": { "app": {
"windows": [ "windows": [
{ {
"title": "Lume", "title": "Lume",
"label": "main", "label": "main",
"width": 1045, "width": 900,
"height": 800, "height": 700,
"minWidth": 480, "minWidth": 480,
"minHeight": 760, "minHeight": 700,
"transparent": true, "transparent": true,
"windowEffects": { "decorations": false,
"effects": [ "windowEffects": {
"mica" "effects": [
] "mica"
} ]
} }
] }
} ]
}
} }

View File

@@ -429,9 +429,6 @@ async openMainWindow() : Promise<void> {
}, },
async forceQuit() : Promise<void> { async forceQuit() : Promise<void> {
await TAURI_INVOKE("force_quit"); await TAURI_INVOKE("force_quit");
},
async setBadge(count: number) : Promise<void> {
await TAURI_INVOKE("set_badge", { count });
} }
} }

View File

@@ -1,5 +1,5 @@
import type { LumeColumn } from "@/types";
import { CheckIcon, HorizontalDotsIcon } from "@/components"; import { CheckIcon, HorizontalDotsIcon } from "@/components";
import type { LumeColumn } from "@/types";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
@@ -84,7 +84,7 @@ export const Column = memo(function Column({
}, [account]); }, [account]);
return ( return (
<div className="h-full w-[480px] shrink-0 p-2"> <div className="h-full w-[440px] shrink-0 p-2">
<div className="flex flex-col w-full h-full rounded-xl bg-black/5 dark:bg-white/10"> <div className="flex flex-col w-full h-full rounded-xl bg-black/5 dark:bg-white/10">
<Header <Header
label={column.label} label={column.label}

View File

@@ -125,7 +125,9 @@ const CreateGroupRoute = CreateGroupImport.update({
const BootstrapRelaysRoute = BootstrapRelaysImport.update({ const BootstrapRelaysRoute = BootstrapRelaysImport.update({
path: '/bootstrap-relays', path: '/bootstrap-relays',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any).lazy(() =>
import('./routes/bootstrap-relays.lazy').then((d) => d.Route),
)
const AccountRoute = AccountImport.update({ const AccountRoute = AccountImport.update({
path: '/$account', path: '/$account',

View File

@@ -170,7 +170,7 @@ function Screen() {
account={account} account={account}
/> />
))} ))}
<div className="shrink-0 p-2 h-full w-[480px]"> <div className="shrink-0 p-2 h-full w-[450px]">
<div className="size-full bg-black/5 dark:bg-white/5 rounded-xl flex items-center justify-center"> <div className="size-full bg-black/5 dark:bg-white/5 rounded-xl flex items-center justify-center">
<button <button
type="button" type="button"

View File

@@ -0,0 +1,144 @@
import { commands } from "@/commands.gen";
import { Frame } from "@/components/frame";
import { Spinner } from "@/components/spinner";
import { Plus, X } from "@phosphor-icons/react";
import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
import { useEffect, useState, useTransition } from "react";
export const Route = createLazyFileRoute("/bootstrap-relays")({
component: Screen,
});
function Screen() {
const bootstrapRelays = Route.useLoaderData();
const [relays, setRelays] = useState<string[]>([]);
const [newRelay, setNewRelay] = useState("");
const [isPending, startTransition] = useTransition();
const add = () => {
try {
let url = newRelay;
if (!url.startsWith("wss://")) {
url = `wss://${url}`;
}
// Validate URL
const relay = new URL(url);
// Update
setRelays((prev) => [...prev, relay.toString()]);
setNewRelay("");
} catch {
message("URL is not valid.", { kind: "error" });
}
};
const remove = (relay: string) => {
setRelays((prev) => prev.filter((item) => item !== relay));
};
const submit = () => {
startTransition(async () => {
if (!relays.length) {
await message("You need to add at least 1 relay", {
title: "Manage Relays",
kind: "info",
});
return;
}
const merged = relays.join("\r\n");
const res = await commands.saveBootstrapRelays(merged);
if (res.status === "ok") {
return await relaunch();
} else {
await message(res.error, {
title: "Manage Relays",
kind: "error",
});
return;
}
});
};
useEffect(() => {
setRelays(bootstrapRelays);
}, [bootstrapRelays]);
return (
<div className="size-full flex items-center justify-center">
<div className="w-[320px] flex flex-col gap-8">
<div className="flex flex-col gap-1 text-center">
<h1 className="leading-tight text-xl font-semibold">Manage Relays</h1>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
This relays will be only use to get user's metadata.
</p>
</div>
<div className="flex flex-col gap-3">
<Frame
className="flex flex-col gap-3 p-3 rounded-xl overflow-hidden"
shadow
>
<div className="flex gap-2">
<input
name="relay"
type="text"
placeholder="ex: relay.nostr.net, ..."
value={newRelay}
onChange={(e) => setNewRelay(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") add();
}}
className="flex-1 px-3 rounded-lg h-9 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600"
/>
<button
type="submit"
onClick={() => add()}
className="inline-flex items-center justify-center size-9 rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
<Plus className="size-5" />
</button>
</div>
<div className="flex flex-col gap-2">
{relays.map((relay) => (
<div
key={relay}
className="flex items-center justify-between h-9 px-2 rounded-lg bg-neutral-100 dark:bg-neutral-900"
>
<div className="text-sm font-medium">{relay}</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => remove(relay)}
className="inline-flex items-center justify-center rounded-md size-7 text-neutral-700 dark:text-white/20 hover:bg-black/10 dark:hover:bg-white/10"
>
<X className="size-3" />
</button>
</div>
</div>
))}
</div>
</Frame>
<div className="flex flex-col items-center gap-1">
<button
type="button"
onClick={() => submit()}
disabled={isPending || !relays.length}
className="inline-flex items-center justify-center w-full h-9 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
>
{isPending ? <Spinner /> : "Save & Restart"}
</button>
<span className="mt-2 w-full text-sm text-neutral-600 dark:text-neutral-400 inline-flex items-center justify-center">
Lume will relaunch after saving.
</span>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,150 +1,14 @@
import { Spinner } from "@/components"; import { commands } from "@/commands.gen";
import { CancelIcon, PlusIcon } from "@/components";
import { NostrQuery } from "@/system";
import type { Relay } from "@/types";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
export const Route = createFileRoute("/bootstrap-relays")({ export const Route = createFileRoute("/bootstrap-relays")({
loader: async () => { loader: async () => {
const bootstrapRelays = await NostrQuery.getBootstrapRelays(); const res = await commands.getBootstrapRelays();
return bootstrapRelays;
if (res.status === "ok") {
return res.data.map((item) => item.replace(",", ""));
} else {
throw new Error(res.error);
}
}, },
component: Screen,
}); });
function Screen() {
const bootstrapRelays = Route.useLoaderData();
const { register, reset, handleSubmit } = useForm();
const [relays, setRelays] = useState<Relay[]>([]);
const [isLoading, setIsLoading] = useState(false);
const removeRelay = (url: string) => {
setRelays((prev) => prev.filter((relay) => relay.url !== url));
};
const onSubmit = async (data: { url: string; purpose: string }) => {
try {
if (!data.url.startsWith("wss://") || !data.url.startsWith("ws://")) {
return await message("Relay must be starts with wss:// or ws://", {
title: "Bootstrap Relays",
kind: "info",
});
}
const relay: Relay = { url: data.url, purpose: data.purpose };
setRelays((prev) => [...prev, relay]);
reset();
} catch (e) {
await message(String(e), { title: "Bootstrap Relays", kind: "error" });
}
};
const save = async () => {
try {
setIsLoading(true);
await NostrQuery.saveBootstrapRelays(relays);
} catch (e) {
await message(String(e), { title: "Bootstrap Relays", kind: "error" });
}
};
useEffect(() => {
setRelays(bootstrapRelays);
}, [bootstrapRelays]);
return (
<div
data-tauri-drag-region
className="relative flex flex-col items-center justify-between w-full h-full"
>
<div
data-tauri-drag-region
className="absolute top-0 left-0 h-14 w-full"
/>
<div className="flex items-end justify-center flex-1 w-full px-4 pb-4">
<div className="text-center">
<h2 className="text-xl font-semibold">Customize Bootstrap Relays</h2>
</div>
</div>
<div className="flex flex-col items-center flex-1 w-full">
<div className="flex flex-col w-full max-w-sm mx-auto p-3 overflow-hidden bg-white divide-y divide-neutral-100 dark:divide-white/5 rounded-xl shadow-primary dark:bg-white/10 dark:ring-1 ring-white/15">
{relays.map((relay) => (
<div
key={relay.url}
className="flex items-center justify-between h-11"
>
<div className="inline-flex items-center gap-2 text-sm font-medium">
{relay.url}
</div>
<div className="flex items-center gap-2">
{relay.purpose?.length ? (
<button
type="button"
className="inline-flex items-center justify-center px-2 text-xs font-medium uppercase rounded-md h-7 w-max hover:bg-black/10 dark:hover:bg-white/10"
>
{relay.purpose}
</button>
) : null}
<button
type="button"
onClick={() => removeRelay(relay.url)}
className="inline-flex items-center justify-center rounded-md size-7 text-neutral-700 dark:text-white/20 hover:bg-black/10 dark:hover:bg-white/10"
>
<CancelIcon className="size-3" />
</button>
</div>
</div>
))}
<div className="flex items-center border-t h-14 border-neutral-100 dark:border-white/5">
<form
onSubmit={handleSubmit(onSubmit)}
className="flex items-center w-full gap-2 mb-0"
>
<div className="flex items-center flex-1 gap-2 border rounded-lg border-neutral-300 dark:border-white/20">
<input
{...register("url", {
required: true,
minLength: 1,
})}
name="url"
placeholder="wss://..."
spellCheck={false}
className="flex-1 px-3 bg-transparent border-none rounded-l-lg h-9 placeholder:text-neutral-500 dark:placeholder:text-neutral-400"
/>
<select
{...register("purpose")}
className="flex-1 p-0 m-0 text-sm bg-transparent border-none outline-none h-9 ring-0 focus:outline-none focus:ring-0"
>
<option value="read">Read</option>
<option value="write">Write</option>
<option value="">Both</option>
</select>
</div>
<button
type="submit"
className="inline-flex items-center justify-center px-2 text-sm font-medium text-white rounded-lg shrink-0 h-9 w-14 bg-black/20 dark:bg-white/20 hover:bg-blue-500 disabled:opacity-50"
>
<PlusIcon className="size-7" />
</button>
</form>
</div>
</div>
<div className="w-full max-w-sm mx-auto">
<button
type="button"
onClick={() => save()}
disabled={isLoading}
className="inline-flex items-center justify-center w-full h-9 mt-4 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
>
{isLoading ? <Spinner /> : "Save & Relaunch"}
</button>
</div>
</div>
<div className="flex-1" />
</div>
);
}

View File

@@ -381,7 +381,7 @@ export const NostrQuery = {
return []; return [];
} }
}, },
saveBootstrapRelays: async (relays: Relay[]) => { saveBootstrapRelays: async (relays: string[]) => {
const text = relays const text = relays
.map((relay) => Object.values(relay).join(",")) .map((relay) => Object.values(relay).join(","))
.join("\n"); .join("\n");