From 6f2f7f27fc65834b7be4a4d102030cd743baaf3c Mon Sep 17 00:00:00 2001 From: isark Date: Mon, 14 Aug 2023 22:53:04 +0200 Subject: [PATCH] Added poe client log reader (currently seems broken), keybinding in settings, log file detection --- epic test.json | 1 + src-tauri/Cargo.lock | 190 ++++++++++++++++++ src-tauri/Cargo.toml | 3 + src-tauri/icons/icon.ico | Bin 0 -> 4286 bytes src-tauri/src/config.rs | 20 ++ src-tauri/src/main.rs | 55 ++++- src-tauri/src/poe_reader.rs | 189 +++++++++++++++++ src-tauri/src/storage.rs | 1 + src-tauri/tauri.conf.json | 3 +- src/app/app.component.html | 5 +- src/app/app.component.scss | 10 +- src/app/app.module.ts | 1 - .../directives/record-key-chord.directive.ts | 14 +- .../plan-display/plan-display.component.scss | 8 + .../plan-display/plan-display.component.ts | 18 +- src/app/services/overlay.service.ts | 13 +- src/app/services/shortcut.service.ts | 5 + src/app/settings/bind-dialog.html | 12 ++ src/app/settings/settings.component.html | 8 +- src/app/settings/settings.component.ts | 89 +++++++- src/styles.scss | 7 +- yarn.lock | 4 +- 22 files changed, 625 insertions(+), 31 deletions(-) create mode 100644 epic test.json create mode 100644 src-tauri/icons/icon.ico create mode 100644 src-tauri/src/poe_reader.rs create mode 100644 src/app/settings/bind-dialog.html diff --git a/epic test.json b/epic test.json new file mode 100644 index 0000000..320b8a7 --- /dev/null +++ b/epic test.json @@ -0,0 +1 @@ +{"plan":[{"area_key":"1_1_town","notes":""},{"area_key":"1_1_1","notes":""},{"area_key":"1_1_2","notes":""},{"area_key":"1_1_2a","notes":""},{"area_key":"1_1_3","notes":""},{"area_key":"1_1_4_0","notes":""},{"area_key":"1_1_4_1","notes":""}],"current":0} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 175c5d6..8c89573 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -794,6 +794,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1022,6 +1031,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futf" version = "0.1.5" @@ -1703,6 +1721,26 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -1816,6 +1854,51 @@ dependencies = [ "treediff", ] +[[package]] +name = "keyvalues-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d990301996c856ea07a84bc291e76f1273db52683663efc05c8d355976897e5" +dependencies = [ + "pest", + "pest_derive", + "thiserror", +] + +[[package]] +name = "keyvalues-serde" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da419ac133bb3ddf0dbf9c12fcc0ce01d994fcb65f6f1713faf15cc689320b5f" +dependencies = [ + "keyvalues-parser", + "once_cell", + "paste", + "regex", + "serde", + "thiserror", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kuchiki" version = "0.8.1" @@ -2030,6 +2113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -2105,6 +2189,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" + [[package]] name = "nothing" version = "0.0.0" @@ -2113,18 +2203,39 @@ dependencies = [ "crossbeam", "directories", "log", + "notify", "poe_data", "raw-window-handle", + "regex", "serde", "serde_json", "simple_logger", "statig", + "steamlocate", "tauri", "tauri-build", "ts-rs", "x11rb", ] +[[package]] +name = "notify" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "notify-rust" version = "4.8.0" @@ -2423,6 +2534,12 @@ dependencies = [ "windows-targets 0.48.1", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pathdiff" version = "0.2.1" @@ -2435,6 +2552,50 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "pest_meta" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.8.0" @@ -3370,6 +3531,29 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "steamlocate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec01c74611d14a808cb212d17c6e03f0e30736a15ed1d5736f8a53154cea3ae" +dependencies = [ + "dirs", + "keyvalues-parser", + "keyvalues-serde", + "serde", + "steamy-vdf", + "winreg 0.11.0", +] + +[[package]] +name = "steamy-vdf" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533127ad49314bfe71c3d3fd36b3ebac3d24f40618092e70e1cfe8362c7fac79" +dependencies = [ + "nom", +] + [[package]] name = "string_cache" version = "0.8.7" @@ -4051,6 +4235,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uds_windows" version = "1.0.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 18ba89f..bc1a5d4 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ ts-rs = "6.2.1" [dependencies] +steamlocate = "1.2.1" tauri = { version = "1.2", features = [ "system-tray", "api-all"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -34,6 +35,8 @@ crossbeam = "0.8.2" poe_data = { path = "poe_data" } directories = "5.0.1" +notify = "6.0.1" +regex = "1.9.3" [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7630b3d5e3ecf69392f0e68fe9d348dcf3fa2149 GIT binary patch literal 4286 zcmeHKU2GIp6h5`ZrKLds*zOkCncXgyU=-t?pK z;<;)1f$*4(YB7<}(0|~!gY7hXawEx-1$+8w{=`kqz#I_cYqfl> zyB_z}stT@P=T&L_brL?f+O@#n3td0TJsnzYY0)aP#EkOkb}fz={-NpNHt^p}XTR@) z?MV`DauLd9Psm0Bk=k-;Qea z(Deat$o#*LbkN+fbPMt^C*EJ9)l)gy~yE`0tW`2AVr;wAYEV$RPI=G}@I;Pv=x2*G~D`)9EICz)MUA9KvL zt6iJA;jbwKe$?1M#ps&973xZz?b{<-yo;K-7x54#f8NsarOv>|3%-_nR%*nfsn?$fe`dUw=03U0 zw9+;Fr%a97hWGFSP7YS;6ZWg%q5r*h|BdriG1&@^lj1OHE&2)eVFe;&lyuV@5u4asvbUrozKO&7v?&!8_)5xK6W2!(COh!Sst#!E}MuV z&X(h=v7g8%oExtsIA@WZ=p#9KOBJuDZlmeLJR literal 0 HcmV?d00001 diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 4c6cbd2..7d972a1 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -1,8 +1,13 @@ +use std::path::PathBuf; + use serde::{Deserialize, Serialize}; #[cfg(not(build_only))] use ts_rs::TS; +#[cfg(build_only)] +use crate::poe_reader; + #[cfg_attr(not(build_only), derive(TS))] #[cfg_attr(not(build_only), ts(rename_all = "camelCase"))] #[derive(Serialize, Deserialize, Debug, Default, Clone)] @@ -21,9 +26,15 @@ pub struct Rect { pub struct Config { pub initial_plan_window_position: Rect, pub hide_on_unfocus: bool, + pub toggle_overlay: String, + pub prev: String, + pub next: String, + pub plan_bg: String, pub backdrop_bg: String, + + pub poe_client_log_path: Option, } impl Default for Config { @@ -32,8 +43,17 @@ impl Default for Config { initial_plan_window_position: Default::default(), hide_on_unfocus: true, toggle_overlay: "F6".into(), + prev: "F7".into(), + next: "F8".into(), plan_bg: "#00000010".to_string(), backdrop_bg: "#00000030".to_string(), + #[cfg(build_only)] + poe_client_log_path: poe_reader::get_poe_path(), + #[cfg(not(build_only))] + poe_client_log_path: None, + } } } + + diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5a9a955..48c75d6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,14 +1,19 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use std::sync::mpsc::Receiver; use std::{path::PathBuf, sync::Mutex}; use config::Config; use crossbeam::channel::Sender; +use notify::PollWatcher; use overlay::{Event, LockedOverlayData, Overlay}; use plan::Plan; +use poe_data::world_area::Matcher; use poe_data::world_area::WorldAreasMap; +use poe_reader::filter_func; +use poe_reader::{blocking_area_filtered_rx, entered_strings_receiver}; use simple_logger::SimpleLogger; use storage::Storage; use tauri::AppHandle; @@ -19,10 +24,12 @@ use tauri::SystemTray; use tauri::SystemTrayEvent; use tauri::SystemTrayMenu; use tauri::SystemTrayMenuItem; +use tauri::Window; mod config; mod overlay; mod plan; +mod poe_reader; mod storage; #[tauri::command] @@ -119,11 +126,19 @@ fn main() { let tx = Overlay::initialize( app.get_window("Overlay") .expect("Could not get main overlay window"), - "Untitled 1 - Mousepad", + "Path of Exile", ); app.manage(tx); app.manage(Mutex::new(Storage::default())); + if let Ok(storage) = app.state::>().lock() { + listen_for_zone_changes( + &storage.config.poe_client_log_path, + app.get_window("Overlay") + .expect("Could not get main overlay window"), + ); + } + app.get_window("Overlay") .expect("Could not get main overlay window") .open_devtools(); @@ -154,6 +169,44 @@ fn main() { }, _ => {} }) + .on_window_event(|event| match event.event() { + tauri::WindowEvent::CloseRequested { api, .. } => { + event.window().hide().ok(); + api.prevent_close(); + } + _ => {} + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } + +fn listen_for_zone_changes(poe_client_log_path: &Option, window: Window) { + if let Some((enter_area_receiver, _watcher)) = receiver_from_path(poe_client_log_path) { + log::info!("got receiver!"); + std::thread::spawn(move || { + let world_areas: WorldAreasMap = load_world_areas(); + + for area in blocking_area_filtered_rx(&enter_area_receiver) { + log::info!("Got area: {area}"); + + if let Some(area) = filter_func(area) { + log::info!("Filtered area: {area}"); + if let Some(entered) = world_areas.get(&area) { + log::info!("Entered: {entered:?}"); + window.emit_to("Overlay", "entered", &entered.named_id).ok(); + } + } + } + }); + } +} + +fn receiver_from_path(path: &Option) -> Option<(Receiver, PollWatcher)> { + if let Some(poe_path) = path { + if poe_path.exists() { + return Some(entered_strings_receiver(poe_path.clone())); + } + } + + None +} diff --git a/src-tauri/src/poe_reader.rs b/src-tauri/src/poe_reader.rs new file mode 100644 index 0000000..50535d1 --- /dev/null +++ b/src-tauri/src/poe_reader.rs @@ -0,0 +1,189 @@ +use std::{ + fs::{File, OpenOptions}, + io::{Read, Seek, SeekFrom}, + path::PathBuf, + sync::mpsc::{channel, Receiver, Sender}, + time::Duration, +}; + +use notify::{event, Config, PollWatcher, RecursiveMode, Watcher, Event}; +use regex::Regex; + +#[cfg(target_os = "windows")] +use std::os::windows::fs::OpenOptionsExt; + +struct LogFileReader { + tx: Sender, + file_path: PathBuf, + contents: Vec, + position: u64, +} + +impl Drop for LogFileReader { + fn drop(&mut self) { + log::debug!("LogFileReader dropped"); + } +} + +#[cfg(target_os = "windows")] +fn share_mode(options: &mut OpenOptions) { + options.share_mode(0x00000004 | 0x00000001 | 0x00000002); +} + +#[cfg(not(target_os = "windows"))] +fn share_mode(options: &mut OpenOptions) {} + +impl LogFileReader { + fn new(file: &PathBuf, tx: Sender) -> Result { + let mut lfr = LogFileReader { + tx, + file_path: file.to_path_buf(), + contents: Vec::new(), + position: 0, + }; + + let mut options = OpenOptions::new(); + + options.read(true).write(false); + + share_mode(&mut options); + + let mut file = options.open(file)?; + lfr.position = file + .seek(SeekFrom::End(0)) + .expect("Could not start at end of file"); + + Ok(lfr) + } + + fn on_write_handler(&mut self) { + self.contents.truncate(0); + + let mut options = OpenOptions::new(); + + options.read(true).write(false); + + share_mode(&mut options); + + let mut file = options + .open(&self.file_path) + .expect("Could not re-open file"); + + let _ = file.seek(SeekFrom::Start(self.position)); + + self.position += file.read_to_end(&mut self.contents).unwrap() as u64; + + for line in String::from_utf8_lossy(&self.contents).lines() { + let line = line.to_string(); + if !line.is_empty() { + self.tx.send(line).expect("Could not send line to receiver"); + } + } + + // do_process(contents) + } +} + +pub fn entered_strings_receiver(path: PathBuf) -> (Receiver, PollWatcher) { + let (tx, rx) = channel(); + let mut lfr = LogFileReader::new(&path, tx).unwrap(); + + + + (rx, watch_file_for_writes(path, Box::new(move || lfr.on_write_handler()))) +} + + +const POE_STEAM_APP_ID: u32 = 238960; +pub fn get_poe_path() -> Option { + if let Some(mut steam_dir) = steamlocate::SteamDir::locate() { + if let Some(app) = steam_dir.app(&POE_STEAM_APP_ID) { + let mut path = app.path.clone(); + path.push("logs"); + path.push("Client.txt"); + return Some(path); + } + } + + None +} + +#[allow(dead_code)] +fn entered_filtered_rx<'a>(rx: &'a Receiver) -> impl Iterator + 'a { + rx.iter().filter_map(|str| { + if str.contains("You have entered ") { + Some(str) + } else { + None + } + }) +} + +/// Returns an iterator of strings with the id +pub fn area_filtered_rx<'a>(rx: &'a Receiver) -> impl Iterator + 'a { + // Generating level 68 area "1_SideArea5_6" with seed 4103532853 + let reg = Regex::new("Generating level ([0-9]+) area \"(.+)\"").unwrap(); + let area_id_location = 2; + rx.try_iter().filter_map(move |str| { + let caps = reg.captures(&str); + if let Some(caps) = caps { + Some(caps[area_id_location].to_string()) + } else { + None + } + }) +} + +pub fn filter_func(str : String) -> Option { + let reg = Regex::new("Generating level ([0-9]+) area \"(.+)\"").unwrap(); + let area_id_location = 2; + + let caps = reg.captures(&str); + if let Some(caps) = caps { + Some(caps[area_id_location].to_string()) + } else { + None + } +} + +pub fn blocking_area_filtered_rx<'a>(rx: &'a Receiver) -> impl Iterator + 'a { + // Generating level 68 area "1_SideArea5_6" with seed 4103532853 + + rx.iter() +} + +fn watch_file_for_writes(path: PathBuf, mut on_write_handler: Box) -> PollWatcher + +{ + + // Create a watcher object, delivering debounced events. + // The notification back-end is selected based on the platform. + let config = Config::default().with_poll_interval(Duration::from_millis(150)); + let mut watcher = PollWatcher::new( + move |e: Result| match e { + Ok(e2) => { + match e2.kind { + + notify::EventKind::Modify(_) => { + on_write_handler() + }, + _ => (), + + } + }, + + e => log::error!("Error watching for file something something: {e:?}"), + }, + config, + ) + .unwrap(); + + //let mut watcher = watcher(tx, Duration::from_millis(100)).unwrap(); + + // Add a path to be watched. All files and directories at that path and + // below will be monitored for changes. + watcher + .watch(path.as_path(), RecursiveMode::NonRecursive) + .unwrap(); + watcher +} diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs index 7cf5d4e..ae8387f 100644 --- a/src-tauri/src/storage.rs +++ b/src-tauri/src/storage.rs @@ -129,3 +129,4 @@ impl Storage { return vec![]; } } + diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6ea9912..1c0aa30 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -26,7 +26,8 @@ "active": true, "icon": [ "icons/32x32.png", - "icons/NothingIcon.png" + "icons/NothingIcon.png", + "icons/icon.ico" ], "identifier": "me.isark.poe.Nothing", "targets": "all" diff --git a/src/app/app.component.html b/src/app/app.component.html index 9109b1b..bd99f66 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,5 +1,5 @@
- + Settings @@ -14,8 +14,7 @@
- - +
diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 7860465..627b41c 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -37,7 +37,15 @@ plan-display { } .content { + height: 100%; display: flex; flex-direction: column; - justify-content: center; +} + +mat-tab-group { + height: 100%; +} + +settings { + height: 100%; } \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e47c9c4..9617dfb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,7 +23,6 @@ export function initializeApp(configService: ConfigService) { @NgModule({ declarations: [ AppComponent, - RecordKeyChord, ], imports: [ BrowserModule, diff --git a/src/app/directives/record-key-chord.directive.ts b/src/app/directives/record-key-chord.directive.ts index 485fdbf..8b77a94 100644 --- a/src/app/directives/record-key-chord.directive.ts +++ b/src/app/directives/record-key-chord.directive.ts @@ -1,25 +1,23 @@ -import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; +import { Directive, EventEmitter, HostListener, Output } from '@angular/core'; @Directive({ - selector: '[RecordKeyChord]' + selector: '[RecordKeyChord]', + standalone: true }) export class RecordKeyChord { - @Output() finishedKeyChord = new EventEmitter; + @Output() chord = new EventEmitter; chordStack: string[] = []; @HostListener('window:keydown', ['$event']) handleKeyDown(event: KeyboardEvent) { - console.log("keydown:", event); this.chordStack.push(event.key); - + this.chord.next(this.chordStack); } - + @HostListener('window:keyup', ['$event']) handleKeyUp(event: KeyboardEvent) { - console.log("keyup:", event); - this.finishedKeyChord.next(this.chordStack); this.chordStack = []; } diff --git a/src/app/plan-display/plan-display.component.scss b/src/app/plan-display/plan-display.component.scss index d878431..c0dfbfb 100644 --- a/src/app/plan-display/plan-display.component.scss +++ b/src/app/plan-display/plan-display.component.scss @@ -78,4 +78,12 @@ notes { width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.5); +} + +.material-symbols-outlined { + font-variation-settings: + 'FILL' 0, + 'wght' 400, + 'GRAD' 0, + 'opsz' 48 } \ No newline at end of file diff --git a/src/app/plan-display/plan-display.component.ts b/src/app/plan-display/plan-display.component.ts index e777d80..3544554 100644 --- a/src/app/plan-display/plan-display.component.ts +++ b/src/app/plan-display/plan-display.component.ts @@ -16,6 +16,7 @@ import { from } from 'rxjs'; import { open } from '@tauri-apps/api/dialog'; import { OverlayRef } from '@angular/cdk/overlay'; import { OverlayService } from '../services/overlay.service'; +import { appWindow } from '@tauri-apps/api/window'; @Component({ selector: 'plan-display', @@ -42,6 +43,19 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { // } window.addEventListener("resize", this.windowInitHandler.bind(this)); overlayService.setInteractable(); + + appWindow.listen("entered", (entered) => { + console.log("entered", entered); + if (this.planService.currentPlan) { + const current = this.planService.currentPlan.current; + const length = this.planService.currentPlan.plan.length; + if (current + 1 < length) { + if (entered.payload === this.planService.currentPlan.plan[current + 1]) { + this.next(); + } + } + } + }); } windowInitHandler() { @@ -129,8 +143,8 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit { this.currentSlides = carousel; if (this.currentSlides) { - this.shortcut.register("F7", this.prev.bind(this)); - this.shortcut.register("F8", this.next.bind(this)); + this.shortcut.register(this.configService.config.prev, this.prev.bind(this)); + this.shortcut.register(this.configService.config.next, this.next.bind(this)); } } diff --git a/src/app/services/overlay.service.ts b/src/app/services/overlay.service.ts index a03f0c4..f103013 100644 --- a/src/app/services/overlay.service.ts +++ b/src/app/services/overlay.service.ts @@ -4,7 +4,7 @@ import { EventsService } from './events.service'; import { Event } from "@tauri-apps/api/event"; import { invoke } from '@tauri-apps/api'; import { ConfigService } from './config.service'; -import { WorldAreaService } from './world-area.service'; +import { appWindow } from '@tauri-apps/api/window'; class StateEvent { Visible?: any; @@ -21,13 +21,15 @@ export class OverlayService { visible: boolean = false; constructor(private shortcuts: ShortcutService, private events: EventsService, private configService: ConfigService) { - this.shortcuts.register(this.configService.config.toggleOverlay, this.onToggleOverlay.bind(this)); - this.events.listen("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this)); + if (appWindow.label == "Overlay") { + this.shortcuts.register(this.configService.config.toggleOverlay, this.onToggleOverlay.bind(this)); + this.events.listen("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this)); + } } onOverlayStateChange(event: Event) { - this.interactable = event.payload.Interactable != null; - if (event.payload.Hidden) {this.visible = false} else {this.visible = true}; + this.interactable = event.payload.Interactable != null; + if (event.payload.Hidden) { this.visible = false } else { this.visible = true }; } setInteractable() { @@ -53,5 +55,6 @@ export class OverlayService { }, ''); this.shortcuts.rebind(chord, this.onToggleOverlay); + this.configService.config.toggleOverlay = chord; } } diff --git a/src/app/services/shortcut.service.ts b/src/app/services/shortcut.service.ts index d607b84..cbe4ef8 100644 --- a/src/app/services/shortcut.service.ts +++ b/src/app/services/shortcut.service.ts @@ -38,4 +38,9 @@ export class ShortcutService { } }); } + + rebind_from_to(previousShortcut: string, nextShortcut: string) { + const oldHandler = [...this.bound.entries()].find((entry: [ShortcutHandler, string]) => entry[1] === previousShortcut)?.[0]; + this.rebind(nextShortcut, oldHandler!); + } } diff --git a/src/app/settings/bind-dialog.html b/src/app/settings/bind-dialog.html new file mode 100644 index 0000000..2ca3a4c --- /dev/null +++ b/src/app/settings/bind-dialog.html @@ -0,0 +1,12 @@ +
+

What's your favorite animal?

+ + data.title + + +
+ +
+ + +
\ No newline at end of file diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index a8e793f..e6b71fb 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -6,8 +6,10 @@ backdrop color
- - Auto hide on unfocus + Auto hide on unfocus
- + + + + \ No newline at end of file diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index e397eb1..a8aa526 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -1,14 +1,23 @@ -import { Component, Input, NgZone } from '@angular/core'; +import { Component, Inject, Input, NgZone } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { ConfigService } from '../services/config.service'; import { Color, ColorPickerComponent } from '../color-picker/color-picker.component'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; +import { OverlayService } from '../services/overlay.service'; +import { PlanService } from '../services/plan.service'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { ShortcutService } from '../services/shortcut.service'; +import { RecordKeyChord } from '../directives/record-key-chord.directive'; +import { Config } from '../models/generated/Config'; @Component({ selector: 'settings', standalone: true, - imports: [CommonModule, FormsModule, ColorPickerComponent, MatSlideToggleModule], + imports: [CommonModule, FormsModule, ColorPickerComponent, MatSlideToggleModule, MatDialogModule], templateUrl: './settings.component.html', styleUrls: ['./settings.component.scss'] }) @@ -18,7 +27,11 @@ export class SettingsComponent { constructor( public configService: ConfigService, - private zone: NgZone + private zone: NgZone, + private overlayService: OverlayService, + private planService: PlanService, + public dialog: MatDialog, + private shortcut: ShortcutService ) { } @@ -31,4 +44,74 @@ export class SettingsComponent { onBackdropColorChange(color: Color) { this.zone.run(() => this.configService.config.backdropBg = color.rgbaString); } + + rebindOverlayToggle() { + const dialogRef = this.dialog.open(BindDialog, { + data: {chord: this.configService.config.toggleOverlay}, + }); + + dialogRef.afterClosed().subscribe(result => { + if(result) { + this.shortcut.rebind_from_to(this.configService.config.toggleOverlay, result); + this.configService.config.toggleOverlay = result; + } + }); + } + + rebindPreviousZone() { + const dialogRef = this.dialog.open(BindDialog, { + data: {chord: this.configService.config.prev}, + }); + + dialogRef.afterClosed().subscribe(result => { + if(result) { + this.shortcut.rebind_from_to(this.configService.config.prev, result); + this.configService.config.prev = result; + } + }); + } + + rebindNextZone() { + const dialogRef = this.dialog.open(BindDialog, { + data: {chord: this.configService.config.next}, + }); + + dialogRef.afterClosed().subscribe(result => { + if(result) { + this.shortcut.rebind_from_to(this.configService.config.next, result); + this.configService.config.next = result; + } + }); + } + +} + +interface BindData { + chord: string; } + +@Component({ + selector: 'bind-dialog', + templateUrl: 'bind-dialog.html', + standalone: true, + imports: [MatDialogModule, MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule, RecordKeyChord], +}) +export class BindDialog { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: BindData, + ) {} + + onNoClick(): void { + this.dialogRef.close(); + } + + onChord(chord: string[]) { + this.data.chord = chord.reduce((acc, curr) => { + if (acc === '') return curr; + + return acc.concat('+').concat(curr); + }, ''); + } +} \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 96dbd60..90fe8e0 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -5,7 +5,7 @@ $my-primary: mat.define-palette(mat.$indigo-palette, 700); $my-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); -$my-theme: mat.define-dark-theme(( +$my-theme: mat.define-light-theme(( color: ( primary: $my-primary, accent: $my-accent, @@ -36,6 +36,11 @@ body { div.picker_wrapper.popup { z-index: 10000; + overflow: visible; +} + +.mat-mdc-tab-body-wrapper{ + flex-grow: 1; } // Emit theme-dependent styles for common features used across multiple components. diff --git a/yarn.lock b/yarn.lock index d2c9bdf..ca4d924 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4212,9 +4212,9 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -"fuzzr@github:isark2/fuzzr#v0.3.1": +"fuzzr@https://github.com/isark2/fuzzr#v0.3.1": version "0.0.0" - resolved "git+ssh://git@github.com/isark2/fuzzr.git#8709d405ae8cc012dc681b46809878d0e793c412" + resolved "https://github.com/isark2/fuzzr#8709d405ae8cc012dc681b46809878d0e793c412" gauge@^4.0.3: version "4.0.4"