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.

cleanup
isark 1 year ago
parent 21333908ec
commit 48d1dda124

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

@ -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<Sender<Event>>) {
}
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<Mutex<Storage>>) -> Option<Config> {
}
#[tauri::command]
fn set_config(
config: Config,
state: tauri::State<Mutex<Storage>>,
) {
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.clone();
@ -86,6 +84,7 @@ fn load_plan(path: PathBuf, state: tauri::State<Mutex<Storage>>) -> Option<Plan>
#[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<String> {
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<String, Plan> {
Storage::enumerate_plans()
.into_iter()
.map(|name| (name.clone(), Storage::load_by_name(name).unwrap()))
.collect()
}
#[tauri::command]
fn load_previous(name: String) -> Option<Plan> {
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<String> {
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::<Sender<Event>>().send(overlay::State::Interactable {}.into()).ok();
app.state::<Sender<Event>>()
.send(overlay::State::Interactable {}.into())
.ok();
}
_ => {}
},

@ -7,6 +7,7 @@ pub struct Plan {
plan: Vec<PlanElement>,
current: usize,
stored_path: Option<String>,
update_url: Option<String>,
}
impl Plan {
pub fn set_stored_path(&mut self, file: String) {
@ -81,6 +82,7 @@ pub fn convert_old(path: &str) -> Option<Plan> {
anchor_act: None,
})
.collect::<Vec<PlanElement>>(),
stored_path: None
stored_path: None,
update_url: None,
})
}

@ -78,6 +78,8 @@ impl Storage {
Ok(())
}
pub fn load_plan<T: Into<String>>(file: T) -> Option<Plan> {
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<T: Into<String>>(file_name: T) -> Option<String> {
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())
}
}

@ -22,6 +22,14 @@
},
"globalShortcut": {
"all": true
},
"http": {
"all": false,
"request": true,
"scope": [
"https://*",
"http://*"
]
}
},
"bundle": {

@ -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<void> = new Subject<void>();
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();

@ -9,9 +9,11 @@ export class EventsService {
constructor(private zone: NgZone) { }
listen<T>(name: string,): Observable<Event<T>> {
return new Observable<Event<T>>((subscriber) => {
const unlisten = listen(name, (v: Event<T>) => {
this.zone.run(() => subscriber.next(v));
});
return async () => { (await unlisten)() };
});
}
@ -19,4 +21,4 @@ export class EventsService {
emit<T>(name: string, event: T): Observable<void> {
return from(emit(name, event));
}
}
}

@ -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<string, Plan> = new Map<string, Plan>();
basePlan?: Plan;
private basePlanSubj: Subject<Plan> = new ReplaySubject<Plan>(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<PlanInterface>('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<boolean>('save_plan_by_name', {
name,
plan: {
plan: plan.plan,
current: plan.current,
update_url: plan.update_url
},
})).subscribe(status => {
});
}
getPreviousPlans() {
from(invoke<string[]>('load_stored_plans')).subscribe(plans => this.planStore = plans);
from(invoke<Map<string, Plan>>('load_stored_plans')).subscribe(plans => this.planStore = plans);
}
loadPrevious(name: string) {
@ -80,6 +98,18 @@ export class PlanService {
}));
}
loadPreviousPlan(plan: Plan): Observable<PlanInterface> {
return new Observable<Plan>(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<PlanInterface> {
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<PlanInterface>((s) => s.complete());
}));
} else {
return this._loadFromUrl(url, name);
}
}
private _loadFromUrl(url: string, name: string): Observable<PlanInterface> {
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();
}));
}
}

