Moved some overlay handling to service. Fixed some issue with configs, implemented config storage, some other cleanup and fixes

merge-notes
isark 2 years ago
parent 9fb81ca554
commit bd8cfb573c

@ -11,11 +11,12 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^16.1.4", "@angular/animations": "^16.1.4",
"@angular/cdk": "^16.1.6",
"@angular/common": "^16.1.4", "@angular/common": "^16.1.4",
"@angular/compiler": "^16.1.4", "@angular/compiler": "^16.1.4",
"@angular/core": "^16.1.4", "@angular/core": "^16.1.4",
"@angular/forms": "^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": "^16.1.4",
"@angular/platform-browser-dynamic": "^16.1.4", "@angular/platform-browser-dynamic": "^16.1.4",
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
@ -24,6 +25,7 @@
"ngx-moveable": "^0.48.1", "ngx-moveable": "^0.48.1",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
"tslib": "^2.6.0", "tslib": "^2.6.0",
"vanilla-picker": "^2.12.1",
"zone.js": "^0.13.1" "zone.js": "^0.13.1"
}, },
"devDependencies": { "devDependencies": {

28
src-tauri/Cargo.lock generated

@ -773,6 +773,15 @@ dependencies = [
"crypto-common", "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]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "2.0.0" version = "2.0.0"
@ -783,6 +792,18 @@ dependencies = [
"dirs-sys-next", "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]] [[package]]
name = "dirs-sys-next" name = "dirs-sys-next"
version = "0.1.2" version = "0.1.2"
@ -2037,6 +2058,7 @@ version = "0.0.0"
dependencies = [ dependencies = [
"Underlayer", "Underlayer",
"crossbeam", "crossbeam",
"directories",
"log", "log",
"poe_data", "poe_data",
"raw-window-handle", "raw-window-handle",
@ -2251,6 +2273,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "ordered-stream" name = "ordered-stream"
version = "0.2.0" version = "0.2.0"

@ -33,6 +33,7 @@ statig = "0.3.0"
crossbeam = "0.8.2" crossbeam = "0.8.2"
poe_data = { path = "poe_data" } poe_data = { path = "poe_data" }
directories = "5.0.1"
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem # this feature is used for production builds or when `devPath` points to the filesystem

@ -12,7 +12,6 @@ fn main() {
//Let's go with uppercase names, allows automatic import to work... //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::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"); 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/config.rs");
println!("cargo:rerun-if-changed=../src/app/models/generated/Rect.ts"); println!("cargo:rerun-if-changed=../src/app/models/generated/Rect.ts");
println!("cargo:rerun-if-changed=../src/app/models/generated/Config.ts"); println!("cargo:rerun-if-changed=../src/app/models/generated/Config.ts");

@ -5,7 +5,8 @@ use ts_rs::TS;
#[cfg_attr(not(build_only), derive(TS))] #[cfg_attr(not(build_only), derive(TS))]
#[cfg_attr(not(build_only), ts(rename_all = "camelCase"))] #[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 { pub struct Rect {
x: i32, x: i32,
y: i32, y: i32,
@ -13,19 +14,22 @@ pub struct Rect {
height: u32, height: u32,
} }
#[cfg_attr(not(build_only), derive(TS))] #[cfg_attr(not(build_only), derive(TS))]
#[cfg_attr(not(build_only), ts(rename_all = "camelCase"))] #[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 { pub struct Config {
initial_plan_window_position: Rect, initial_plan_window_position: Rect,
hide_on_unfocus: bool,
toggle_overlay: String,
} }
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
initial_plan_window_position: Default::default(), initial_plan_window_position: Default::default(),
hide_on_unfocus: true,
toggle_overlay: "F6".into(),
} }
} }
} }

