diff --git a/apps/desktop2/src/routes/panel.$account.tsx b/apps/desktop2/src/routes/panel.$account.tsx new file mode 100644 index 00000000..ba780055 --- /dev/null +++ b/apps/desktop2/src/routes/panel.$account.tsx @@ -0,0 +1,357 @@ +import { Note } from "@/components/note"; +import { User } from "@/components/user"; +import { HorizontalDotsIcon, InfoIcon, RepostIcon } from "@lume/icons"; +import { type LumeEvent, LumeWindow, NostrQuery, useEvent } from "@lume/system"; +import { Kind } from "@lume/types"; +import { Spinner } from "@lume/ui"; +import { + checkForAppUpdates, + decodeZapInvoice, + formatCreatedAt, +} from "@lume/utils"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import * as Tabs from "@radix-ui/react-tabs"; +import { useQuery } from "@tanstack/react-query"; +import { createFileRoute } from "@tanstack/react-router"; +import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; +import { getCurrent } from "@tauri-apps/api/window"; +import { exit } from "@tauri-apps/plugin-process"; +import { open } from "@tauri-apps/plugin-shell"; +import { type ReactNode, useCallback, useEffect, useRef } from "react"; +import { Virtualizer } from "virtua"; + +export const Route = createFileRoute("/panel/$account")({ + component: Screen, +}); + +function Screen() { + const { account } = Route.useParams(); + const { queryClient } = Route.useRouteContext(); + const { isLoading, data } = useQuery({ + queryKey: ["notification", account], + queryFn: async () => { + const events = await NostrQuery.getNotifications(); + return events; + }, + select: (events) => { + const zaps = new Map(); + const reactions = new Map(); + const texts = events.filter((ev) => ev.kind === Kind.Text); + const zapEvents = events.filter((ev) => ev.kind === Kind.ZapReceipt); + const reactEvents = events.filter( + (ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction, + ); + + for (const event of reactEvents) { + const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1]; + + if (rootId) { + if (reactions.has(rootId)) { + reactions.get(rootId).push(event); + } else { + reactions.set(rootId, [event]); + } + } + } + + for (const event of zapEvents) { + const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1]; + + if (rootId) { + if (zaps.has(rootId)) { + zaps.get(rootId).push(event); + } else { + zaps.set(rootId, [event]); + } + } + } + + return { texts, zaps, reactions }; + }, + }); + + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "Open Lume", + action: () => LumeWindow.openMainWindow(), + }), + MenuItem.new({ + text: "New Post", + action: () => LumeWindow.openEditor(), + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "About Lume", + action: async () => await open("https://lume.nu"), + }), + MenuItem.new({ + text: "Check for Updates", + action: async () => await checkForAppUpdates(false), + }), + MenuItem.new({ + text: "Settings", + action: () => LumeWindow.openSettings(), + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "Quit", + action: async () => await exit(0), + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, []); + + useEffect(() => { + const unlisten = getCurrent().listen("notification", async (data) => { + const event: LumeEvent = JSON.parse(data.payload as string); + await queryClient.setQueryData( + ["notification", account], + (data: LumeEvent[]) => [event, ...data], + ); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+
+
+

Notifications

+
+
+ + + + + + +
+
+ + + + Replies + + + Reactions + + + Zaps + + + + + {data.texts.map((event, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + ))} + + + {[...data.reactions.entries()].map(([root, events]) => ( +
+
+
+ +
+
+ {events.map((event) => ( + + + +
+ {event.kind === Kind.Reaction ? ( + event.content === "+" ? ( + "👍" + ) : ( + event.content + ) + ) : ( + + )} +
+
+
+ ))} +
+
+
+ ))} +
+ + {[...data.zaps.entries()].map(([root, events]) => ( +
+
+
+ +
+
+ {events.map((event) => ( + tag[0] === "P")[1]} + > + + +
+ ₿ {decodeZapInvoice(event.tags).bitcoinFormatted} +
+
+
+ ))} +
+
+
+ ))} +
+ + + + +
+
+
+ ); +} + +function Tab({ value, children }: { value: string; children: ReactNode[] }) { + const ref = useRef(null); + + return ( + + + {children} + + + ); +} + +function RootNote({ id }: { id: string }) { + const { isLoading, isError, data } = useEvent(id); + + if (isLoading) { + return ( +
+
+
+
+ ); + } + + if (isError || !data) { + return ( +
+
+ +
+

+ Event not found with your current relay set +

+
+ ); + } + + return ( + + + + + + + +
{data.content}
+
+
+ ); +} + +function TextNote({ event }: { event: LumeEvent }) { + const pTags = event.tags + .filter((tag) => tag[0] === "p") + .map((tag) => tag[1]) + .slice(0, 3); + + return ( + + + + + +
+
+ + + {formatCreatedAt(event.created_at)} + +
+
+ + Reply to: + +
+ {pTags.map((replyTo) => ( + + + + + + ))} +
+
+
+
+
+
+
+
{event.content}
+
+ + + ); +} diff --git a/apps/desktop2/src/routes/panel.tsx b/apps/desktop2/src/routes/panel.tsx deleted file mode 100644 index e28d54b5..00000000 --- a/apps/desktop2/src/routes/panel.tsx +++ /dev/null @@ -1,384 +0,0 @@ -import { Note } from "@/components/note"; -import { User } from "@/components/user"; -import { - HorizontalDotsIcon, - InfoIcon, - RepostIcon, - SearchIcon, -} from "@lume/icons"; -import { type LumeEvent, LumeWindow, NostrQuery, useEvent } from "@lume/system"; -import { Kind } from "@lume/types"; -import { - checkForAppUpdates, - decodeZapInvoice, - formatCreatedAt, -} from "@lume/utils"; -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import * as Tabs from "@radix-ui/react-tabs"; -import { createFileRoute } from "@tanstack/react-router"; -import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; -import { getCurrent } from "@tauri-apps/api/window"; -import { exit } from "@tauri-apps/plugin-process"; -import { open } from "@tauri-apps/plugin-shell"; -import { - type ReactNode, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { Virtualizer } from "virtua"; - -interface EmitAccount { - account: string; -} - -export const Route = createFileRoute("/panel")({ - component: Screen, -}); - -function Screen() { - const [account, setAccount] = useState(null); - const [events, setEvents] = useState([]); - - const { texts, zaps, reactions } = useMemo(() => { - const zaps = new Map(); - const reactions = new Map(); - const texts = events.filter((ev) => ev.kind === Kind.Text); - const zapEvents = events.filter((ev) => ev.kind === Kind.ZapReceipt); - const reactEvents = events.filter( - (ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction, - ); - - for (const event of reactEvents) { - const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1]; - - if (rootId) { - if (reactions.has(rootId)) { - reactions.get(rootId).push(event); - } else { - reactions.set(rootId, [event]); - } - } - } - - for (const event of zapEvents) { - const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1]; - - if (rootId) { - if (zaps.has(rootId)) { - zaps.get(rootId).push(event); - } else { - zaps.set(rootId, [event]); - } - } - } - - return { texts, zaps, reactions }; - }, [events?.length]); - - const showContextMenu = useCallback(async (e: React.MouseEvent) => { - e.preventDefault(); - - const menuItems = await Promise.all([ - MenuItem.new({ - text: "Open Lume", - action: () => LumeWindow.openMainWindow(), - }), - MenuItem.new({ - text: "New Post", - action: () => LumeWindow.openEditor(), - }), - PredefinedMenuItem.new({ item: "Separator" }), - MenuItem.new({ - text: "About Lume", - action: async () => await open("https://lume.nu"), - }), - MenuItem.new({ - text: "Check for Updates", - action: async () => await checkForAppUpdates(false), - }), - MenuItem.new({ - text: "Settings", - action: () => LumeWindow.openSettings(), - }), - PredefinedMenuItem.new({ item: "Separator" }), - MenuItem.new({ - text: "Quit", - action: async () => await exit(0), - }), - ]); - - const menu = await Menu.new({ - items: menuItems, - }); - - await menu.popup().catch((e) => console.error(e)); - }, []); - - useEffect(() => { - if (account?.length && account?.startsWith("npub1")) { - NostrQuery.getNotifications() - .then((data) => { - const sorted = data.sort((a, b) => b.created_at - a.created_at); - console.log("sorted"); - setEvents(sorted); - }) - .catch((e) => console.log(e)); - } - }, [account]); - - useEffect(() => { - const unlistenLoad = getCurrent().listen( - "load-notification", - (data) => { - setAccount(data.payload.account); - }, - ); - - const unlistenNewEvent = getCurrent().listen("notification", (data) => { - const event: LumeEvent = JSON.parse(data.payload as string); - setEvents((prev) => [event, ...prev]); - }); - - return () => { - unlistenLoad.then((f) => f()); - unlistenNewEvent.then((f) => f()); - }; - }, []); - - if (!account) { - return ( -
- Please log in. -
- ); - } - - return ( -
-
-
-

Notifications

-
-
- - - - - - -
-
- - - - - Replies - - - Reactions - - - Zaps - - -
- - {texts.map((event, index) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: - - ))} - - - {[...reactions.entries()].map(([root, events]) => ( -
-
-
- -
-
- {events.map((event) => ( - - - -
- {event.kind === Kind.Reaction ? ( - event.content === "+" ? ( - "👍" - ) : ( - event.content - ) - ) : ( - - )} -
-
-
- ))} -
-
-
- ))} -
- - {[...zaps.entries()].map(([root, events]) => ( -
-
-
- -
-
- {events.map((event) => ( - tag[0] === "P")[1]} - > - - -
- ₿ {decodeZapInvoice(event.tags).bitcoinFormatted} -
-
-
- ))} -
-
-
- ))} -
-
-
- - - - -
-
- ); -} - -function Tab({ value, children }: { value: string; children: ReactNode[] }) { - const ref = useRef(null); - - return ( - - - {children} - - - ); -} - -function RootNote({ id }: { id: string }) { - const { isLoading, isError, data } = useEvent(id); - - if (isLoading) { - return ( -
-
-
-
- ); - } - - if (isError || !data) { - return ( -
-
- -
-

- Event not found with your current relay set -

-
- ); - } - - return ( - - - - - - - -
{data.content}
-
-
- ); -} - -function TextNote({ event }: { event: LumeEvent }) { - const pTags = event.tags - .filter((tag) => tag[0] === "p") - .map((tag) => tag[1]) - .slice(0, 3); - - return ( - - - - - -
-
- - - {formatCreatedAt(event.created_at)} - -
-
- - Reply to: - -
- {pTags.map((replyTo) => ( - - - - - - ))} -
-
-
-
-
-
-
-
{event.content}
-
- - - ); -} diff --git a/packages/system/src/account.ts b/packages/system/src/account.ts index 4a0dd348..dd3f5d19 100644 --- a/packages/system/src/account.ts +++ b/packages/system/src/account.ts @@ -1,5 +1,4 @@ import type { Metadata } from "@lume/types"; -import { Window } from "@tauri-apps/api/window"; import { type Result, commands } from "./commands"; export class NostrAccount { @@ -24,9 +23,6 @@ export class NostrAccount { } if (query.status === "ok") { - const panel = Window.getByLabel("panel"); - panel.emit("load-notification", { account: npub }); // trigger load notification - return query.data; } else { throw new Error(query.error); diff --git a/packages/system/src/commands.ts b/packages/system/src/commands.ts index 31e2b030..982deee1 100644 --- a/packages/system/src/commands.ts +++ b/packages/system/src/commands.ts @@ -396,9 +396,6 @@ try { else return { status: "error", error: e as any }; } }, -async showInFolder(path: string) : Promise { -await TAURI_INVOKE("show_in_folder", { path }); -}, async createColumn(column: Column) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("create_column", { column }) }; diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7cf0d769..7b731f68 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,7 +15,6 @@ nostr-sdk = { version = "0.32", features = ["sqlite"] } tokio = { version = "1", features = ["full"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" } tauri = { version = "2.0.0-beta", features = [ "unstable", "tray-icon", @@ -34,7 +33,6 @@ tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace" tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } -tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" } tauri-specta = { version = "^2.0.0-rc.11", features = ["typescript"] } tauri-plugin-theme = "0.4.1" tauri-plugin-decorum = "0.1.0" @@ -51,6 +49,8 @@ regex = "1.10.4" cocoa = "0.25.0" objc = "0.2.7" rand = "0.8.5" +monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" } +tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" } [profile.release] codegen-units = 1 diff --git a/src-tauri/src/fns.rs b/src-tauri/src/commands/fns.rs similarity index 87% rename from src-tauri/src/fns.rs rename to src-tauri/src/commands/fns.rs index 0f45ee28..2cf44fd8 100644 --- a/src-tauri/src/fns.rs +++ b/src-tauri/src/commands/fns.rs @@ -1,18 +1,16 @@ use std::ffi::CString; -use cocoa::appkit::NSWindowCollectionBehavior; -use tauri::Manager; +use tauri::{AppHandle, Manager, WebviewWindow}; use tauri_nspanel::{ block::ConcreteBlock, cocoa::{ - appkit::{NSMainMenuWindowLevel, NSView, NSWindow}, + appkit::{NSMainMenuWindowLevel, NSView, NSWindow, NSWindowCollectionBehavior}, base::{id, nil}, foundation::{NSPoint, NSRect}, }, objc::{class, msg_send, runtime::NO, sel, sel_impl}, panel_delegate, ManagerExt, WebviewWindowExt, }; -use tauri_plugin_decorum::WebviewWindowExt as WebviewWindowExt2; #[allow(non_upper_case_globals)] const NSWindowStyleMaskNonActivatingPanel: i32 = 1 << 7; @@ -23,14 +21,17 @@ pub fn swizzle_to_menubar_panel(app_handle: &tauri::AppHandle) { }); let window = app_handle.get_webview_window("panel").unwrap(); - window.make_transparent().unwrap(); let panel = window.to_panel().unwrap(); + let handle = app_handle.clone(); panel_delegate.set_listener(Box::new(move |delegate_name: String| { - if delegate_name.as_str() == "window_did_resign_key" { - let _ = handle.emit("menubar_panel_did_resign_key", ()); + match delegate_name.as_str() { + "window_did_resign_key" => { + let _ = handle.emit("menubar_panel_did_resign_key", ()); + } + _ => (), } })); @@ -47,12 +48,14 @@ pub fn swizzle_to_menubar_panel(app_handle: &tauri::AppHandle) { panel.set_delegate(panel_delegate); } -pub fn setup_menubar_panel_listeners(app_handle: &tauri::AppHandle) { +pub fn setup_menubar_panel_listeners(app_handle: &AppHandle) { fn hide_menubar_panel(app_handle: &tauri::AppHandle) { if check_menubar_frontmost() { return; } + let panel = app_handle.get_webview_panel("panel").unwrap(); + panel.order_out(None); } @@ -79,19 +82,16 @@ pub fn setup_menubar_panel_listeners(app_handle: &tauri::AppHandle) { ); } -pub fn update_menubar_appearance(app_handle: &tauri::AppHandle) { - let window = app_handle.get_window("panel").unwrap(); - set_corner_radius(&window, 13.0); -} - -pub fn set_corner_radius(window: &tauri::Window, radius: f64) { +pub fn set_corner_radius(window: &WebviewWindow, radius: f64) { let win: id = window.ns_window().unwrap() as _; unsafe { let view: id = win.contentView(); + view.wantsLayer(); let layer: id = view.layer(); + let _: () = msg_send![layer, setCornerRadius: radius]; } } @@ -138,7 +138,6 @@ pub fn position_menubar_panel(app_handle: &tauri::AppHandle, padding_top: f64) { fn register_workspace_listener(name: String, callback: Box) { let workspace: id = unsafe { msg_send![class!(NSWorkspace), sharedWorkspace] }; - let notification_center: id = unsafe { msg_send![workspace, notificationCenter] }; let block = ConcreteBlock::new(move |_notif: id| { @@ -160,7 +159,6 @@ fn register_workspace_listener(name: String, callback: Box) { fn app_pid() -> i32 { let process_info: id = unsafe { msg_send![class!(NSProcessInfo), processInfo] }; - let pid: i32 = unsafe { msg_send![process_info, processIdentifier] }; pid diff --git a/src-tauri/src/commands/folder.rs b/src-tauri/src/commands/folder.rs deleted file mode 100644 index 94b13cff..00000000 --- a/src-tauri/src/commands/folder.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::process::Command; - -#[tauri::command] -#[specta::specta] -pub async fn show_in_folder(path: String) { - #[cfg(target_os = "windows")] - { - Command::new("explorer") - .args(["/select,", &path]) // The comma after select is not a typo - .spawn() - .unwrap(); - } - - #[cfg(target_os = "linux")] - { - use std::fs::metadata; - use std::path::PathBuf; - if path.contains(",") { - // see https://gitlab.freedesktop.org/dbus/dbus/-/issues/76 - let new_path = match metadata(&path).unwrap().is_dir() { - true => path, - false => { - let mut path2 = PathBuf::from(path); - path2.pop(); - path2.into_os_string().into_string().unwrap() - } - }; - Command::new("xdg-open").arg(&new_path).spawn().unwrap(); - } else { - Command::new("dbus-send") - .args([ - "--session", - "--dest=org.freedesktop.FileManager1", - "--type=method_call", - "/org/freedesktop/FileManager1", - "org.freedesktop.FileManager1.ShowItems", - format!("array:string:file://{path}").as_str(), - "string:\"\"", - ]) - .spawn() - .unwrap(); - } - } - - #[cfg(target_os = "macos")] - { - Command::new("open").args(["-R", &path]).spawn().unwrap(); - } -} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 5b07a021..a886120d 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -1,2 +1,3 @@ -pub mod folder; +pub mod fns; +pub mod tray; pub mod window; diff --git a/src-tauri/src/commands/tray.rs b/src-tauri/src/commands/tray.rs new file mode 100644 index 00000000..4ab34a28 --- /dev/null +++ b/src-tauri/src/commands/tray.rs @@ -0,0 +1,65 @@ +use std::path::PathBuf; +use tauri::window::{Effect, EffectsBuilder}; +use tauri::{ + tray::{MouseButtonState, TrayIconEvent}, + WebviewWindowBuilder, +}; +use tauri::{AppHandle, Manager, WebviewUrl}; +use tauri_nspanel::ManagerExt; + +use super::fns::{ + position_menubar_panel, set_corner_radius, setup_menubar_panel_listeners, + swizzle_to_menubar_panel, +}; + +pub fn create_tray_panel(account: &str, app: &AppHandle) { + let tray = app.tray_by_id("main").unwrap(); + + tray.on_tray_icon_event(|tray, event| { + if let TrayIconEvent::Click { button_state, .. } = event { + if button_state == MouseButtonState::Up { + let app = tray.app_handle(); + let panel = app.get_webview_panel("panel").unwrap(); + + match panel.is_visible() { + true => panel.order_out(None), + false => { + position_menubar_panel(app, 0.0); + panel.show(); + } + } + } + } + }); + + if let Some(window) = app.get_webview_window("panel") { + let _ = window.destroy(); + }; + + let mut url = "/panel/".to_owned(); + url.push_str(account); + + let window = WebviewWindowBuilder::new(app, "panel", WebviewUrl::App(PathBuf::from(url))) + .title("Panel") + .inner_size(350.0, 500.0) + .fullscreen(false) + .resizable(false) + .visible(false) + .decorations(false) + .transparent(true) + .build() + .unwrap(); + + let _ = window.set_effects( + EffectsBuilder::new() + .effect(Effect::Popover) + .state(tauri::window::EffectState::FollowsWindowActiveState) + .build(), + ); + + set_corner_radius(&window, 13.0); + + // Convert window to panel + swizzle_to_menubar_panel(app); + setup_menubar_panel_listeners(app); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2aa065d6..0e2a7b0e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -9,6 +9,9 @@ extern crate cocoa; #[macro_use] extern crate objc; +use nostr_sdk::prelude::*; +use serde::{Deserialize, Serialize}; +use specta::Type; use std::sync::Mutex; use std::time::Duration; use std::{ @@ -16,24 +19,10 @@ use std::{ io::{self, BufRead}, str::FromStr, }; - -use nostr_sdk::prelude::*; -use serde::{Deserialize, Serialize}; -use specta::Type; -#[cfg(target_os = "macos")] -use tauri::tray::{MouseButtonState, TrayIconEvent}; use tauri::{path::BaseDirectory, Manager}; -use tauri_nspanel::ManagerExt; use tauri_plugin_decorum::WebviewWindowExt; -#[cfg(target_os = "macos")] -use crate::fns::{ - position_menubar_panel, setup_menubar_panel_listeners, swizzle_to_menubar_panel, - update_menubar_appearance, -}; - pub mod commands; -pub mod fns; pub mod nostr; #[derive(Serialize)] @@ -129,7 +118,6 @@ fn main() { nostr::event::event_to_bech32, nostr::event::user_to_bech32, nostr::event::unlisten, - commands::folder::show_in_folder, commands::window::create_column, commands::window::close_column, commands::window::reposition_column, @@ -161,37 +149,6 @@ fn main() { #[cfg(target_os = "macos")] main_window.set_traffic_lights_inset(8.0, 16.0).unwrap(); - // Create panel - #[cfg(target_os = "macos")] - swizzle_to_menubar_panel(app.handle()); - #[cfg(target_os = "macos")] - update_menubar_appearance(app.handle()); - #[cfg(target_os = "macos")] - setup_menubar_panel_listeners(app.handle()); - - // Setup tray icon - #[cfg(target_os = "macos")] - let tray = app.tray_by_id("tray_panel").unwrap(); - - // Handle tray icon event - #[cfg(target_os = "macos")] - tray.on_tray_icon_event(|tray, event| { - if let TrayIconEvent::Click { button_state, .. } = event { - if button_state == MouseButtonState::Up { - let app = tray.app_handle(); - let panel = app.get_webview_panel("panel").unwrap(); - - match panel.is_visible() { - true => panel.order_out(None), - false => { - position_menubar_panel(app, 0.0); - panel.show(); - } - } - } - } - }); - // Create data folder if not exist let home_dir = app.path().home_dir().unwrap(); let _ = fs::create_dir_all(home_dir.join("Lume/")); diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index a3ce615d..157c8662 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -7,6 +7,7 @@ use std::time::Duration; use tauri::{EventTarget, Manager, State}; use tauri_plugin_notification::NotificationExt; +use crate::commands::tray::create_tray_panel; use crate::nostr::event::RichEvent; use crate::nostr::internal::{get_user_settings, init_nip65}; use crate::nostr::utils::parse_event; @@ -202,6 +203,10 @@ pub async fn load_account( // Connect to user's relay (NIP-65) init_nip65(client).await; + // Create tray (macOS) + #[cfg(target_os = "macos")] + create_tray_panel(npub, &handle); + // Get user's contact list if let Ok(contacts) = client.get_contact_list(None).await { *state.contact_list.lock().unwrap() = contacts diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f3d7fd54..819e20e7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,124 +1,128 @@ { - "$schema": "../node_modules/@tauri-apps/cli/schema.json", - "productName": "Lume", - "version": "4.0.12", - "identifier": "nu.lume.Lume", - "build": { - "beforeBuildCommand": "pnpm desktop:build", - "beforeDevCommand": "pnpm desktop:dev", - "devUrl": "http://localhost:3000", - "frontendDist": "../dist" - }, - "app": { - "macOSPrivateApi": true, - "withGlobalTauri": true, - "trayIcon": { - "id": "tray_panel", - "iconPath": "./icons/tray.png", - "iconAsTemplate": true, - "menuOnLeftClick": false - }, - "security": { - "assetProtocol": { - "enable": true, - "scope": [ - "$APPDATA/*", - "$DATA/*", - "$LOCALDATA/*", - "$DESKTOP/*", - "$DOCUMENT/*", - "$DOWNLOAD/*", - "$HOME/*", - "$PICTURE/*", - "$PUBLIC/*", - "$VIDEO/*", - "$APPCONFIG/*", - "$RESOURCE/*" - ] - } - } - }, - "bundle": { - "licenseFile": "../LICENSE", - "longDescription": "nostr client for desktop", - "shortDescription": "nostr client", - "targets": "all", - "active": true, - "category": "SocialNetworking", - "resources": ["resources/*", "locales/*"], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "linux": { - "appimage": { - "bundleMediaFramework": true, - "files": {} - }, - "deb": { - "files": {} - }, - "rpm": { - "epoch": 0, - "files": {}, - "release": "1" - } - }, - "macOS": { - "dmg": { - "appPosition": { - "x": 180, - "y": 170 - }, - "applicationFolderPosition": { - "x": 480, - "y": 170 - }, - "windowSize": { - "height": 400, - "width": 660 - } - }, - "files": {}, - "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", - "description": "Nostr Bech32", - "ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"], - "role": "Viewer" - } - ] - }, - "plugins": { - "updater": { - "active": true, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK", - "windows": { - "installMode": "quiet" - }, - "endpoints": [ - "https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}", - "https://lus.reya3772.workers.dev/{{target}}/{{current_version}}" - ] - } - } + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "productName": "Lume", + "version": "4.0.12", + "identifier": "nu.lume.Lume", + "build": { + "beforeBuildCommand": "pnpm desktop:build", + "beforeDevCommand": "pnpm desktop:dev", + "devUrl": "http://localhost:3000", + "frontendDist": "../dist" + }, + "app": { + "macOSPrivateApi": true, + "withGlobalTauri": true, + "security": { + "assetProtocol": { + "enable": true, + "scope": [ + "$APPDATA/*", + "$DATA/*", + "$LOCALDATA/*", + "$DESKTOP/*", + "$DOCUMENT/*", + "$DOWNLOAD/*", + "$HOME/*", + "$PICTURE/*", + "$PUBLIC/*", + "$VIDEO/*", + "$APPCONFIG/*", + "$RESOURCE/*" + ] + } + } + }, + "bundle": { + "licenseFile": "../LICENSE", + "longDescription": "nostr client for desktop", + "shortDescription": "nostr client", + "targets": "all", + "active": true, + "category": "SocialNetworking", + "resources": [ + "resources/*", + "locales/*" + ], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "linux": { + "appimage": { + "bundleMediaFramework": true, + "files": {} + }, + "deb": { + "files": {} + }, + "rpm": { + "epoch": 0, + "files": {}, + "release": "1" + } + }, + "macOS": { + "dmg": { + "appPosition": { + "x": 180, + "y": 170 + }, + "applicationFolderPosition": { + "x": 480, + "y": 170 + }, + "windowSize": { + "height": 400, + "width": 660 + } + }, + "files": {}, + "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", + "description": "Nostr Bech32", + "ext": [ + "npub", + "nsec", + "nprofile", + "nevent", + "naddr", + "nrelay" + ], + "role": "Viewer" + } + ] + }, + "plugins": { + "updater": { + "active": true, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK", + "windows": { + "installMode": "quiet" + }, + "endpoints": [ + "https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}", + "https://lus.reya3772.workers.dev/{{target}}/{{current_version}}" + ] + } + } } diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json index 5943a274..a723829e 100644 --- a/src-tauri/tauri.macos.conf.json +++ b/src-tauri/tauri.macos.conf.json @@ -1,6 +1,12 @@ { "$schema": "../node_modules/@tauri-apps/cli/schema.json", "app": { + "trayIcon": { + "id": "main", + "iconPath": "./icons/tray.png", + "iconAsTemplate": true, + "menuOnLeftClick": false + }, "windows": [ { "title": "Lume", @@ -17,22 +23,6 @@ "underWindowBackground" ] } - }, - { - "title": "Lume Panel", - "label": "panel", - "url": "/panel", - "width": 350, - "height": 500, - "fullscreen": false, - "resizable": false, - "visible": false, - "decorations": false, - "windowEffects": { - "effects": [ - "popover" - ] - } } ] }