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"
- platform: "macos-latest" # for Intel based macs.
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"
- platform: 'ubuntu-22.04'
args: ''
- platform: 'windows-latest'
args: '--target x86_64-pc-windows-msvc'
#- platform: 'ubuntu-22.04'
# args: ''
#- platform: 'windows-latest'
# args: '--target x86_64-pc-windows-msvc'
runs-on: ${{ matrix.platform }}
steps:
- 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"
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]]
name = "deranged"
version = "0.3.11"
@@ -2762,7 +2738,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b9af47ded4df3067484d7d45758ca2b36bd083bf6d024c2952bbd8af1cdaa4"
dependencies = [
"byteorder",
"dbus-secret-service",
"security-framework",
"windows-sys 0.59.0",
]
@@ -2837,15 +2812,6 @@ version = "0.2.157"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "libloading"
version = "0.7.4"

View File

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

View File

@@ -1,25 +1,15 @@
use std::path::PathBuf;
use std::str::FromStr;
#[cfg(target_os = "macos")]
use border::WebviewWindowExt as BorderWebviewWindowExt;
#[cfg(target_os = "macos")]
use cocoa::{appkit::NSApp, base::nil, foundation::NSString};
use serde::{Deserialize, Serialize};
use specta::Type;
#[cfg(not(target_os = "linux"))]
use tauri::utils::config::WindowEffectsConfig;
#[cfg(not(target_os = "linux"))]
use tauri::window::Effect;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
use tauri::WebviewWindowBuilder;
use tauri::{LogicalPosition, LogicalSize, Manager, State, WebviewUrl};
#[cfg(not(target_os = "linux"))]
use tauri_plugin_decorum::WebviewWindowExt;
use url::Url;
use crate::Nostr;
use tauri::{LogicalPosition, LogicalSize, Manager, WebviewUrl};
#[derive(Serialize, Deserialize, Type)]
pub struct Window {
@@ -43,44 +33,28 @@ pub struct Column {
height: f32,
}
#[tauri::command]
#[tauri::command(async)]
#[specta::specta]
pub fn create_column(
column: Column,
app_handle: tauri::AppHandle,
state: State<'_, Nostr>,
) -> Result<String, String> {
let settings = state.settings.lock().unwrap().clone();
pub fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result<String, String> {
match app_handle.get_window("main") {
Some(main_window) => match app_handle.get_webview(&column.label) {
Some(_) => Ok(column.label),
None => {
let path = PathBuf::from(column.url);
let webview_url = WebviewUrl::App(path);
let builder = match settings.proxy {
Some(url) => {
let proxy = Url::from_str(&url).unwrap();
tauri::webview::WebviewBuilder::new(column.label, webview_url)
.user_agent("Lume/4.0")
.zoom_hotkeys_enabled(true)
.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(
let builder = tauri::webview::WebviewBuilder::new(column.label, webview_url)
.incognito(true)
.transparent(true);
if let Ok(webview) = main_window.add_child(
builder,
LogicalPosition::new(column.x, column.y),
LogicalSize::new(column.width, column.height),
) {
Ok(webview) => Ok(webview.label().into()),
Err(_) => Err("Create webview failed".into()),
Ok(webview.label().into())
} else {
Err("Create webview failed".into())
}
}
},
@@ -88,10 +62,10 @@ pub fn create_column(
}
}
#[tauri::command]
#[tauri::command(async)]
#[specta::specta]
pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> Result<bool, String> {
match app_handle.get_webview(label) {
pub fn close_column(label: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
match app_handle.get_webview(&label) {
Some(webview) => {
if webview.close().is_ok() {
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]
pub fn reposition_column(
label: &str,
label: String,
x: f32,
y: f32,
app_handle: tauri::AppHandle,
) -> Result<(), String> {
match app_handle.get_webview(label) {
match app_handle.get_webview(&label) {
Some(webview) => {
if webview.set_position(LogicalPosition::new(x, y)).is_ok() {
Ok(())
@@ -123,15 +97,15 @@ pub fn reposition_column(
}
}
#[tauri::command]
#[tauri::command(async)]
#[specta::specta]
pub fn resize_column(
label: &str,
label: String,
width: f32,
height: f32,
app_handle: tauri::AppHandle,
) -> Result<(), String> {
match app_handle.get_webview(label) {
match app_handle.get_webview(&label) {
Some(webview) => {
if webview.set_size(LogicalSize::new(width, height)).is_ok() {
Ok(())
@@ -143,10 +117,10 @@ pub fn resize_column(
}
}
#[tauri::command]
#[tauri::command(async)]
#[specta::specta]
pub fn reload_column(label: &str, app_handle: tauri::AppHandle) -> Result<(), String> {
match app_handle.get_webview(label) {
pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<(), String> {
match app_handle.get_webview(&label) {
Some(webview) => {
if webview.eval("window.location.reload()").is_ok() {
Ok(())
@@ -203,6 +177,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.inner_size(window.width, window.height)
.minimizable(window.minimizable)
.maximizable(window.maximizable)
.transparent(true)
.effects(WindowEffectsConfig {
state: None,
effects: vec![Effect::Mica],
@@ -212,22 +187,8 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.build()
.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
#[cfg(not(target_os = "linux"))]
#[cfg(target_os = "windows")]
window.create_overlay_titlebar().unwrap();
// Restore native border
@@ -266,18 +227,3 @@ pub fn open_main_window(app: tauri::AppHandle) {
pub fn force_quit() {
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")]
extern crate cocoa;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
#[cfg(target_os = "macos")]
use border::WebviewWindowExt as BorderWebviewWindowExt;
@@ -24,7 +21,6 @@ use std::{
time::Duration,
};
use tauri::{path::BaseDirectory, Manager};
#[cfg(not(target_os = "linux"))]
use tauri_plugin_decorum::WebviewWindowExt;
use tauri_specta::{collect_commands, Builder};
@@ -132,8 +128,7 @@ fn main() {
reload_column,
open_window,
open_main_window,
force_quit,
set_badge
force_quit
]);
builder
@@ -156,7 +151,7 @@ fn main() {
#[cfg(target_os = "macos")]
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();
// Set custom decoration for Windows
@@ -188,9 +183,11 @@ fn main() {
let home_dir = app.path().home_dir().unwrap();
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
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
let opts = Options::new()
@@ -199,13 +196,13 @@ fn main() {
.timeout(Duration::from_secs(30));
// Setup nostr client
let client = match database {
Ok(db) => ClientBuilder::default().database(db).opts(opts).build(),
Err(_) => ClientBuilder::default().opts(opts).build(),
};
let client = ClientBuilder::default()
.database(database)
.opts(opts)
.build();
// Get bootstrap relays
if let Ok(path) = app
if let Ok(path) = handle
.path()
.resolve("resources/relays.txt", BaseDirectory::Resource)
{
@@ -240,12 +237,14 @@ fn main() {
// Connect
client.connect().await;
// Update global state
app.handle().manage(Nostr {
client,
contact_list: Mutex::new(vec![]),
settings: Mutex::new(Settings::default()),
})
client
});
// Create global state
app.manage(Nostr {
client,
contact_list: Mutex::new(vec![]),
settings: Mutex::new(Settings::default()),
});
Ok(())

View File

@@ -51,37 +51,16 @@
"icons/icon.icns",
"icons/icon.ico"
],
"linux": {
"appimage": {
"bundleMediaFramework": true,
"files": {}
},
"deb": {
"files": {}
},
"rpm": {
"epoch": 0,
"files": {},
"release": "1"
"windows": {
"allowDowngrades": true,
"webviewInstallMode": {
"silent": true,
"type": "downloadBootstrapper"
}
},
"macOS": {
"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": [
{
"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",
"app": {
"trayIcon": {
"id": "main",
"iconPath": "./icons/tray.png",
"iconAsTemplate": true,
"menuOnLeftClick": false
},
"windows": [
{
"title": "Lume",
"label": "main",
"titleBarStyle": "Overlay",
"width": 1045,
"height": 800,
"minWidth": 480,
"minHeight": 760,
"hiddenTitle": true,
"transparent": true,
"windowEffects": {
"state": "followsWindowActiveState",
"effects": [
"underWindowBackground"
]
}
}
]
}
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"app": {
"windows": [
{
"title": "Lume",
"label": "main",
"titleBarStyle": "Overlay",
"width": 900,
"height": 700,
"minWidth": 480,
"minHeight": 700,
"hiddenTitle": true,
"transparent": true,
"windowEffects": {
"effects": [
"underWindowBackground"
]
}
}
]
}
}

View File

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

View File

@@ -429,9 +429,6 @@ async openMainWindow() : Promise<void> {
},
async forceQuit() : Promise<void> {
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 type { LumeColumn } from "@/types";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
@@ -84,7 +84,7 @@ export const Column = memo(function Column({
}, [account]);
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">
<Header
label={column.label}

View File

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

View File

@@ -170,7 +170,7 @@ function Screen() {
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">
<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 { CancelIcon, PlusIcon } from "@/components";
import { NostrQuery } from "@/system";
import type { Relay } from "@/types";
import { commands } from "@/commands.gen";
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")({
loader: async () => {
const bootstrapRelays = await NostrQuery.getBootstrapRelays();
return bootstrapRelays;
const res = await commands.getBootstrapRelays();
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 [];
}
},
saveBootstrapRelays: async (relays: Relay[]) => {
saveBootstrapRelays: async (relays: string[]) => {
const text = relays
.map((relay) => Object.values(relay).join(","))
.join("\n");