From 48d1dda124dd6c0a47da77e4c2f0b1104d4c564d Mon Sep 17 00:00:00 2001 From: isark Date: Wed, 14 Feb 2024 21:54:30 +0100 Subject: [PATCH] Lots of work around importing plans from urls, also keeping track of the url to allow updating the plan later on if there's been a change. --- src-tauri/Cargo.toml | 2 +- src-tauri/src/main.rs | 49 ++++++---- src-tauri/src/plan.rs | 4 +- src-tauri/src/storage.rs | 8 ++ src-tauri/tauri.conf.json | 8 ++ src/app/_models/plan.ts | 13 ++- src/app/_services/events.service.ts | 4 +- src/app/_services/plan.service.ts | 90 +++++++++++++++++-- src/app/app.module.ts | 4 +- src/app/editor/editor.component.ts | 1 - .../plan-display/plan-display.component.html | 32 ++++--- .../plan-display/plan-display.component.ts | 14 +-- .../plan-display/url-dialog.component.html | 7 ++ src/app/plan-display/url-dialog.component.ts | 31 +++++++ src/assets/public.svg | 1 + 15 files changed, 218 insertions(+), 50 deletions(-) create mode 100644 src/app/plan-display/url-dialog.component.html create mode 100644 src/app/plan-display/url-dialog.component.ts create mode 100644 src/assets/public.svg diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dfa59c6..f40b13a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,7 +20,7 @@ ts-rs = "6.2.1" [dependencies] steamlocate = "1.2.1" -tauri = { version = "1.2", features = [ "dialog-open", "global-shortcut-all", "dialog-save", "updater", "system-tray"] } +tauri = { version = "1.2", features = [ "http-request", "dialog-open", "global-shortcut-all", "dialog-save", "updater", "system-tray"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" Underlayer = { git = "https://git.isark.me/isark/Underlay.git" } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8ed83b2..d41ae68 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,6 +1,7 @@ // 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::sync::mpsc::Receiver; use std::{path::PathBuf, sync::Mutex}; @@ -42,9 +43,9 @@ fn set_interactable(interactable: bool, state: tauri::State>) { } lazy_static! { - static ref WORLD_AREAS_MAP: WorldAreasMap = poe_data::world_area::load_world_areas_map(include_str!( - "../../data/processed_world_areas.json" - )); + static ref WORLD_AREAS_MAP: WorldAreasMap = poe_data::world_area::load_world_areas_map( + include_str!("../../data/processed_world_areas.json") + ); } #[tauri::command] @@ -59,10 +60,7 @@ fn load_config(state: tauri::State>) -> Option { } #[tauri::command] -fn set_config( - config: Config, - state: tauri::State>, -) { +fn set_config(config: Config, state: tauri::State>) { log::info!("Saved config: {:?}", config); if let Ok(mut storage) = state.lock() { storage.config = config.clone(); @@ -86,6 +84,7 @@ fn load_plan(path: PathBuf, state: tauri::State>) -> Option #[tauri::command] fn save_plan(path: PathBuf, plan: Plan) -> bool { if let Some(path_string) = path.with_extension("json").to_str() { + log::trace!("Attempting to save plan at path {path_string}"); return Storage::save_plan(path_string, plan).is_ok(); } @@ -93,26 +92,39 @@ fn save_plan(path: PathBuf, plan: Plan) -> bool { } #[tauri::command] -fn load_stored_plans() -> Vec { +fn save_plan_by_name(name: String, plan: Plan) -> bool { + let path = Storage::create_path_for_name(name); + log::trace!("plan: {plan:?}"); + + if let Some(path) = path { + return save_plan(path.into(), plan); + } + + false +} + +#[tauri::command] +fn load_stored_plans() -> HashMap { Storage::enumerate_plans() + .into_iter() + .map(|name| (name.clone(), Storage::load_by_name(name).unwrap())) + .collect() } #[tauri::command] fn load_previous(name: String) -> Option { - let plan = Storage::load_by_name(name); - log::info!("got plan: {plan:?}"); - return plan; + let plan = Storage::load_by_name(name); + log::info!("got plan: {plan:?}"); + return plan; } - #[tauri::command] fn path_for_previous(prev: String) -> Option { - let path = Storage::path_for_name(prev); - log::info!("got path: {path:?}"); - return path; + let path = Storage::path_for_name(prev); + log::info!("got path: {path:?}"); + return path; } - #[tauri::command] fn base_plan() -> Plan { const BASE_PLAN_STRING: &str = include_str!("../../data/base_plan.json"); @@ -170,6 +182,7 @@ fn main() { load_plan, load_stored_plans, save_plan, + save_plan_by_name, base_plan, load_previous, path_for_previous, @@ -187,7 +200,9 @@ fn main() { } } "force_show" => { - app.state::>().send(overlay::State::Interactable {}.into()).ok(); + app.state::>() + .send(overlay::State::Interactable {}.into()) + .ok(); } _ => {} }, diff --git a/src-tauri/src/plan.rs b/src-tauri/src/plan.rs index a1f60d8..41a421c 100644 --- a/src-tauri/src/plan.rs +++ b/src-tauri/src/plan.rs @@ -7,6 +7,7 @@ pub struct Plan { plan: Vec, current: usize, stored_path: Option, + update_url: Option, } impl Plan { pub fn set_stored_path(&mut self, file: String) { @@ -81,6 +82,7 @@ pub fn convert_old(path: &str) -> Option { anchor_act: None, }) .collect::>(), - stored_path: None + stored_path: None, + update_url: None, }) } diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs index 15331f7..dec4780 100644 --- a/src-tauri/src/storage.rs +++ b/src-tauri/src/storage.rs @@ -78,6 +78,8 @@ impl Storage { Ok(()) } + + pub fn load_plan>(file: T) -> Option { let mut file = file.into(); let plan_file = Path::new(&file); @@ -149,5 +151,11 @@ impl Storage { let file = proj_dir()?.data_dir().join(SAVED_PLANS).join(file); Some(file.to_str()?.to_string()) } + + pub fn create_path_for_name>(file_name: T) -> Option { + let file_name = file_name.into(); + let file: PathBuf = proj_dir()?.data_dir().join(SAVED_PLANS).join(file_name).with_extension("json"); + Some(file.to_str()?.to_string()) + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 09bb97f..2a96534 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -22,6 +22,14 @@ }, "globalShortcut": { "all": true + }, + "http": { + "all": false, + "request": true, + "scope": [ + "https://*", + "http://*" + ] } }, "bundle": { diff --git a/src/app/_models/plan.ts b/src/app/_models/plan.ts index 22dce0f..4b211fc 100644 --- a/src/app/_models/plan.ts +++ b/src/app/_models/plan.ts @@ -5,18 +5,23 @@ export interface PlanInterface { plan: PlanElement[]; current: number; stored_path?: string; + update_url?: string; + name?: string; } export class Plan { plan: PlanElement[]; current: number; + update_url?: string; + name?: string; private path?: string; private saveSubject: Subject = new Subject(); + constructor(plan: PlanInterface) { this.plan = plan.plan; this.current = plan.current; - if(plan.stored_path) { + if (plan.stored_path) { this.path = plan.stored_path; } this.saveSubject.pipe(debounceTime(500)).subscribe(() => this.underlyingSave()); @@ -48,6 +53,12 @@ export class Plan { }); } + setPreviousPlan(prev: Plan) { + if (prev.path) { + this.setPath(prev.path); + } + } + private save() { if (this.path) { this.saveSubject.next(); diff --git a/src/app/_services/events.service.ts b/src/app/_services/events.service.ts index 85ab707..91701c2 100644 --- a/src/app/_services/events.service.ts +++ b/src/app/_services/events.service.ts @@ -9,9 +9,11 @@ export class EventsService { constructor(private zone: NgZone) { } listen(name: string,): Observable> { return new Observable>((subscriber) => { + const unlisten = listen(name, (v: Event) => { this.zone.run(() => subscriber.next(v)); }); + return async () => { (await unlisten)() }; }); } @@ -19,4 +21,4 @@ export class EventsService { emit(name: string, event: T): Observable { return from(emit(name, event)); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/_services/plan.service.ts b/src/app/_services/plan.service.ts index 538ddb1..0c94657 100644 --- a/src/app/_services/plan.service.ts +++ b/src/app/_services/plan.service.ts @@ -1,21 +1,23 @@ import { Injectable } from '@angular/core'; import { invoke } from '@tauri-apps/api'; -import { Observable, ReplaySubject, Subject, from, map, tap } from 'rxjs'; +import { Observable, ReplaySubject, Subject, from, map, switchMap, tap } from 'rxjs'; import { Plan, PlanInterface } from '../_models/plan'; +import { MatDialog } from '@angular/material/dialog'; +import { UrlDialog } from '../plan-display/url-dialog.component'; +import { fetch } from '@tauri-apps/api/http'; @Injectable({ providedIn: 'root' }) export class PlanService { - currentPlan?: Plan; - planStore: string[] = []; + planStore: Map = new Map(); basePlan?: Plan; private basePlanSubj: Subject = new ReplaySubject(1); - - constructor() { + + constructor(private dialog: MatDialog) { this.getPreviousPlans(); this.loadBasePlan(); } @@ -39,12 +41,12 @@ export class PlanService { ); } - + loadBasePlan() { - if(!this.basePlan) { + if (!this.basePlan) { from(invoke('base_plan')).subscribe(plan => { - plan.plan.forEach(elem => {elem.edited = false;}); + plan.plan.forEach(elem => { elem.edited = false; }); this.basePlan = new Plan(plan); this.basePlanSubj?.next(this.basePlan); }); @@ -68,8 +70,24 @@ export class PlanService { }); } + savePlanByName(name: string, plan: Plan) { + plan.plan.forEach(elem => { + if (!elem.notes) { elem.notes = "" } + }); + + return from(invoke('save_plan_by_name', { + name, + plan: { + plan: plan.plan, + current: plan.current, + update_url: plan.update_url + }, + })).subscribe(status => { + }); + } + getPreviousPlans() { - from(invoke('load_stored_plans')).subscribe(plans => this.planStore = plans); + from(invoke>('load_stored_plans')).subscribe(plans => this.planStore = plans); } loadPrevious(name: string) { @@ -80,6 +98,18 @@ export class PlanService { })); } + loadPreviousPlan(plan: Plan): Observable { + return new Observable(observer => { + observer.next(plan); + }).pipe(map(plan => { + console.log("previous loaded: ", plan); + this.currentPlan = plan; + this.currentPlan.setPreviousPlan(plan); + + return plan as PlanInterface; + })); + } + zoneFromUuid(uuid: string) { if (!this.basePlan) { return undefined; @@ -87,4 +117,46 @@ export class PlanService { return this.basePlan.plan.find(elem => elem.uuid === uuid); } + + loadFromUrl(url?: string, name?: string): Observable { + if (!url || !name) { + const dialogRef = this.dialog.open(UrlDialog, { + data: { + url: url, + name: name + } + }); + + return dialogRef.afterClosed().pipe(switchMap(data => { + console.log("import from url data: ", data); + if (data.url) { + return this._loadFromUrl(data.url, data.name); + } + + return new Observable((s) => s.complete()); + })); + + } else { + return this._loadFromUrl(url, name); + } + } + + private _loadFromUrl(url: string, name: string): Observable { + return from(fetch( + url, + { + method: 'GET', + timeout: 30 + })).pipe(map(response => { + return response.data as PlanInterface; + })).pipe(tap(plan => { + plan.update_url = url; + const planObj = new Plan(plan); + planObj.update_url = url; + console.log("plan and plan interface: ", planObj, plan); + this.savePlanByName(name, planObj); + this.currentPlan = planObj; + this.getPreviousPlans(); + })); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 34ea19c..7460502 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -15,6 +15,7 @@ import { SettingsComponent } from "./settings/settings.component"; import { MatTabsModule } from '@angular/material/tabs'; import { MAT_DIALOG_DEFAULT_OPTIONS } from "@angular/material/dialog"; import { TooltipComponent } from "./tooltip/tooltip.component"; +import { HttpClientModule } from "@angular/common/http"; // import { GemFinderComponent } from "./gem-finder/gem-finder.component"; export function initializeApp(configService: ConfigService) { @@ -38,7 +39,8 @@ export function initializeApp(configService: ConfigService) { OverlayModule, SettingsComponent, MatTabsModule, - TooltipComponent + TooltipComponent, + HttpClientModule ], providers: [ { diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts index 974fa2d..51ff44e 100644 --- a/src/app/editor/editor.component.ts +++ b/src/app/editor/editor.component.ts @@ -322,7 +322,6 @@ export class EditorComponent implements OnInit { } } - function isWorldAreaEvent(event: any): event is CdkDragDrop { return (event.previousContainer.data.length > 0 && 'connections_world_areas_keys' in event.previousContainer.data[0]); } \ No newline at end of file diff --git a/src/app/plan-display/plan-display.component.html b/src/app/plan-display/plan-display.component.html index da4b404..b263aa3 100644 --- a/src/app/plan-display/plan-display.component.html +++ b/src/app/plan-display/plan-display.component.html @@ -6,9 +6,9 @@ [style.transform]="transform()" [style.width]="rect.width + 'px'" [style.height]="rect.height + 'px'" [class]="specialClasses()" (wheel)="onScroll($event)" #targetRef> -
(W) = Waypoint (T) = Trial - The plan window's will have a glow in the corresponding color(s) above to help indicate if the current zone has any of those. - You can scroll in the plan window (while it is in 'interactable' mode) to quickly switch many zones + The plan window's will have a glow in the corresponding color(s) above to help indicate if the + current zone has any of those. + You can scroll in the plan window (while it is in 'interactable' mode) to quickly switch many + zones
@@ -59,29 +61,37 @@ [cdkConnectedOverlayOrigin]="globalTopLeft" (detach)="settingsOpen = false">
-
+
-
+
-
+
+
+ +
+ *ngFor="let plan of planService.planStore | keyvalue"> + +
- +
+ +
\ No newline at end of file diff --git a/src/app/plan-display/url-dialog.component.ts b/src/app/plan-display/url-dialog.component.ts new file mode 100644 index 0000000..2b70bd8 --- /dev/null +++ b/src/app/plan-display/url-dialog.component.ts @@ -0,0 +1,31 @@ +import { Component, Inject } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; + +interface UrlData { + url: string; + name: string; + } + + @Component({ + selector: 'url-dialog', + templateUrl: 'url-dialog.component.html', + standalone: true, + imports: [MatDialogModule, MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule], + }) + export class UrlDialog { + public data2: UrlData; + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: UrlData, + ) { + this.data2 = data; + } + + cancel() { + this.dialogRef.close(); + } + } \ No newline at end of file diff --git a/src/assets/public.svg b/src/assets/public.svg new file mode 100644 index 0000000..ddcd567 --- /dev/null +++ b/src/assets/public.svg @@ -0,0 +1 @@ + \ No newline at end of file