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.
Nothing/src/app/editor/editor.component.ts

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]);
}