From bd8cfb573c1474184c217289dbe61b984a005bd3 Mon Sep 17 00:00:00 2001 From: isark Date: Sun, 30 Jul 2023 19:08:19 +0200 Subject: [PATCH] Moved some overlay handling to service. Fixed some issue with configs, implemented config storage, some other cleanup and fixes --- package.json | 4 +- src-tauri/Cargo.lock | 28 ++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/build.rs | 1 - src-tauri/src/config.rs | 12 ++- src-tauri/src/main.rs | 42 +++++++-- src-tauri/src/plan.rs | 14 +++ src-tauri/src/storage.rs | 86 +++++++++++++++++++ src/app/app.component.html | 6 +- src/app/app.component.ts | 55 +++--------- src/app/app.module.ts | 24 +++++- .../color-picker/color-picker.component.html | 3 + .../color-picker/color-picker.component.scss | 0 .../color-picker.component.spec.ts | 21 +++++ .../color-picker/color-picker.component.ts | 50 +++++++++++ src/app/models/plan.ts | 13 ++- .../plan-display/plan-display.component.html | 17 +++- .../plan-display/plan-display.component.scss | 4 + .../plan-display/plan-display.component.ts | 54 ++++++++++-- src/app/plan-display/plan-display.module.ts | 3 +- src/app/services/config.service.ts | 49 ++++++++++- src/app/services/overlay.service.ts | 53 ++++++++++++ src/app/services/shortcut.service.ts | 5 +- src/styles.scss | 32 ++++++- yarn.lock | 31 +++++-- 25 files changed, 514 insertions(+), 94 deletions(-) create mode 100644 src-tauri/src/storage.rs create mode 100644 src/app/color-picker/color-picker.component.html create mode 100644 src/app/color-picker/color-picker.component.scss create mode 100644 src/app/color-picker/color-picker.component.spec.ts create mode 100644 src/app/color-picker/color-picker.component.ts create mode 100644 src/app/services/overlay.service.ts diff --git a/package.json b/package.json index f803bef..f1b0229 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "^16.1.4", + "@angular/cdk": "^16.1.6", "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", "@angular/forms": "^16.1.4", - "@angular/material": "^16.1.5", + "@angular/material": "^16.1.4", "@angular/platform-browser": "^16.1.4", "@angular/platform-browser-dynamic": "^16.1.4", "@tauri-apps/api": "^1.2.0", @@ -24,6 +25,7 @@ "ngx-moveable": "^0.48.1", "rxjs": "~7.8.1", "tslib": "^2.6.0", + "vanilla-picker": "^2.12.1", "zone.js": "^0.13.1" }, "devDependencies": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b92c6ff..2e23468 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -773,6 +773,15 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -783,6 +792,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2037,6 +2058,7 @@ version = "0.0.0" dependencies = [ "Underlayer", "crossbeam", + "directories", "log", "poe_data", "raw-window-handle", @@ -2251,6 +2273,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8307b05..9fb5650 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,7 @@ statig = "0.3.0" crossbeam = "0.8.2" poe_data = { path = "poe_data" } +directories = "5.0.1" [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/src-tauri/src/build.rs b/src-tauri/src/build.rs index dc2cfa5..8a7f7b7 100644 --- a/src-tauri/src/build.rs +++ b/src-tauri/src/build.rs @@ -12,7 +12,6 @@ fn main() { //Let's go with uppercase names, allows automatic import to work... config::Rect::export_to("../src/app/models/generated/Rect.ts").expect("Could not generate config struct"); config::Config::export_to("../src/app/models/generated/Config.ts").expect("Could not generate config struct"); - println!("cargo:rerun-if-changed=src/config.rs"); println!("cargo:rerun-if-changed=../src/app/models/generated/Rect.ts"); println!("cargo:rerun-if-changed=../src/app/models/generated/Config.ts"); diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index e8cb34c..19efef8 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -5,7 +5,8 @@ use ts_rs::TS; #[cfg_attr(not(build_only), derive(TS))] #[cfg_attr(not(build_only), ts(rename_all = "camelCase"))] -#[derive(Serialize, Deserialize, Debug, Default)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +#[serde(rename_all = "camelCase")] pub struct Rect { x: i32, y: i32, @@ -13,19 +14,22 @@ pub struct Rect { height: u32, } - - #[cfg_attr(not(build_only), derive(TS))] #[cfg_attr(not(build_only), ts(rename_all = "camelCase"))] -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] pub struct Config { initial_plan_window_position: Rect, + hide_on_unfocus: bool, + toggle_overlay: String, } impl Default for Config { fn default() -> Self { Self { initial_plan_window_position: Default::default(), + hide_on_unfocus: true, + toggle_overlay: "F6".into(), } } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f1b3178..0e52d58 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,19 +1,22 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use std::collections::HashMap; +use std::{collections::HashMap, sync::Mutex}; +use config::Config; use crossbeam::channel::Sender; use overlay::{Event, LockedOverlayData, Overlay}; use poe_data::world_area::WorldArea; use simple_logger::SimpleLogger; +use storage::Storage; // use overlay::{Overlay, Manager}; use tauri::Manager; +mod config; mod overlay; mod plan; -mod config; +mod storage; #[tauri::command] fn set_interactable(interactable: bool, state: tauri::State>) { @@ -36,7 +39,27 @@ fn set_auto_hide(auto_hide: bool, state: tauri::State) { #[tauri::command] fn load_world_areas() -> HashMap { log::info!("Loading world areas"); - poe_data::world_area::load_world_areas_map(include_str!("../../data/processed_world_areas.json")) + poe_data::world_area::load_world_areas_map(include_str!( + "../../data/processed_world_areas.json" + )) +} + +#[tauri::command] +fn load_config(state: tauri::State>) -> Config { + if let Ok(storage) = state.lock() { + log::info!("Loaded config from storage: {:?}", storage.config); + return storage.config.clone(); + } + panic!("ERROR!") +} + +#[tauri::command] +fn set_config(config: Config, state: tauri::State>) { + log::info!("Saved config: {:?}", config); + if let Ok(mut storage) = state.lock() { + storage.config = config; + storage.save(); + } } fn main() { @@ -54,17 +77,22 @@ fn main() { "Untitled 1 - Mousepad", ); app.manage(tx); + app.manage(Mutex::new(Storage::default())); + - // app.get_window("Overlay") - // .expect("Could not get main overlay window").open_devtools(); + app.get_window("Overlay") + .expect("Could not get main overlay window") + .open_devtools(); Ok(()) }) .invoke_handler(tauri::generate_handler![ set_interactable, set_auto_hide, - load_world_areas + load_world_areas, + load_config, + set_config, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); -} \ No newline at end of file +} diff --git a/src-tauri/src/plan.rs b/src-tauri/src/plan.rs index e69de29..b129a56 100644 --- a/src-tauri/src/plan.rs +++ b/src-tauri/src/plan.rs @@ -0,0 +1,14 @@ +use poe_data::world_area::WorldArea; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Plan { + plan: Vec, + current: usize, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PlanElement { + area: WorldArea, + notes: String, +} \ No newline at end of file diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs new file mode 100644 index 0000000..82f79e0 --- /dev/null +++ b/src-tauri/src/storage.rs @@ -0,0 +1,86 @@ +use std::error::Error; + +use directories::ProjectDirs; +use serde::{Deserialize, Serialize}; + +use crate::config::Config; +use crate::plan::Plan; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Storage { + pub config: Config, + #[serde(skip)] + pub plan: Option, + pub plan_name: Option, +} + +const QUALIFIER: &'static str = "me"; +const ORGANIZATION: &'static str = "isark.poe"; +const APPLICATION: &'static str = "Nothing"; +const CONFIG_FILE: &'static str = "configuration.json"; +const SAVED_PLANS: &'static str = "plans"; + +fn proj_dir() -> Option { + ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION) +} + +impl Default for Storage { + fn default() -> Self { + let config = Self::load(); + match config { + Some(config) => config, + None => Self { + config: Default::default(), + plan: Default::default(), + plan_name: None, + }, + } + } +} + +impl Storage { + fn load() -> Option { + let mut storage: Option = serde_json::from_str( + &std::fs::read_to_string(proj_dir()?.data_dir().with_file_name(CONFIG_FILE)).ok()?, + ) + .ok(); + + if let Some(storage) = &mut storage { + if let Some(plan_name) = &storage.plan_name { + storage.plan = Self::load_plan(plan_name); + } + } + + log::info!("Loaded storage: {:?}", storage); + + storage + } + + fn load_plan>(file_name: T) -> Option { + serde_json::from_str( + &std::fs::read_to_string( + proj_dir()? + .data_dir() + .join(SAVED_PLANS) + .with_file_name(file_name.into()), + ) + .ok()?, + ) + .ok() + } + + pub fn save(&self) { + if let Ok(content) = serde_json::to_string_pretty(&self) { + if let Some(dir) = proj_dir() { + match std::fs::write(dir.data_dir().with_file_name(CONFIG_FILE), content) { + Ok(_) => { + if let Some(c) = dir.data_dir().with_file_name(CONFIG_FILE).to_str() { + log::info!("Saved config to {}", c) + } + } + Err(_) => log::error!("Could not write config"), + } + } + } + } +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 698da26..3bb6e48 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,4 @@ - + -matched init \ No newline at end of file +matched init + +Click me for color picker! \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index cfea586..e033c93 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,67 +2,32 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { invoke } from "@tauri-apps/api/tauri"; import { ShortcutService } from "./services/shortcut.service"; import { EventsService } from "./services/events.service"; -import { Event } from "@tauri-apps/api/event"; import { WorldAreaService } from "./services/world-area.service"; - -class StateEvent { - Visible?: any; - Interactable?: any; - Hidden?: any; -} +import { Color } from "./color-picker/color-picker.component"; +import { OverlayService } from "./services/overlay.service"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.scss"], }) -export class AppComponent implements OnInit, OnDestroy { +export class AppComponent implements OnInit { auto_hide: boolean = true; interactable: boolean = false; isBinding: boolean = false; + planColor?: Color; - constructor(public worldAreas: WorldAreaService, private shortcuts: ShortcutService, private events: EventsService) { } + constructor( + private overlayService: OverlayService, + public worldAreas: WorldAreaService, + private shortcuts: ShortcutService, + private events: EventsService + ) { } - ngOnDestroy(): void { - this.shortcuts.unregister(this.onToggleOverlay); - } ngOnInit(): void { - invoke("set_auto_hide", { auto_hide: this.auto_hide }).then(); - console.log("Loading world areas"); - this.shortcuts.register('F6', this.onToggleOverlay); - this.events.listen("OverlayStateChange").subscribe(this.onOverlayStateChange) - } - onOverlayStateChange(event: Event) { - this.interactable = event.payload.Interactable != null; } - onToggleOverlay = () => { - console.log(this); - if (this.interactable) { - this.interactable = false; - } else { - this.interactable = true; - } - invoke("set_interactable", { interactable: this.interactable }).then(); - } - - toggleAutoHide() { - invoke("set_auto_hide", { autoHide: this.auto_hide }).then(); - } - - onBindFinish(keys: string[]) { - this.isBinding = false; - let chord = keys.reduce((acc, curr) => { - if (acc === '') return curr; - - return acc.concat('+').concat(curr); - }, ''); - console.log(chord); - - this.shortcuts.rebind(chord, this.onToggleOverlay); - - } } \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f30ad73..b852c89 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,22 +1,40 @@ -import { NgModule } from "@angular/core"; +import { APP_INITIALIZER, NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { AppComponent } from "./app.component"; import { FormsModule } from "@angular/forms"; import { RecordKeyChord } from "./directives/record-key-chord.directive"; import { PlanDisplayModule } from "./plan-display/plan-display.module"; +import { ConfigService } from "./services/config.service"; +import { ColorPickerComponent } from './color-picker/color-picker.component'; +import { MatButtonModule } from "@angular/material/button"; + +export function initializeApp(configService: ConfigService) { + return (): Promise => { + return configService.init(); + }; +} @NgModule({ declarations: [ AppComponent, - RecordKeyChord + RecordKeyChord, ColorPickerComponent ], imports: [ BrowserModule, FormsModule, PlanDisplayModule, + MatButtonModule, + ], + providers: [ + { + // Makes sure we can get the config initialized ASAP as we might need it in lots of places really early. + provide: APP_INITIALIZER, + useFactory: initializeApp, + deps: [ConfigService], + multi: true + } ], - providers: [], bootstrap: [AppComponent], }) export class AppModule { } diff --git a/src/app/color-picker/color-picker.component.html b/src/app/color-picker/color-picker.component.html new file mode 100644 index 0000000..757429d --- /dev/null +++ b/src/app/color-picker/color-picker.component.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/color-picker/color-picker.component.scss b/src/app/color-picker/color-picker.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/color-picker/color-picker.component.spec.ts b/src/app/color-picker/color-picker.component.spec.ts new file mode 100644 index 0000000..370d7c8 --- /dev/null +++ b/src/app/color-picker/color-picker.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ColorPickerComponent } from './color-picker.component'; + +describe('ColorPickerComponent', () => { + let component: ColorPickerComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ColorPickerComponent] + }); + fixture = TestBed.createComponent(ColorPickerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/color-picker/color-picker.component.ts b/src/app/color-picker/color-picker.component.ts new file mode 100644 index 0000000..6b7e76c --- /dev/null +++ b/src/app/color-picker/color-picker.component.ts @@ -0,0 +1,50 @@ +import { AfterContentInit, AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import Picker from 'vanilla-picker'; +import { MatButtonModule } from '@angular/material/button'; +import { Subject, delay, startWith } from 'rxjs'; + +export interface Color { + rgba: number[]; + hsla: number[]; + rgbString: string; + rgbaString: string; + hslString: string; + hslaString: string; + hex: string; +} + +@Component({ + selector: 'color-picker', + templateUrl: './color-picker.component.html', + styleUrls: ['./color-picker.component.scss'], +}) +export class ColorPickerComponent implements AfterViewInit { + + @ViewChild('clickColorPickerElement') + clickColorPickerElement?: ElementRef; + + @Output('color') + outColor = new EventEmitter; + + @Input('initialColor') + initialColor?: string; + + loaded: Subject = new Subject(); + + picker?: Picker; + showPicker: boolean = false; + + constructor() { + this.loaded.pipe(delay(0)).subscribe(() => { + this.picker = new Picker({ + onChange: this.outColor.next.bind(this.outColor), + parent: this.clickColorPickerElement?.nativeElement, + color: this.initialColor + }); + }) + } + + ngAfterViewInit(): void { + this.loaded.next(); + } +} diff --git a/src/app/models/plan.ts b/src/app/models/plan.ts index 4d134ec..9239efe 100644 --- a/src/app/models/plan.ts +++ b/src/app/models/plan.ts @@ -1,14 +1,11 @@ -interface Area { - Act: number; - Connections_WorldAreasKeys: number[]; - HasWaypoint: boolean; - IsTown: boolean; - Name: string; - _rid: number; +import { WorldArea } from "./world-area"; + +export class Plan { + } interface PlanElement { - area: Area; + area: WorldArea; note: string; prev: boolean; current: boolean; diff --git a/src/app/plan-display/plan-display.component.html b/src/app/plan-display/plan-display.component.html index 51a9669..9604303 100644 --- a/src/app/plan-display/plan-display.component.html +++ b/src/app/plan-display/plan-display.component.html @@ -1,3 +1,14 @@ -
Hello!
- \ No newline at end of file +
+ + Has Plan! + +
+ \ No newline at end of file diff --git a/src/app/plan-display/plan-display.component.scss b/src/app/plan-display/plan-display.component.scss index 9e0a214..d610e61 100644 --- a/src/app/plan-display/plan-display.component.scss +++ b/src/app/plan-display/plan-display.component.scss @@ -6,4 +6,8 @@ .target { min-width: 50px; min-height: 50px; + display: flex; + & > * { + flex: 1 1 auto; + } } \ 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 1cec293..a851363 100644 --- a/src/app/plan-display/plan-display.component.ts +++ b/src/app/plan-display/plan-display.component.ts @@ -1,15 +1,25 @@ -import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { NgxMoveableComponent, OnResize } from 'ngx-moveable'; +import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core'; +import { NgxMoveableComponent, OnDragEnd, OnResize, OnResizeEnd } from 'ngx-moveable'; import { OnDrag } from 'ngx-moveable'; import { ConfigService } from '../services/config.service'; +import { Rect } from '../models/generated/Rect'; +import { Plan } from '../models/plan'; +import { Color } from '../color-picker/color-picker.component'; @Component({ selector: 'plan-display', templateUrl: './plan-display.component.html', styleUrls: ['./plan-display.component.scss'] }) -export class PlanDisplayComponent implements OnInit { +export class PlanDisplayComponent implements OnInit, AfterViewInit { draggable: boolean = true; + initialized: boolean = false; + + @Input() + plan: boolean = false; + + @Input() + backgroundColor?: Color; @ViewChild("moveableRef") moveableRef!: NgxMoveableComponent; @@ -20,6 +30,15 @@ export class PlanDisplayComponent implements OnInit { } + ngAfterViewInit(): void { + let init = this.configService.config.initialPlanWindowPosition; + this.setPosition(init.x, init.y); + this.setArea(init.width, init.height); + this.initialized = true; + console.log("init: ", init); + console.log("cfg at init: ", this.configService.config); + } + setPosition(x: number, y: number) { this.moveableRef.request("draggable", { x, @@ -29,16 +48,29 @@ export class PlanDisplayComponent implements OnInit { setArea(width: number, height: number) { this.moveableRef.request("resizable", { - offsetWidth: 500, - offsetHeight: 500, + offsetWidth: width, + offsetHeight: height, }, true); } + toRect(): Rect { + let rectInfo = this.moveableRef.getRect(); + return { + x: rectInfo.left, + y: rectInfo.top, + width: rectInfo.width, + height: rectInfo.height + }; + } + onDrag(e: OnDrag) { e.target.style.transform = e.transform; - // this.x = e.left; - // this.y = e.top; - // console.log(this.x); + } + + onDragEnd(e: OnDragEnd) { + if (this.initialized) { + this.configService.config.initialPlanWindowPosition = this.toRect(); + } } onResize(e: OnResize) { @@ -46,4 +78,10 @@ export class PlanDisplayComponent implements OnInit { e.target.style.height = `${e.height}px`; e.target.style.transform = e.drag.transform; } + + onResizeEnd(e: OnResizeEnd) { + if (this.initialized) { + this.configService.config.initialPlanWindowPosition = this.toRect(); + } + } } diff --git a/src/app/plan-display/plan-display.module.ts b/src/app/plan-display/plan-display.module.ts index 8ead1d4..042955a 100644 --- a/src/app/plan-display/plan-display.module.ts +++ b/src/app/plan-display/plan-display.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NgxMoveableComponent } from 'ngx-moveable'; import { PlanDisplayComponent } from './plan-display.component'; - +import {MatCardModule} from '@angular/material/card'; @NgModule({ @@ -12,6 +12,7 @@ import { PlanDisplayComponent } from './plan-display.component'; imports: [ CommonModule, NgxMoveableComponent, + MatCardModule, ], exports: [ PlanDisplayComponent diff --git a/src/app/services/config.service.ts b/src/app/services/config.service.ts index 5cd3d2b..7ce586f 100644 --- a/src/app/services/config.service.ts +++ b/src/app/services/config.service.ts @@ -1,12 +1,55 @@ import { Injectable } from '@angular/core'; +import { Config } from '../models/generated/Config'; +import { invoke } from '@tauri-apps/api'; +import { Subject, debounceTime } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ConfigService { - constructor() {} + //We're making sure this is loaded initially so let's not indicate it being nullable. + config!: Config; + private syncSubject: Subject = new Subject(); + + constructor() { + // Let's do some premature optimization! Now we can do lots of config changes quickly from FE without as many sync calls :D + this.syncSubject.pipe(debounceTime(500)).subscribe(() => this.underlyingSync()); + } + + init() { + return invoke('load_config').then(cfg => { + const _this = this; + + + // Mostly to wrap setters so we can run the (debounced) sync function on any config changes! + this.config = new Proxy(cfg, { + get(target: any, property) { + return target[property as keyof Config]; + }, + set(target: Config, property:any , value) { + // target[property as keyof Config] = value; + setConfig(target, property, value); + _this.sync(); + return true; + } + }); - public get lastPlanWindowPosition() { - return 0; + console.log("Loaded cfg:", this.config); + console.log("Loaded x:", this.config.initialPlanWindowPosition.x); + + }); + } + + sync() { + this.syncSubject.next(); + } + + private underlyingSync() { + console.log("saving config"); + invoke('set_config', { config: this.config }); } } + +function setConfig(target: Config, property: T, value: Config[T]) { + target[property] = value; +} \ No newline at end of file diff --git a/src/app/services/overlay.service.ts b/src/app/services/overlay.service.ts new file mode 100644 index 0000000..86cf08d --- /dev/null +++ b/src/app/services/overlay.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { ShortcutService } from './shortcut.service'; +import { EventsService } from './events.service'; +import { Event } from "@tauri-apps/api/event"; +import { invoke } from '@tauri-apps/api'; +import { ConfigService } from './config.service'; + +class StateEvent { + Visible?: any; + Interactable?: any; + Hidden?: any; +} + +@Injectable({ + providedIn: 'root' +}) +export class OverlayService { + interactable: boolean = false; + isBinding: boolean = false; + + + constructor(private shortcuts: ShortcutService, private events: EventsService, private configService: ConfigService) { + console.log("cfg", configService); + this.shortcuts.register(this.configService.config.toggleOverlay, this.onToggleOverlay); + this.events.listen("OverlayStateChange").subscribe(this.onOverlayStateChange); + } + + onOverlayStateChange(event: Event) { + this.interactable = event.payload.Interactable != null; + } + + onToggleOverlay = () => { + if (this.interactable) { + this.interactable = false; + } else { + this.interactable = true; + } + + invoke("set_interactable", { interactable: this.interactable }).then(); + } + + onBindToggleOverlayFinish(keys: string[]) { + this.isBinding = false; + let chord = keys.reduce((acc, curr) => { + if (acc === '') return curr; + + return acc.concat('+').concat(curr); + }, ''); + console.log(chord); + + this.shortcuts.rebind(chord, this.onToggleOverlay); + } +} diff --git a/src/app/services/shortcut.service.ts b/src/app/services/shortcut.service.ts index ef29bb7..22802c8 100644 --- a/src/app/services/shortcut.service.ts +++ b/src/app/services/shortcut.service.ts @@ -1,6 +1,7 @@ import { Injectable, NgZone } from '@angular/core'; import { ShortcutHandler, register, unregister } from '@tauri-apps/api/globalShortcut'; import { EMPTY, Observable, catchError, empty, from } from 'rxjs'; +import { ConfigService } from './config.service'; @Injectable({ providedIn: 'root' @@ -8,8 +9,10 @@ import { EMPTY, Observable, catchError, empty, from } from 'rxjs'; export class ShortcutService { bound: Map = new Map(); - constructor(private zone: NgZone) { } + constructor(private zone: NgZone) { + } register(shortcut: string, handler: ShortcutHandler) { + console.log("binding key:", shortcut, "to handler", handler); this.bound.set(handler, shortcut); return from(register(shortcut, (s) => { diff --git a/src/styles.scss b/src/styles.scss index 54ad422..7c68bdb 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,9 +1,30 @@ +@use '@angular/material' as mat; + +@include mat.core(); + +$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(( + color: ( + primary: $my-primary, + accent: $my-accent, + ), + density: 0, +)); + + + :root { color: #afafaf; - background-color: rgba($color: #000000, $alpha: 0); + background-color: rgba($color: #444, $alpha: 0); text-rendering: optimizeLegibility; } +.interactable { + background-color: rgba($color: #444, $alpha: 0.1); +} + html, body { height: 100vh; @@ -11,4 +32,11 @@ body { body { overflow: hidden; -} \ No newline at end of file +} + +div.picker_wrapper.popup { + z-index: 10000; +} + +// Emit theme-dependent styles for common features used across multiple components. +@include mat.all-component-themes($my-theme); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 08998f6..4ae4351 100644 --- a/yarn.lock +++ b/yarn.lock @@ -127,6 +127,15 @@ dependencies: tslib "^2.3.0" +"@angular/cdk@^16.1.6": + version "16.1.6" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-16.1.6.tgz#b85fb6b318dc58f506bd5cc5e060e2b6f153c541" + integrity sha512-ICwX3OyxmVotlhzlkvilvfZz32y9RXvUAaVtPsU1i20orgQBOMp+JGdP/vahLjTQRioUus834Wh6bu0KdHjCEg== + dependencies: + tslib "^2.3.0" + optionalDependencies: + parse5 "^7.1.2" + "@angular/cli@~16.1.4": version "16.1.4" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.1.4.tgz#b32de345b6fdb4e975f957b0f9cbcafdcf184f8a" @@ -193,10 +202,10 @@ dependencies: tslib "^2.3.0" -"@angular/material@^16.1.5": - version "16.1.5" - resolved "https://registry.yarnpkg.com/@angular/material/-/material-16.1.5.tgz#5ba16d5b8c3e3bbe1582fd70ac21df675a48a3a6" - integrity sha512-l11mH/WWBmfiBhrf4/0hCihhLxK4Ldu7+fP8zucHO3X2TiLlpsgJZpcYwJkZf0Ai0rDqIzqCVnks7L9jiuTGCQ== +"@angular/material@^16.1.4": + version "16.1.6" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-16.1.6.tgz#2edbde9c76973fe237108c5c990de61e8d05883d" + integrity sha512-PhfwqWN6cCiKCN2B1hUqrg3uwHC3ZwiCndJ/2CWEwEC824aOdf2b2ifTXZdtGbP3bFTkURkfvwfJVle4j/fbHw== dependencies: "@material/animation" "15.0.0-canary.b994146f6.0" "@material/auto-init" "15.0.0-canary.b994146f6.0" @@ -2337,6 +2346,11 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== +"@sphinxxxx/color-conversion@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz#03ecc29279e3c0c832f6185a5bfa3497858ac8ca" + integrity sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw== + "@tauri-apps/api@^1.2.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.4.0.tgz#b4013ca3d17b853f7df29fe14079ebb4d52dbffa" @@ -5693,7 +5707,7 @@ parse5-sax-parser@^7.0.0: dependencies: parse5 "^7.0.0" -parse5@^7.0.0: +parse5@^7.0.0, parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== @@ -6914,6 +6928,13 @@ validate-npm-package-name@^5.0.0: dependencies: builtins "^5.0.0" +vanilla-picker@^2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/vanilla-picker/-/vanilla-picker-2.12.1.tgz#6e619eecf553891b8d2d042b745a23c91f19f34c" + integrity sha512-2qrEP9VYylKXbyzXKsbu2dferBTvqnlsr29XjHwFE+/MEp0VNj6oEUESLDtKZ7DWzGdSv1x/+ujqFZF+KsO3cg== + dependencies: + "@sphinxxxx/color-conversion" "^2.2.2" + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"