@ -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: [
{

@ -322,7 +322,6 @@ export class EditorComponent implements OnInit {
}
}
function isWorldAreaEvent(event: any): event is CdkDragDrop<WorldArea[]> {
return (event.previousContainer.data.length > 0 && 'connections_world_areas_keys' in event.previousContainer.data[0]);
}

@ -6,9 +6,9 @@
[style.transform]="transform()" [style.width]="rect.width + 'px'" [style.height]="rect.height + 'px'"
[class]="specialClasses()" (wheel)="onScroll($event)" #targetRef>
<ng-container *ngIf="planService.currentPlan">
<carousel class="zones" [initIndex]="planService.currentPlan.current" [numVisible]="configService.config.numVisible"
[offset]="clampedOffset()" [slides]="planService.currentPlan.plan"
(afterInitSelf)="registerZoneSlides($event)"
<carousel class="zones" [initIndex]="planService.currentPlan.current"
[numVisible]="configService.config.numVisible" [offset]="clampedOffset()"
[slides]="planService.currentPlan.plan" (afterInitSelf)="registerZoneSlides($event)"
[ngStyle]="zonesStyle()">
<ng-template let-slide let-index="index">
<div class="zone-slide" [style.color]="configService.config.noteDefaultFg"
@ -41,8 +41,10 @@
<div class="d-flex flex-column help-area">
<span><span class="waypoint-text">(W)</span> = Waypoint</span>
<span><span class="trial-text">(T)</span> = Trial</span>
<span>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.</span>
<span>You can scroll in the plan window (while it is in 'interactable' mode) to quickly switch many zones</span>
<span>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.</span>
<span>You can scroll in the plan window (while it is in 'interactable' mode) to quickly switch many
zones</span>
</div>
</tooltip>
</div>
@ -59,29 +61,37 @@
[cdkConnectedOverlayOrigin]="globalTopLeft" (detach)="settingsOpen = false">
<div class="overlay container-fluid vw-100">
<div class="row row-cols-2">
<div class="planChooser col-xs-6 col-sm-6 col-md-4 col-lg-3 col-xl-3">
<div class="planChooser col-xs-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<div class="d-flex justify-content-evenly">
<div class="col-xs-6">
<div class="col-xs-4">
<button class="" mat-raised-button color="accent" (click)="openDialog()">Browse
Plans
</button>
</div>
<div class="col-xs-6">
<div class="col-xs-4">
<button class="" mat-raised-button color="accent" (click)="loadBasePlan()">
Load base plan
</button>
</div>
<div class="col-xs-4">
<button class="" mat-raised-button color="accent" (click)="loadFromUrl()">
Import from url
</button>
</div>
</div>
<div class="enumerated">
<mat-list role="list">
<mat-list-item class="d-flex flex-column" role="listitem"
*ngFor="let plan of planService.planStore"><button
(click)="setPrevious(plan)">{{plan}}</button></mat-list-item>
*ngFor="let plan of planService.planStore | keyvalue">
<button (click)="setPrevious(plan.key)">
<img *ngIf="plan.value.update_url" src="assets/public.svg">{{plan.key}}
</button>
</mat-list-item>
</mat-list>
</div>
</div>
<settings class="col-xs-6 col-sm-6 offset-md-3 col-md-5 offset-lg-5 col-lg-4 offset-xl-5 col-xl-4">
<settings class="col-xs-6 col-sm-6 offset-md-1 col-md-5 offset-lg-4 col-lg-4 offset-xl-4 col-xl-4">
</settings>
</div>
<button mat-icon-button class="exit" *ngIf="planService.currentPlan"

@ -23,6 +23,7 @@ import { Event } from '@tauri-apps/api/event';
styleUrls: ['./plan-display.component.scss']
})
export class PlanDisplayComponent implements AfterViewInit, OnInit {
@Input() backgroundColor?: String;
draggable: boolean = true;
rect?: Rect;
@ -49,11 +50,6 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
})
});
appWindow.listen("entered", (entered) => {
if (this.planService.currentPlan) {
const current = this.planService.currentPlan.current;
@ -195,7 +191,7 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
}
next() {
if(this.overlayService.visible) {
if (this.overlayService.visible) {
this.planService.currentPlan!.next();
this.currentSlides?.next();
this.zoneSlides?.next();
@ -203,7 +199,7 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
}
prev() {
if(this.overlayService.visible) {
if (this.overlayService.visible) {
this.planService.currentPlan!.prev();
this.currentSlides?.prev();
this.zoneSlides?.prev();
@ -287,4 +283,8 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
'max-height': `${this.configService.config.numVisible * 40}px`
}
}
loadFromUrl() {
this.planService.loadFromUrl().subscribe();
}
}

@ -0,0 +1,7 @@
<label>Plan name</label><input matInput type="text" [(ngModel)]="data2.name"><br>
<label>Url</label><input matInput type="text" [(ngModel)]="data2.url">
<div mat-dialog-actions>
<button mat-button (click)="cancel()">Cancel</button>
<button mat-button [mat-dialog-close]="data2" cdkFocusInitial>Save</button>
</div>

@ -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<UrlDialog>,
@Inject(MAT_DIALOG_DATA) public data: UrlData,
) {
this.data2 = data;
}
cancel() {
this.dialogRef.close();
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>

After

Width:  |  Height:  |  Size: 457 B

Loading…
Cancel
Save