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
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]

@ -1,12 +1,13 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::sync::Mutex;
use std::{path::PathBuf, sync::Mutex};
use config::Config;
use crossbeam::channel::Sender;
use overlay::{Event, LockedOverlayData, Overlay};
use plan::Plan;
use poe_data::world_area::WorldAreasMap;
use simple_logger::SimpleLogger;
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() {
SimpleLogger::new()
.with_module_level("underlayer", log::LevelFilter::Info)
@ -86,6 +105,8 @@ fn main() {
load_world_areas,
load_config,
set_config,
load_plan,
load_stored_plans
])
.run(tauri::generate_context!())
.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};
#[derive(Serialize, Deserialize, Debug)]
@ -10,4 +16,41 @@ pub struct Plan {
pub struct PlanElement {
area_key: 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 serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::plan::Plan;
use crate::plan::{convert_old, Plan};
#[derive(Serialize, Deserialize, Debug)]
pub struct Storage {
pub config: Config,
#[serde(skip)]
pub plan: Option<Plan>,
pub plan_name: Option<String>,
pub last_plan: Option<String>,
}
const QUALIFIER: &'static str = "me";
@ -26,13 +24,12 @@ fn proj_dir() -> Option<ProjectDirs> {
impl Default for Storage {
fn default() -> Self {
let config = Self::load();
match config {
Some(config) => config,
let storage = Self::load();
match storage {
Some(storage) => storage,
None => Self {
config: Default::default(),
plan: Default::default(),
plan_name: None,
last_plan: None,
},
}
}
@ -40,35 +37,16 @@ impl Default for Storage {
impl Storage {
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()?,
)
.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() {
@ -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">
<plan-display [plan]="true" [backgroundColor]="planColor"></plan-display>
<span *ngIf="worldAreas.matcher">matched init</span>
<plan-display *ngIf="planService.currentPlan" [backgroundColor]="planColor"></plan-display>
<color-picker [initialColor]="'#00000010'" (color)="planColor = $event">Click me for color picker!</color-picker>
<button (click)="openDialog()">Browse Plans</button>
</div>

@ -6,6 +6,9 @@ import { WorldAreaService } from "./services/world-area.service";
import { Color } from "./color-picker/color-picker.component";
import { OverlayService } from "./services/overlay.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({
selector: "app-root",
@ -21,11 +24,26 @@ export class AppComponent implements OnInit {
constructor(
public overlayService: OverlayService,
public worldAreas: WorldAreaService,
private shortcuts: ShortcutService,
private events: EventsService,
public planService: PlanService,
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 {

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

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

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

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

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

@ -1,9 +1,28 @@
import { Injectable } from '@angular/core';
import { invoke } from '@tauri-apps/api';
import { from } from 'rxjs';
import { Plan } from '../models/plan';
@Injectable({
providedIn: 'root'
})
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