diff --git a/changelog.md b/changelog.md index bb07f6ae..8beaaf72 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ - improvements on system color detection and expose more system colors based in accent gamma. - improve theme creation experience by adding live reload feature. - improve toolbar layouts (placeholders) creation experience by adding live reload feature. +- improve weg items editor experience by adding live reload feature. ### refactor - deprecate `onClick` and add new `onClickV2` on toolbar modules. diff --git a/src/apps/seelenweg/modules/shared/store/infra.ts b/src/apps/seelenweg/modules/shared/store/infra.ts index 9846e748..5817450c 100644 --- a/src/apps/seelenweg/modules/shared/store/infra.ts +++ b/src/apps/seelenweg/modules/shared/store/infra.ts @@ -6,10 +6,11 @@ import { SwItemType, SwSavedItem } from '../../../../shared/schemas/SeelenWegIte import { Theme } from '../../../../shared/schemas/Theme'; import { updateHitbox } from '../../../events'; import i18n from '../../../i18n'; -import { loadPinnedItems } from './storeApi'; +import { IsSavingPinnedItems, loadPinnedItems } from './storeApi'; import { configureStore } from '@reduxjs/toolkit'; import { listen as listenGlobal } from '@tauri-apps/api/event'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; +import { debounce } from 'lodash'; import { SwPinnedAppUtils } from '../../item/app/PinnedApp'; import { SwTemporalAppUtils } from '../../item/app/TemporalApp'; @@ -111,6 +112,21 @@ export async function registerStoreEvents() { const userSettings = await new UserSettingsLoader().load(); loadThemeCSS(userSettings); }); + + await listenGlobal( + 'weg-items', + debounce(async () => { + if (IsSavingPinnedItems.current) { + IsSavingPinnedItems.current = false; + return; + } + + const apps = await loadPinnedItems(); + store.dispatch(RootActions.setItemsOnLeft(await cleanSavedItems(apps.left))); + store.dispatch(RootActions.setItemsOnCenter(await cleanSavedItems(apps.center))); + store.dispatch(RootActions.setItemsOnRight(await cleanSavedItems(apps.right))); + }, 100), + ); } function loadSettingsCSS(settings: Seelenweg) { diff --git a/src/apps/seelenweg/modules/shared/store/storeApi.ts b/src/apps/seelenweg/modules/shared/store/storeApi.ts index 1885964c..cbd5944e 100644 --- a/src/apps/seelenweg/modules/shared/store/storeApi.ts +++ b/src/apps/seelenweg/modules/shared/store/storeApi.ts @@ -5,7 +5,8 @@ import { SwSaveFileSchema, } from '../../../../shared/schemas/SeelenWegItems'; import { path } from '@tauri-apps/api'; -import { exists, readTextFile, writeTextFile } from '@tauri-apps/plugin-fs'; +import { invoke } from '@tauri-apps/api/core'; +import { writeTextFile } from '@tauri-apps/plugin-fs'; import yaml from 'js-yaml'; import { debounce } from 'lodash'; @@ -13,6 +14,10 @@ import { store } from './infra'; import { RootState, SwItem } from './domain'; +export const IsSavingPinnedItems = { + current: false, +}; + export const savePinnedItems = debounce( async (state: RootState = store.getState()): Promise => { const cb = (acc: SwSavedItem[], item: SwItem) => { @@ -40,17 +45,13 @@ export const savePinnedItems = debounce( }; const yaml_route = await path.join(await path.appDataDir(), 'seelenweg_items.yaml'); + IsSavingPinnedItems.current = true; await writeTextFile(yaml_route, yaml.dump(data)); }, 1000, ); export const loadPinnedItems = async (): Promise => { - let yaml_route = await path.join(await path.appDataDir(), 'seelenweg_items.yaml'); - - if (!(await exists(yaml_route))) { - return SwSaveFileSchema.parse({}); - } - - return SwSaveFileSchema.parse(yaml.load(await readTextFile(yaml_route))); + let items = await invoke('state_get_weg_items'); + return SwSaveFileSchema.parse(items || {}); }; diff --git a/src/apps/shared/schemas/SeelenWegItems.ts b/src/apps/shared/schemas/SeelenWegItems.ts index a6871e85..0e3601b5 100644 --- a/src/apps/shared/schemas/SeelenWegItems.ts +++ b/src/apps/shared/schemas/SeelenWegItems.ts @@ -40,7 +40,7 @@ export const SwSavedItemSchema = z.union([ StartMenuItemSchema, ]); -export type SwSaveFile = z.infer; +export interface SwSaveFile extends z.infer {} export const SwSaveFileSchema = z.object({ left: z.array(SwSavedItemSchema).default([ { diff --git a/src/background/exposed.rs b/src/background/exposed.rs index cd169249..0a42fa63 100644 --- a/src/background/exposed.rs +++ b/src/background/exposed.rs @@ -182,6 +182,7 @@ pub fn register_invoke_handler(app_builder: Builder) -> Builder { get_auto_start_status, state_get_themes, state_get_placeholders, + state_get_weg_items, // Media media_prev, media_toggle_play_pause, diff --git a/src/background/state/application/mod.rs b/src/background/state/application/mod.rs new file mode 100644 index 00000000..aa109ad8 --- /dev/null +++ b/src/background/state/application/mod.rs @@ -0,0 +1,91 @@ +use lazy_static::lazy_static; +use notify::{RecursiveMode, Watcher}; +use parking_lot::Mutex; +use std::sync::Arc; +use tauri::{AppHandle, Emitter, Manager}; + +use crate::{error_handler::Result, log_error, seelen::get_app_handle}; + +use super::domain::WegItems; + +lazy_static! { + pub static ref FULL_STATE: Arc> = Arc::new(Mutex::new( + FullState::new().expect("Failed to create placeholders manager") + )); +} + +pub struct FullState { + handle: AppHandle, + weg_items: WegItems, +} + +impl FullState { + fn new() -> Result { + let mut manager = Self { + handle: get_app_handle(), + weg_items: serde_yaml::Value::Null, + }; + manager.load_all()?; + manager.start_listeners()?; + Ok(manager) + } + + pub fn weg_items(&self) -> &WegItems { + &self.weg_items + } + + fn start_listeners(&mut self) -> Result<()> { + let (tx, rx) = crossbeam_channel::unbounded(); + + let mut watcher = notify::recommended_watcher(tx)?; + + let data_dir = self.handle.path().app_data_dir()?; + let weg_items_path = data_dir.join("seelenweg_items.yaml"); + + watcher.watch(&data_dir, RecursiveMode::Recursive)?; + + std::thread::spawn(move || { + let _watcher = watcher; + for event in rx { + match event { + Ok(event) => { + if event.paths.contains(&weg_items_path) { + log::info!("Weg Items changed: {:?}", weg_items_path); + let mut manager = FULL_STATE.lock(); + log_error!(manager.load_weg_items()); + log_error!(manager.emit_weg_items()); + } + } + Err(e) => log::error!("Placeholder watcher error: {:?}", e), + } + } + }); + + log::info!("Seelen UI Data Watcher started!"); + Ok(()) + } + + fn load_weg_items(&mut self) -> Result<()> { + let dir = self.handle.path().app_data_dir()?; + let path = dir.join("seelenweg_items.yaml"); + + self.weg_items = if !path.exists() { + serde_yaml::Value::Null + } else { + serde_yaml::from_str(&std::fs::read_to_string(&path)?)? + }; + + Ok(()) + } + + fn load_all(&mut self) -> Result<()> { + self.load_weg_items()?; + Ok(()) + } + + fn emit_weg_items(&self) -> Result<()> { + let handle = get_app_handle(); + handle.emit("weg-items", self.weg_items())?; + Ok(()) + } +} diff --git a/src/background/state/domain.rs b/src/background/state/domain.rs index 16b4f6ff..a58f65e3 100644 --- a/src/background/state/domain.rs +++ b/src/background/state/domain.rs @@ -46,3 +46,7 @@ pub struct Placeholder { pub center: Vec, pub right: Vec, } + +// ============== WEG ============== + +pub type WegItems = serde_yaml::Value; diff --git a/src/background/state/infrastructure.rs b/src/background/state/infrastructure.rs index 690bc7c8..f60ddf66 100644 --- a/src/background/state/infrastructure.rs +++ b/src/background/state/infrastructure.rs @@ -1,7 +1,8 @@ use itertools::Itertools; use super::{ - domain::{Placeholder, Theme}, + application::FULL_STATE, + domain::{Placeholder, Theme, WegItems}, placeholders::PLACEHOLDERS_MANAGER, themes::THEME_MANAGER, }; @@ -25,3 +26,8 @@ pub fn state_get_placeholders() -> Vec { .cloned() .collect_vec() } + +#[tauri::command] +pub fn state_get_weg_items() -> WegItems { + FULL_STATE.lock().weg_items().clone() +} diff --git a/src/background/state/mod.rs b/src/background/state/mod.rs index 18c346fe..3c452e61 100644 --- a/src/background/state/mod.rs +++ b/src/background/state/mod.rs @@ -1,3 +1,4 @@ +mod application; mod domain; pub mod infrastructure; mod placeholders;