You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
9.1 KiB
325 lines
9.1 KiB
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
|
|
|
import {
|
|
CdkDrag,
|
|
CdkDragDrop,
|
|
CdkDropList,
|
|
CdkDropListGroup,
|
|
moveItemInArray,
|
|
} from '@angular/cdk/drag-drop';
|
|
|
|
import { CommonModule } from '@angular/common';
|
|
import { WorldArea } from '../_models/world-area';
|
|
import { Plan, PlanElement } from '../_models/plan';
|
|
import { WorldAreaService } from '../_services/world-area.service';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { Fuzzr } from '../fuzzr/fuzzr';
|
|
import { from } from 'rxjs';
|
|
import { save } from '@tauri-apps/api/dialog';
|
|
import { PlanService } from '../_services/plan.service';
|
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
|
import { EditNotesComponentDialog } from './notes/notes.component';
|
|
import { open } from '@tauri-apps/api/dialog';
|
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
import { MatInputModule } from '@angular/material/input';
|
|
import { MatSelectModule } from '@angular/material/select';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
|
|
interface Act {
|
|
value: number;
|
|
name: string;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'plan-editor',
|
|
templateUrl: './editor.component.html',
|
|
styleUrls: ['./editor.component.scss'],
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
CdkDropListGroup,
|
|
CdkDropList,
|
|
CdkDrag,
|
|
MatDialogModule,
|
|
MatFormFieldModule,
|
|
MatInputModule,
|
|
MatSelectModule,
|
|
MatButtonModule,
|
|
MatSlideToggleModule
|
|
],
|
|
})
|
|
export class EditorComponent implements OnInit {
|
|
planInEditing: Plan;
|
|
|
|
areas?: WorldArea[];
|
|
planAreas: WorldArea[];
|
|
areasMap?: Map<String, WorldArea>;
|
|
areaSearchString: string = "";
|
|
planSearchString: string = "";
|
|
planFuzzer: Fuzzr;
|
|
filterAct: Act;
|
|
planFilterAct: Act;
|
|
acts: Act[];
|
|
@ViewChild('planList') planListElement!: ElementRef;
|
|
autoScrollToEnd: boolean;
|
|
reverseDisplay: boolean;
|
|
disabledPlanDD: boolean;
|
|
|
|
|
|
constructor(public worldAreaService: WorldAreaService, private cdr: ChangeDetectorRef, private planService: PlanService, public dialog: MatDialog) {
|
|
this.planInEditing = new Plan({
|
|
plan: [],
|
|
current: 0
|
|
});
|
|
this.disabledPlanDD = false;
|
|
|
|
this.autoScrollToEnd = false;
|
|
|
|
this.planFuzzer = new Fuzzr(this.planInEditing.plan, {
|
|
toString: (e: PlanElement) => {
|
|
return this.areasMap?.get(e.area_key)?.name;
|
|
}
|
|
});
|
|
|
|
this.planAreas = [];
|
|
|
|
this.reverseDisplay = false;
|
|
|
|
this.acts = [];
|
|
for (let i = 0; i <= 10; i++) {
|
|
if (i == 0) {
|
|
this.acts.push({ value: i, name: "All" });
|
|
} else {
|
|
this.acts.push({ value: i, name: "Act " + i.toString() });
|
|
}
|
|
}
|
|
|
|
this.filterAct = this.acts[0];
|
|
this.planFilterAct = this.acts[0];
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.worldAreaService.getWorldAreas().subscribe(worldAreas => {
|
|
this.areas = [...worldAreas.values()];
|
|
this.areasMap = worldAreas;
|
|
});
|
|
}
|
|
|
|
sortPredicate(index: number, _item: CdkDrag<WorldArea> | CdkDrag<PlanElement>) {
|
|
return !(this.planElemFilterBounds() && index == 0)
|
|
}
|
|
|
|
dropHandler(event: CdkDragDrop<WorldArea[]> | CdkDragDrop<PlanElement[]>) {
|
|
if (event.previousContainer === event.container && !isWorldAreaEvent(event)) {
|
|
const realCurrent = this.planInEditing.plan.indexOf(event.previousContainer.data[event.currentIndex]);
|
|
const realPrev = this.planInEditing.plan.indexOf(event.previousContainer.data[event.previousIndex]);
|
|
moveItemInArray(this.planInEditing.plan, realPrev, realCurrent);
|
|
} else
|
|
if (this.planInEditing && this.areas && isWorldAreaEvent(event)) {
|
|
if (event.container.data.length > 0 && 'connections_world_areas_keys' in event.container.data[0]) {
|
|
return;
|
|
}
|
|
|
|
const bounds = this.planElemFilterBounds();
|
|
let index = event.currentIndex;
|
|
if (bounds) {
|
|
index += bounds[0];
|
|
}
|
|
this.planInEditing.plan.splice(index, 0, this.planItemFromArea(event.previousContainer.data[event.previousIndex]));
|
|
}
|
|
}
|
|
|
|
dropEndHandler(event: CdkDragDrop<WorldArea[]> | CdkDragDrop<PlanElement[]>) {
|
|
if (isWorldAreaEvent(event) && this.areas) {
|
|
this.planInEditing.plan.splice(this.getEnd(), 0, this.planItemFromArea(event.previousContainer.data[event.previousIndex]));
|
|
} else {
|
|
moveItemInArray(this.planInEditing.plan, event.previousIndex, this.getEnd());
|
|
}
|
|
|
|
this.scrollToEnd();
|
|
}
|
|
|
|
getEnd() {
|
|
let bounds = this.planElemFilterBounds();
|
|
if (bounds) {
|
|
return bounds[1];
|
|
} else {
|
|
return this.planInEditing.plan.length;
|
|
}
|
|
}
|
|
|
|
remove(item: PlanElement) {
|
|
this.planInEditing.plan.splice(this.planIndexOf(item), 1);
|
|
}
|
|
|
|
canDrop = () => {
|
|
return !this.disabledPlanDD;
|
|
}
|
|
|
|
planItemFromArea(area: WorldArea): PlanElement {
|
|
return {
|
|
area_key: area.named_id,
|
|
notes: undefined,
|
|
uuid: undefined,
|
|
edited: false
|
|
};
|
|
}
|
|
|
|
filterAreas() {
|
|
if (this.areaSearchString !== "" || this.filterAct.value != 0) {
|
|
return this.worldAreaService.matcher!.search(this.areaSearchString).map(({ item }) => {
|
|
return item[1];
|
|
}).filter(item => item.act == this.filterAct.value || this.filterAct.value == 0);
|
|
} else {
|
|
return this.areas!;
|
|
}
|
|
}
|
|
|
|
scrollToEnd() {
|
|
if (!this.autoScrollToEnd) {
|
|
return;
|
|
}
|
|
this.cdr.detectChanges();
|
|
if (!this.reverseDisplay) {
|
|
this.planListElement.nativeElement.scrollTop = this.planListElement.nativeElement.scrollHeight;
|
|
} else {
|
|
this.planListElement.nativeElement.scrollTop = 0;
|
|
}
|
|
}
|
|
|
|
doubleClickArea(item: WorldArea) {
|
|
this.planInEditing.plan.splice(this.planInEditing.plan.length, 0, this.planItemFromArea(item));
|
|
this.scrollToEnd();
|
|
}
|
|
|
|
planElemFilterBounds() {
|
|
if (this.planFilterAct.value !== 0) {
|
|
let bounds = this.planInEditing.plan.filter(item => item.anchor_act === this.planFilterAct.value || this.planFilterAct.value + 1 === item.anchor_act).map((value) => this.planIndexOf(value));
|
|
if (bounds.length == 2) {
|
|
return bounds;
|
|
}
|
|
|
|
if (bounds.length == 1 && this.planFilterAct.value == 10) {
|
|
bounds[1] = this.planInEditing.plan.length;
|
|
return bounds;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
filterPlanElements() {
|
|
const value = (): any[] => {
|
|
if (this.planSearchString !== "") {
|
|
this.disabledPlanDD = true;
|
|
} else {
|
|
this.disabledPlanDD = false;
|
|
}
|
|
|
|
|
|
if (this.planSearchString !== "" || this.planFilterAct.value != 0) {
|
|
let bounds = this.planElemFilterBounds();
|
|
|
|
const searched = this.planFuzzer.search(this.planSearchString).map(({ item }) => item);
|
|
|
|
if (bounds) {
|
|
const definedBounds: number[] = bounds;
|
|
return searched.filter(item => {
|
|
const index = this.planIndexOf(item);
|
|
return index >= definedBounds[0] && index < definedBounds[1];
|
|
});
|
|
} else {
|
|
return searched.filter(item => {
|
|
return this.areasMap?.get(item.area_key)?.act === this.planFilterAct.value || this.planFilterAct.value === 0;
|
|
});
|
|
}
|
|
|
|
} else {
|
|
return this.planInEditing.plan;
|
|
}
|
|
}
|
|
|
|
if (this.reverseDisplay) {
|
|
return value().slice().reverse();
|
|
} else {
|
|
return value();
|
|
}
|
|
}
|
|
|
|
planIndexOf(planElement: PlanElement) {
|
|
const index = this.planInEditing.plan.indexOf(planElement);
|
|
return index;
|
|
}
|
|
|
|
clearPlan() {
|
|
this.planInEditing.plan.length = 0;
|
|
this.cdr.detectChanges();
|
|
}
|
|
|
|
save() {
|
|
from(save({
|
|
filters: [{
|
|
name: 'JSON (.json)',
|
|
extensions: ['json']
|
|
}]
|
|
})).subscribe(file => {
|
|
if (file) {
|
|
this.planService.savePlanAtPath(file, this.planInEditing).subscribe();
|
|
}
|
|
});
|
|
}
|
|
|
|
openPlan() {
|
|
from(open({
|
|
multiple: false,
|
|
filters: [
|
|
{
|
|
name: "JSON (.json)",
|
|
extensions: ['json']
|
|
}
|
|
]
|
|
})).subscribe(file => {
|
|
if (file) {
|
|
// We disallow multiple but interface still says it can be multiple, thus the cast.
|
|
this.planService.loadPlanFromPath(file as string, false).subscribe(plan => {
|
|
this.planInEditing.plan.length = 0;
|
|
plan.plan.forEach(p => this.planInEditing.plan.push(p));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
loadBasePlan() {
|
|
this.planService.getBasePlan().subscribe(plan => {
|
|
this.planInEditing.plan.length = 0;
|
|
plan.plan.forEach(p => this.planInEditing.plan.push(p));
|
|
})
|
|
}
|
|
|
|
addNote(event: MouseEvent, item: PlanElement) {
|
|
event.preventDefault();
|
|
|
|
const dialogRef = this.dialog.open(EditNotesComponentDialog, {
|
|
data: {
|
|
note: item.notes
|
|
},
|
|
disableClose: true
|
|
},)
|
|
|
|
dialogRef.afterClosed().subscribe(note => {
|
|
if (note != undefined && note != null) {
|
|
if (item.notes !== note) {
|
|
item.edited = true;
|
|
}
|
|
item.notes = note;
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
function isWorldAreaEvent(event: any): event is CdkDragDrop<WorldArea[]> {
|
|
return (event.previousContainer.data.length > 0 && 'connections_world_areas_keys' in event.previousContainer.data[0]);
|
|
} |