Added poe client log reader (currently seems broken), keybinding in settings, log file detection

merge-notes
isark 2 years ago
parent 640a2bfaba
commit 6f2f7f27fc

@ -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}

190
src-tauri/Cargo.lock generated

@ -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"

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -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<PathBuf>,
}
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,
}
}
}

@ -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::<Mutex<Storage>>().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<PathBuf>, 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<PathBuf>) -> Option<(Receiver<String>, PollWatcher)> {
if let Some(poe_path) = path {
if poe_path.exists() {
return Some(entered_strings_receiver(poe_path.clone()));
}
}
None
}

@ -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<String>,
file_path: PathBuf,
contents: Vec<u8>,
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<String>) -> Result<Self, std::io::Error> {
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<String>, 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<PathBuf> {
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<String>) -> impl Iterator<Item = String> + '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<String>) -> impl Iterator<Item = String> + '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<String> {
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<String>) -> impl Iterator<Item = String> + '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<dyn FnMut() + Send>) -> 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<Event, _>| 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
}

@ -129,3 +129,4 @@ impl Storage {
return vec![];
}
}

@ -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"

@ -1,5 +1,5 @@
<div *ngIf="!isOverlay" class="main-container standalone">
<mat-tab-group mat-stretch-tabs="false">
<mat-tab-group mat-stretch-tabs="false" class="tabs">
<mat-tab>
<ng-template mat-tab-label>
<span class="tab-label">Settings</span>
@ -14,8 +14,7 @@
</mat-tab-group>
</div>
<ng-container *ngIf="overlayService.interactable">
<ng-container *ngIf="isOverlay">
<div class="main-container overlay" *ngIf="isOverlay"
[ngStyle]="overlayService.interactable ? {'background-color' : configService.config.backdropBg} : {'background-color' : configService.config.backdropBg} ">
<plan-display [backgroundColor]="configService.config.planBg"></plan-display>

@ -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%;
}

@ -23,7 +23,6 @@ export function initializeApp(configService: ConfigService) {
@NgModule({
declarations: [
AppComponent,
RecordKeyChord,
],
imports: [
BrowserModule,

@ -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<string[]>;
@Output() chord = new EventEmitter<string[]>;
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 = [];
}

@ -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
}

@ -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));
}
}

@ -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<StateEvent>("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this));
if (appWindow.label == "Overlay") {
this.shortcuts.register(this.configService.config.toggleOverlay, this.onToggleOverlay.bind(this));
this.events.listen<StateEvent>("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this));
}
}
onOverlayStateChange(event: Event<StateEvent>) {
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;
}
}

@ -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!);
}
}

@ -0,0 +1,12 @@
<div mat-dialog-content>
<p>What's your favorite animal?</p>
<mat-form-field>
<mat-label>data.title</mat-label>
<input matInput [(ngModel)]="data.chord" readonly RecordKeyChord (chord)="onChord($event)">
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button mat-button [mat-dialog-close]="data.chord" cdkFocusInitial>Save</button>
</div>

@ -6,8 +6,10 @@
backdrop color</color-picker>
</div>
<div class="checkboxes">
<input id ="autohide" type="checkbox" > <label for="autohide">Auto hide on losing focus</label>
<mat-slide-toggle [(ngModel)]="configService.config.hideOnUnfocus">Auto hide on unfocus</mat-slide-toggle>
<mat-slide-toggle color="primary" [(ngModel)]="configService.config.hideOnUnfocus">Auto hide on unfocus</mat-slide-toggle>
</div>
<button mat-raised-button (click)="rebindOverlayToggle()">Overlay toggle shortcut: {{configService.config.toggleOverlay}}</button>
<button mat-raised-button (click)="rebindPreviousZone()">Previous zone shortcut: {{configService.config.prev}}</button>
<button mat-raised-button (click)="rebindNextZone()">Next zone shortcut: {{configService.config.next}}</button>
</div>

@ -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<BindDialog>,
@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);
}, '');
}
}

@ -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.

@ -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"

Loading…
Cancel
Save