@ -1,19 +1,22 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![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 crossbeam::channel::Sender;
use overlay::{Event, LockedOverlayData, Overlay}; use overlay::{Event, LockedOverlayData, Overlay};
use poe_data::world_area::WorldArea; use poe_data::world_area::WorldArea;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use storage::Storage;
// use overlay::{Overlay, Manager}; // use overlay::{Overlay, Manager};
use tauri::Manager; use tauri::Manager;
mod config;
mod overlay; mod overlay;
mod plan; mod plan;
mod config; mod storage;
#[tauri::command] #[tauri::command]
fn set_interactable(interactable: bool, state: tauri::State<Sender<Event>>) { fn set_interactable(interactable: bool, state: tauri::State<Sender<Event>>) {
@ -36,7 +39,27 @@ fn set_auto_hide(auto_hide: bool, state: tauri::State<LockedOverlayData>) {
#[tauri::command] #[tauri::command]
fn load_world_areas() -> HashMap<String, WorldArea> { fn load_world_areas() -> HashMap<String, WorldArea> {
log::info!("Loading world areas"); 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<Mutex<Storage>>) -> 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<Mutex<Storage>>) {
log::info!("Saved config: {:?}", config);
if let Ok(mut storage) = state.lock() {
storage.config = config;
storage.save();
}
} }
fn main() { fn main() {
@ -54,17 +77,22 @@ fn main() {
"Untitled 1 - Mousepad", "Untitled 1 - Mousepad",
); );
app.manage(tx); app.manage(tx);
app.manage(Mutex::new(Storage::default()));
// app.get_window("Overlay") app.get_window("Overlay")
// .expect("Could not get main overlay window").open_devtools(); .expect("Could not get main overlay window")
.open_devtools();
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
set_interactable, set_interactable,
set_auto_hide, set_auto_hide,
load_world_areas load_world_areas,
load_config,
set_config,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

@ -0,0 +1,14 @@
use poe_data::world_area::WorldArea;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Plan {
plan: Vec<PlanElement>,
current: usize,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PlanElement {
area: WorldArea,
notes: String,
}

@ -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<Plan>,
pub plan_name: Option<String>,
}
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> {
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<Self> {
let mut storage: Option<Storage> = 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<T: Into<String>>(file_name: T) -> Option<Plan> {
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"),
}
}
}
}
}

@ -1,4 +1,4 @@
<plan-display></plan-display> <plan-display [plan]="false" [backgroundColor]="planColor"></plan-display>
<!-- <div *ngIf="areas && areas.matcher && areas.world_areas"> <!-- <div *ngIf="areas && areas.matcher && areas.world_areas">
<ul> <ul>
@ -6,4 +6,6 @@
</ul> </ul>
</div> --> </div> -->
<span *ngIf="worldAreas.matcher">matched init</span> <span *ngIf="worldAreas.matcher">matched init</span>
<color-picker [initialColor]="'#00000010'" (color)="planColor = $event">Click me for color picker!</color-picker>

@ -2,67 +2,32 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { ShortcutService } from "./services/shortcut.service"; import { ShortcutService } from "./services/shortcut.service";
import { EventsService } from "./services/events.service"; import { EventsService } from "./services/events.service";
import { Event } from "@tauri-apps/api/event";
import { WorldAreaService } from "./services/world-area.service"; import { WorldAreaService } from "./services/world-area.service";
import { Color } from "./color-picker/color-picker.component";
class StateEvent { import { OverlayService } from "./services/overlay.service";
Visible?: any;
Interactable?: any;
Hidden?: any;
}
@Component({ @Component({
selector: "app-root", selector: "app-root",
templateUrl: "./app.component.html", templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"], styleUrls: ["./app.component.scss"],
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit {
auto_hide: boolean = true; auto_hide: boolean = true;
interactable: boolean = false; interactable: boolean = false;
isBinding: 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 { 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<StateEvent>("OverlayStateChange").subscribe(this.onOverlayStateChange)
}
onOverlayStateChange(event: Event<StateEvent>) {
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);
}
} }

@ -1,22 +1,40 @@
import { NgModule } from "@angular/core"; import { APP_INITIALIZER, NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser"; import { BrowserModule } from "@angular/platform-browser";
import { AppComponent } from "./app.component"; import { AppComponent } from "./app.component";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { RecordKeyChord } from "./directives/record-key-chord.directive"; import { RecordKeyChord } from "./directives/record-key-chord.directive";
import { PlanDisplayModule } from "./plan-display/plan-display.module"; 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<any> => {
return configService.init();
};
}
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
RecordKeyChord RecordKeyChord, ColorPickerComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
PlanDisplayModule, 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], bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule { }

@ -0,0 +1,3 @@
<span #clickColorPickerElement>
<button mat-raised-button color="primary"><ng-content></ng-content></button>
</span>

@ -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<ColorPickerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ColorPickerComponent]
});
fixture = TestBed.createComponent(ColorPickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -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<Color>;
@Input('initialColor')
initialColor?: string;
loaded: Subject<void> = new Subject<void>();
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();
}
}

