Lots of plan management, loading and so on

merge-notes
isark 2 years ago
parent 35e2ba31b4
commit 95a69df695

@ -39,3 +39,4 @@ directories = "5.0.1"
# 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
# DO NOT REMOVE!! # DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]

@ -1,12 +1,13 @@
// 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::sync::Mutex; use std::{path::PathBuf, sync::Mutex};
use config::Config; use config::Config;
use crossbeam::channel::Sender; use crossbeam::channel::Sender;
use overlay::{Event, LockedOverlayData, Overlay}; use overlay::{Event, LockedOverlayData, Overlay};
use plan::Plan;
use poe_data::world_area::WorldAreasMap; use poe_data::world_area::WorldAreasMap;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use storage::Storage; use storage::Storage;
@ -57,6 +58,24 @@ fn set_config(config: Config, state: tauri::State<Mutex<Storage>>) {
} }
} }
#[tauri::command]
fn load_plan(path: PathBuf, state: tauri::State<Mutex<Storage>>) -> Option<Plan> {
if let Ok(mut storage) = state.lock() {
if let Some(path_string) = path.to_str() {
storage.last_plan = Some(path_string.to_string());
let plan = Storage::load_plan(storage.last_plan.as_ref()?);
log::info!("got plan: {plan:?}");
return plan;
}
}
None
}
#[tauri::command]
fn load_stored_plans() -> Vec<String> {
Storage::enumerate_plans()
}
fn main() { fn main() {
SimpleLogger::new() SimpleLogger::new()
.with_module_level("underlayer", log::LevelFilter::Info) .with_module_level("underlayer", log::LevelFilter::Info)
@ -86,6 +105,8 @@ fn main() {
load_world_areas, load_world_areas,
load_config, load_config,
set_config, set_config,
load_plan,
load_stored_plans
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

@ -1,3 +1,9 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use poe_data::world_area::WorldArea;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -10,4 +16,41 @@ pub struct Plan {
pub struct PlanElement { pub struct PlanElement {
area_key: String, area_key: String,
notes: String, notes: String,
} }
#[derive(Debug, Deserialize)]
struct OldPlanElement {
area: OldArea,
note: String,
}
#[derive(Debug, Deserialize)]
struct OldPlan {
elements: Vec<OldPlanElement>,
position: usize,
}
#[derive(Debug, Deserialize)]
struct OldArea {
_rid: usize,
}
pub fn convert_old(path: &str) -> Option<Plan> {
let plan: OldPlan = serde_json::from_str(&std::fs::read_to_string(path).expect("Could not convert old")).expect("could not convert old");
let map = poe_data::world_area::load_world_areas_map(include_str!(
"../../data/processed_world_areas.json"
));
let map = map
.iter()
.map(|(k, v)| (v.key_id, k))
.collect::<HashMap<usize, &String>>();
Some(Plan {
current: 0,
plan: plan.elements.into_iter().map(|plan_element| PlanElement {
area_key: map[&plan_element.area._rid].to_owned(),
notes: plan_element.note,
}).collect::<Vec<PlanElement>>(),
})
}

@ -1,17 +1,15 @@
use std::error::Error; use std::path::{Path, PathBuf};
use directories::ProjectDirs; use directories::ProjectDirs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::config::Config; use crate::config::Config;
use crate::plan::Plan; use crate::plan::{convert_old, Plan};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Storage { pub struct Storage {
pub config: Config, pub config: Config,
#[serde(skip)] pub last_plan: Option<String>,
pub plan: Option<Plan>,
pub plan_name: Option<String>,
} }
const QUALIFIER: &'static str = "me"; const QUALIFIER: &'static str = "me";
@ -26,13 +24,12 @@ fn proj_dir() -> Option<ProjectDirs> {
impl Default for Storage { impl Default for Storage {
fn default() -> Self { fn default() -> Self {
let config = Self::load(); let storage = Self::load();
match config { match storage {
Some(config) => config, Some(storage) => storage,
None => Self { None => Self {
config: Default::default(), config: Default::default(),
plan: Default::default(), last_plan: None,
plan_name: None,
}, },
} }
} }
@ -40,35 +37,16 @@ impl Default for Storage {
impl Storage { impl Storage {
fn load() -> Option<Self> { fn load() -> Option<Self> {
let mut storage: Option<Storage> = serde_json::from_str( let storage: Option<Storage> = serde_json::from_str(
&std::fs::read_to_string(proj_dir()?.data_dir().with_file_name(CONFIG_FILE)).ok()?, &std::fs::read_to_string(proj_dir()?.data_dir().with_file_name(CONFIG_FILE)).ok()?,
) )
.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); log::info!("Loaded storage: {:?}", 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) { pub fn save(&self) {
if let Ok(content) = serde_json::to_string_pretty(&self) { if let Ok(content) = serde_json::to_string_pretty(&self) {
if let Some(dir) = proj_dir() { if let Some(dir) = proj_dir() {
@ -83,4 +61,60 @@ impl Storage {
} }
} }
} }
pub fn load_plan<T: Into<String>>(file: T) -> Option<Plan> {
let mut file = file.into();
let plan_file = Path::new(&file);
//basically copy to our own dir to store if for reuse without contaminating or depending on the original file
if let Some(path) = plan_file.parent() {
if !path.ends_with(SAVED_PLANS) {
if let Some(proj_dir) = proj_dir() {
if let Some(file_name) = plan_file.file_name() {
let copy_result = std::fs::copy(
&file,
proj_dir
.data_dir()
.join(SAVED_PLANS)
.with_file_name(file_name),
);
match copy_result {
Ok(_) => {
file = proj_dir
.data_dir()
.join(SAVED_PLANS)
.with_file_name(file_name)
.to_str()
.unwrap()
.to_string()
}
Err(e) => log::error!("Could not store plan file {e:?}"),
}
}
}
}
}
log::info!("Loading plan: {file:?}");
if let Some(plan) = serde_json::from_str(&std::fs::read_to_string(&file).ok()?).ok()
{
plan
} else {
log::info!("Attempting to convert old");
let plan = convert_old(&file)?;
std::fs::write(&file, serde_json::to_string(&plan).ok()?).ok();
Some(plan)
}
}
pub fn enumerate_plans() -> Vec<String> {
if let Some(proj_dir) = proj_dir() {
if let Ok(read_dir) = proj_dir.data_dir().join(SAVED_PLANS).read_dir() {
return read_dir
.filter_map(|entry| Some(entry.ok()?.path().to_str()?.to_string()))
.collect::<Vec<String>>();
}
}
return vec![];
}
} }

@ -1,8 +1,8 @@
<div *ngIf="overlayService.visible"> <div *ngIf="overlayService.visible">
<plan-display [plan]="true" [backgroundColor]="planColor"></plan-display> <plan-display *ngIf="planService.currentPlan" [backgroundColor]="planColor"></plan-display>
<span *ngIf="worldAreas.matcher">matched init</span>
<color-picker [initialColor]="'#00000010'" (color)="planColor = $event">Click me for color picker!</color-picker> <color-picker [initialColor]="'#00000010'" (color)="planColor = $event">Click me for color picker!</color-picker>
<button (click)="openDialog()">Browse Plans</button>
</div> </div>

@ -6,6 +6,9 @@ import { WorldAreaService } from "./services/world-area.service";
import { Color } from "./color-picker/color-picker.component"; import { Color } from "./color-picker/color-picker.component";
import { OverlayService } from "./services/overlay.service"; import { OverlayService } from "./services/overlay.service";
import { ConfigService } from "./services/config.service"; import { ConfigService } from "./services/config.service";
import { PlanService } from "./services/plan.service";
import { open } from '@tauri-apps/api/dialog';
import { from } from "rxjs";
@Component({ @Component({
selector: "app-root", selector: "app-root",
@ -21,11 +24,26 @@ export class AppComponent implements OnInit {
constructor( constructor(
public overlayService: OverlayService, public overlayService: OverlayService,
public worldAreas: WorldAreaService, public worldAreas: WorldAreaService,
private shortcuts: ShortcutService, public planService: PlanService,
private events: EventsService,
public configService: ConfigService, public configService: ConfigService,
) { } ) { }
openDialog() {
from(open({
multiple: false,
filters: [
// {
// name: "Plan Files",
// extensions: ['PPF']
// }
]
})).subscribe(file => {
if (file) {
this.planService.loadPlan(file as string);
}
});
}
ngOnInit(): void { ngOnInit(): void {

@ -14,27 +14,27 @@ import { Subject, debounceTime } from 'rxjs';
], ],
animations: [ animations: [
trigger('direction', [ trigger('direction', [
state('vertical', style({
'grid-auto-flow': 'row', state('vertical', style({})),
})), state('horizontal', style({})),
state('horizontal', style({
'grid-auto-flow': 'column',
})),
state('hidden', style({ state('hidden', style({
opacity: 0, opacity: 0,
})), })),
state('unhidden', style({ state('unhidden', style({
opacity: 1, opacity: 1,
})), })),
transition('* => hidden', animate(`{{directionTime}}ms`)), transition('* => hidden', animate(`{{directionTime}}ms`)),
transition('hidden => unhidden', animate(`{{directionTime}}ms`)), transition('hidden => unhidden', animate(`{{directionTime}}ms`)),
transition('unhidden => *', animate(0)), transition('unhidden => *', animate('10ms')),
]) ])
] ]
}) })
export class CarouselComponent implements OnInit, AfterViewInit { export class CarouselComponent implements OnInit, AfterViewInit {
@Output() afterInitSelf: EventEmitter<CarouselComponent> = new EventEmitter<CarouselComponent>(); @Output() afterInitSelf: EventEmitter<CarouselComponent> = new EventEmitter<CarouselComponent>();
@Output() changedIndex: EventEmitter<number> = new EventEmitter<number>();
@Input() slides?: any[]; @Input() slides?: any[];
@ContentChild(TemplateRef) template?: TemplateRef<any>; @ContentChild(TemplateRef) template?: TemplateRef<any>;
@ -124,6 +124,7 @@ export class CarouselComponent implements OnInit, AfterViewInit {
this.increasing = true; this.increasing = true;
if (this.slides && this.current + 1 < this.slides?.length) { if (this.slides && this.current + 1 < this.slides?.length) {
this.current += 1; this.current += 1;
this.changedIndex.emit(this.current);
if (this.current + 1 < this.slides.length) { if (this.current + 1 < this.slides.length) {
if (!this.visibleSlides?.find(e => e.index == this.current + 1)) { if (!this.visibleSlides?.find(e => e.index == this.current + 1)) {
@ -142,6 +143,7 @@ export class CarouselComponent implements OnInit, AfterViewInit {
this.increasing = false; this.increasing = false;
if (this.current - 1 >= 0) { if (this.current - 1 >= 0) {
this.current -= 1; this.current -= 1;
this.changedIndex.emit(this.current);
if (this.current - 1 >= 0) { if (this.current - 1 >= 0) {
if (!this.visibleSlides?.find(e => e.index == this.current - 1)) { if (!this.visibleSlides?.find(e => e.index == this.current - 1)) {
@ -189,15 +191,7 @@ export class CarouselComponent implements OnInit, AfterViewInit {
const len = this.slides?.length; const len = this.slides?.length;
return `repeat(${len}, minmax(calc(100% / 3), 1fr))`; return `repeat(${len}, minmax(calc(100% / 3), 1fr))`;
} }
// .window {
// grid-template-columns: repeat(3, 1fr);
// grid-auto-flow: column;
// }
// .window-vertical {
// grid-template-rows: repeat(3, 1fr);
// grid-auto-flow: row;
// }
style() { style() {
let style: any = { let style: any = {
@ -205,8 +199,11 @@ export class CarouselComponent implements OnInit, AfterViewInit {
if (this.vertical) { if (this.vertical) {
style['grid-template-rows'] = this.templateValue(); style['grid-template-rows'] = this.templateValue();
style['grid-auto-flow'] = 'column';
} else { } else {
style['grid-template-columns'] = this.templateValue(); style['grid-template-columns'] = this.templateValue();
style['grid-auto-flow'] = 'row';
} }
if (!this.angularAnimating) { if (!this.angularAnimating) {

@ -1,13 +1,10 @@
import { WorldArea } from "./world-area"; import { WorldArea } from "./world-area";
export class Plan { export interface Plan {
elements: PlanElement[];
} }
interface PlanElement { interface PlanElement {
area: WorldArea; area: WorldArea;
note: string; note: string;
prev: boolean;
current: boolean;
next: boolean;
} }

@ -4,11 +4,14 @@
#targetRef> #targetRef>
<ng-container *ngIf="plan"> <ng-container *ngIf="plan">
<carousel [slides]="slides" (afterInitSelf)="registerCarousel($event)" #caro> <carousel [slides]="plan.elements" (afterInitSelf)="registerCarousel($event)" #caro>
<ng-template let-slide> <ng-template let-slide>
{{slide}} {{slide}}
</ng-template> </ng-template>
</carousel> </carousel>
<div class="mainSlideContent">
<span class="notes-label">Notes:</span><span class="notes">{{plan.elements[slideIndex].note}}</span>
</div>
</ng-container> </ng-container>
</div> </div>

@ -6,6 +6,8 @@ import { Rect } from '../models/generated/Rect';
import { Color } from '../color-picker/color-picker.component'; import { Color } from '../color-picker/color-picker.component';
import { ShortcutService } from '../services/shortcut.service'; import { ShortcutService } from '../services/shortcut.service';
import { CarouselComponent } from '../carousel/carousel.component'; import { CarouselComponent } from '../carousel/carousel.component';
import { PlanService } from '../services/plan.service';
import { Plan } from '../models/plan';
@Component({ @Component({
selector: 'plan-display', selector: 'plan-display',
@ -13,24 +15,24 @@ import { CarouselComponent } from '../carousel/carousel.component';
styleUrls: ['./plan-display.component.scss'] styleUrls: ['./plan-display.component.scss']
}) })
export class PlanDisplayComponent implements AfterViewInit, OnInit { export class PlanDisplayComponent implements AfterViewInit, OnInit {
@Input() plan: boolean = false;
@Input() backgroundColor?: Color; @Input() backgroundColor?: Color;
draggable: boolean = true; draggable: boolean = true;
rect?: Rect; rect?: Rect;
slides!: number[]; // slides!: number[];
plan?: Plan;
slideIndex: number = 0; slideIndex: number = 0;
carouselComponent?: CarouselComponent; carouselComponent?: CarouselComponent;
constructor(private configService: ConfigService, private cdr: ChangeDetectorRef, private shortcut: ShortcutService) { constructor(private configService: ConfigService, private cdr: ChangeDetectorRef, private shortcut: ShortcutService, private planService: PlanService) {
this.slides = []; // for(let i = 0; i < 100; i++) {
for(let i = 0; i < 100; i++) { // this.slides.push(i);
this.slides.push(i); // }
}
} }
ngOnInit() { ngOnInit() {
this.plan = this.planService.currentPlan;
} }
abs(v: number) { abs(v: number) {
@ -51,7 +53,6 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
ngAfterViewInit(): void { ngAfterViewInit(): void {
const cfgRect = this.configService.config.initialPlanWindowPosition; const cfgRect = this.configService.config.initialPlanWindowPosition;
console.log(window.innerWidth);
this.rect = { this.rect = {
x: cfgRect.x * window.innerWidth, x: cfgRect.x * window.innerWidth,
y: cfgRect.y * window.innerHeight, y: cfgRect.y * window.innerHeight,
@ -75,8 +76,6 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
this.rect!.width = e.width; this.rect!.width = e.width;
this.rect!.height = e.height; this.rect!.height = e.height;
this.onDrag(e.drag); this.onDrag(e.drag);
console.log(e.style);
} }
onResizeEnd(e: OnResizeEnd) { onResizeEnd(e: OnResizeEnd) {
@ -103,7 +102,6 @@ export class PlanDisplayComponent implements AfterViewInit, OnInit {
this.carouselComponent?.next(); this.carouselComponent?.next();
}); });
} else { } else {
console.log("no carousel", this.carouselComponent);
} }
} }
} }

@ -4,6 +4,7 @@ import { EventsService } from './events.service';
import { Event } from "@tauri-apps/api/event"; import { Event } from "@tauri-apps/api/event";
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { ConfigService } from './config.service'; import { ConfigService } from './config.service';
import { WorldAreaService } from './world-area.service';
class StateEvent { class StateEvent {
Visible?: any; Visible?: any;
@ -20,7 +21,7 @@ export class OverlayService {
visible: boolean = false; visible: boolean = false;
constructor(private shortcuts: ShortcutService, private events: EventsService, private configService: ConfigService, private zone: NgZone) { constructor(private shortcuts: ShortcutService, private events: EventsService, private configService: ConfigService) {
this.shortcuts.register(this.configService.config.toggleOverlay, this.onToggleOverlay.bind(this)); this.shortcuts.register(this.configService.config.toggleOverlay, this.onToggleOverlay.bind(this));
this.events.listen<StateEvent>("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this)); this.events.listen<StateEvent>("OverlayStateChange").subscribe(this.onOverlayStateChange.bind(this));
} }
@ -28,7 +29,6 @@ export class OverlayService {
onOverlayStateChange(event: Event<StateEvent>) { onOverlayStateChange(event: Event<StateEvent>) {
this.interactable = event.payload.Interactable != null; this.interactable = event.payload.Interactable != null;
if (event.payload.Hidden) {this.visible = false} else {this.visible = true}; if (event.payload.Hidden) {this.visible = false} else {this.visible = true};
console.log("visible: ", this.visible);
invoke("set_interactable", { interactable: true }).then(); invoke("set_interactable", { interactable: true }).then();
} }

@ -1,9 +1,28 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { invoke } from '@tauri-apps/api';
import { from } from 'rxjs';
import { Plan } from '../models/plan';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class PlanService { export class PlanService {
currentPlan?: Plan;
planStore: string[] = [];
constructor() { } constructor() {
this.getPreviousPlans();
}
loadPlan(path: string) {
console.log("loading path: ", path);
from(invoke<Plan>('load_plan', {path})).subscribe(plan => {
console.log("got plan: ", plan);
this.currentPlan = plan;
});
}
getPreviousPlans() {
from(invoke<string[]>('load_stored_plans')).subscribe(plans => this.planStore = plans);
}
} }

Loading…
Cancel
Save