@ -1,14 +1,11 @@
interface Area { import { WorldArea } from "./world-area";
Act: number;
Connections_WorldAreasKeys: number[]; export class Plan {
HasWaypoint: boolean;
IsTown: boolean;
Name: string;
_rid: number;
} }
interface PlanElement { interface PlanElement {
area: Area; area: WorldArea;
note: string; note: string;
prev: boolean; prev: boolean;
current: boolean; current: boolean;

@ -1,3 +1,14 @@
<div class="target" #targetRef>Hello!</div> <div class="target" [style.background-color]="backgroundColor ? backgroundColor.rgbaString : 'rgba(0, 0, 0, 0.1)'" #targetRef>
<ngx-moveable [target]="targetRef" [draggable]="draggable" [resizable]="true" (drag)="onDrag($event)" <ng-container *ngIf="plan">
(resize)="onResize($event)" #moveableRef></ngx-moveable> Has Plan!
</ng-container>
</div>
<ngx-moveable
[target]="targetRef"
[draggable]="draggable"
[resizable]="true"
(drag)="onDrag($event)"
(resize)="onResize($event)"
(dragEnd)="onDragEnd($event)"
(resizeEnd)="onResizeEnd($event)"
#moveableRef></ngx-moveable>

@ -6,4 +6,8 @@
.target { .target {
min-width: 50px; min-width: 50px;
min-height: 50px; min-height: 50px;
display: flex;
& > * {
flex: 1 1 auto;
}
} }

@ -1,15 +1,25 @@
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
import { NgxMoveableComponent, OnResize } from 'ngx-moveable'; import { NgxMoveableComponent, OnDragEnd, OnResize, OnResizeEnd } from 'ngx-moveable';
import { OnDrag } from 'ngx-moveable'; import { OnDrag } from 'ngx-moveable';
import { ConfigService } from '../services/config.service'; 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({ @Component({
selector: 'plan-display', selector: 'plan-display',
templateUrl: './plan-display.component.html', templateUrl: './plan-display.component.html',
styleUrls: ['./plan-display.component.scss'] styleUrls: ['./plan-display.component.scss']
}) })
export class PlanDisplayComponent implements OnInit { export class PlanDisplayComponent implements OnInit, AfterViewInit {
draggable: boolean = true; draggable: boolean = true;
initialized: boolean = false;
@Input()
plan: boolean = false;
@Input()
backgroundColor?: Color;
@ViewChild("moveableRef") @ViewChild("moveableRef")
moveableRef!: NgxMoveableComponent; 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) { setPosition(x: number, y: number) {
this.moveableRef.request("draggable", { this.moveableRef.request("draggable", {
x, x,
@ -29,16 +48,29 @@ export class PlanDisplayComponent implements OnInit {
setArea(width: number, height: number) { setArea(width: number, height: number) {
this.moveableRef.request("resizable", { this.moveableRef.request("resizable", {
offsetWidth: 500, offsetWidth: width,
offsetHeight: 500, offsetHeight: height,
}, true); }, true);
} }
toRect(): Rect {
let rectInfo = this.moveableRef.getRect();
return {
x: rectInfo.left,
y: rectInfo.top,
width: rectInfo.width,
height: rectInfo.height
};
}
onDrag(e: OnDrag) { onDrag(e: OnDrag) {
e.target.style.transform = e.transform; 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) { onResize(e: OnResize) {
@ -46,4 +78,10 @@ export class PlanDisplayComponent implements OnInit {
e.target.style.height = `${e.height}px`; e.target.style.height = `${e.height}px`;
e.target.style.transform = e.drag.transform; e.target.style.transform = e.drag.transform;
} }
onResizeEnd(e: OnResizeEnd) {
if (this.initialized) {
this.configService.config.initialPlanWindowPosition = this.toRect();
}
}
} }

@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgxMoveableComponent } from 'ngx-moveable'; import { NgxMoveableComponent } from 'ngx-moveable';
import { PlanDisplayComponent } from './plan-display.component'; import { PlanDisplayComponent } from './plan-display.component';
import {MatCardModule} from '@angular/material/card';
@NgModule({ @NgModule({
@ -12,6 +12,7 @@ import { PlanDisplayComponent } from './plan-display.component';
imports: [ imports: [
CommonModule, CommonModule,
NgxMoveableComponent, NgxMoveableComponent,
MatCardModule,
], ],
exports: [ exports: [
PlanDisplayComponent PlanDisplayComponent

@ -1,12 +1,55 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Config } from '../models/generated/Config';
import { invoke } from '@tauri-apps/api';
import { Subject, debounceTime } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ConfigService { 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<void> = new Subject<void>();
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<Config>('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() { console.log("Loaded cfg:", this.config);
return 0; 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<T extends keyof Config>(target: Config, property: T, value: Config[T]) {
target[property] = value;
}

@ -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<StateEvent>("OverlayStateChange").subscribe(this.onOverlayStateChange);
}
onOverlayStateChange(event: Event<StateEvent>) {
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);
}
}

@ -1,6 +1,7 @@
import { Injectable, NgZone } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import { ShortcutHandler, register, unregister } from '@tauri-apps/api/globalShortcut'; import { ShortcutHandler, register, unregister } from '@tauri-apps/api/globalShortcut';
import { EMPTY, Observable, catchError, empty, from } from 'rxjs'; import { EMPTY, Observable, catchError, empty, from } from 'rxjs';
import { ConfigService } from './config.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -8,8 +9,10 @@ import { EMPTY, Observable, catchError, empty, from } from 'rxjs';
export class ShortcutService { export class ShortcutService {
bound: Map<ShortcutHandler, string> = new Map<ShortcutHandler, string>(); bound: Map<ShortcutHandler, string> = new Map<ShortcutHandler, string>();
constructor(private zone: NgZone) { } constructor(private zone: NgZone) {
}
register(shortcut: string, handler: ShortcutHandler) { register(shortcut: string, handler: ShortcutHandler) {
console.log("binding key:", shortcut, "to handler", handler);
this.bound.set(handler, shortcut); this.bound.set(handler, shortcut);
return from(register(shortcut, (s) => { return from(register(shortcut, (s) => {

@ -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 { :root {
color: #afafaf; color: #afafaf;
background-color: rgba($color: #000000, $alpha: 0); background-color: rgba($color: #444, $alpha: 0);
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
.interactable {
background-color: rgba($color: #444, $alpha: 0.1);
}
html, html,
body { body {
height: 100vh; height: 100vh;
@ -11,4 +32,11 @@ body {
body { body {
overflow: hidden; overflow: hidden;
} }
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);

@ -127,6 +127,15 @@
dependencies: dependencies:
tslib "^2.3.0" 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": "@angular/cli@~16.1.4":
version "16.1.4" version "16.1.4"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.1.4.tgz#b32de345b6fdb4e975f957b0f9cbcafdcf184f8a" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.1.4.tgz#b32de345b6fdb4e975f957b0f9cbcafdcf184f8a"
@ -193,10 +202,10 @@
dependencies: dependencies:
tslib "^2.3.0" tslib "^2.3.0"
"@angular/material@^16.1.5": "@angular/material@^16.1.4":
version "16.1.5" version "16.1.6"
resolved "https://registry.yarnpkg.com/@angular/material/-/material-16.1.5.tgz#5ba16d5b8c3e3bbe1582fd70ac21df675a48a3a6" resolved "https://registry.yarnpkg.com/@angular/material/-/material-16.1.6.tgz#2edbde9c76973fe237108c5c990de61e8d05883d"
integrity sha512-l11mH/WWBmfiBhrf4/0hCihhLxK4Ldu7+fP8zucHO3X2TiLlpsgJZpcYwJkZf0Ai0rDqIzqCVnks7L9jiuTGCQ== integrity sha512-PhfwqWN6cCiKCN2B1hUqrg3uwHC3ZwiCndJ/2CWEwEC824aOdf2b2ifTXZdtGbP3bFTkURkfvwfJVle4j/fbHw==
dependencies: dependencies:
"@material/animation" "15.0.0-canary.b994146f6.0" "@material/animation" "15.0.0-canary.b994146f6.0"
"@material/auto-init" "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" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== 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": "@tauri-apps/api@^1.2.0":
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.4.0.tgz#b4013ca3d17b853f7df29fe14079ebb4d52dbffa" 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: dependencies:
parse5 "^7.0.0" parse5 "^7.0.0"
parse5@^7.0.0: parse5@^7.0.0, parse5@^7.1.2:
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
@ -6914,6 +6928,13 @@ validate-npm-package-name@^5.0.0:
dependencies: dependencies:
builtins "^5.0.0" 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: vary@^1, vary@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"

Loading…
Cancel
Save