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 { areas?: WorldArea[]; planAreas: WorldArea[]; plan: Plan; areasMap?: Map; 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.plan = new Plan({ plan: [], current: 0 }); this.disabledPlanDD = false; this.autoScrollToEnd = false; this.planFuzzer = new Fuzzr(this.plan.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; }); } dropHandler(event: CdkDragDrop | CdkDragDrop) { if (event.previousContainer === event.container && !isWorldAreaEvent(event)) { const realCurrent = this.plan.plan.indexOf(event.previousContainer.data[event.currentIndex]); const realPrev = this.plan.plan.indexOf(event.previousContainer.data[event.previousIndex]); moveItemInArray(this.plan.plan, realPrev, realCurrent); } else if (this.plan && this.areas && isWorldAreaEvent(event)) { if (event.container.data.length > 0 && 'connections_world_areas_keys' in event.container.data[0]) { return; } this.plan.plan.splice(event.currentIndex, 0, this.planItemFromArea(event.previousContainer.data[event.previousIndex])); } } dropEndHandler(event: CdkDragDrop | CdkDragDrop) { if (isWorldAreaEvent(event) && this.areas) { this.plan.plan.splice(this.plan.plan.length, 0, this.planItemFromArea(event.previousContainer.data[event.previousIndex])); this.scrollToEnd(); } else { moveItemInArray(this.plan.plan, event.previousIndex, this.plan.plan.length); this.scrollToEnd(); } } remove(item: PlanElement) { this.plan.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.plan.plan.splice(this.plan.plan.length, 0, this.planItemFromArea(item)); this.scrollToEnd(); } filterPlanElements() { const value = (): any[] => { if (this.planSearchString !== "" || this.planFilterAct.value != 0) { this.disabledPlanDD = true; return this.planFuzzer.search(this.planSearchString).map(({ item }) => item).filter(item => { return this.areasMap?.get(item.area_key)?.act === this.planFilterAct.value || this.planFilterAct.value === 0; }); } else { this.disabledPlanDD = false; return this.plan.plan; } } if (this.reverseDisplay) { return value().slice().reverse(); } else { return value(); } } planIndexOf(planElement: PlanElement) { const index = this.plan.plan.indexOf(planElement); return index; } clearPlan() { this.plan.plan.length = 0; this.cdr.detectChanges(); } save() { from(save({ filters: [{ name: 'JSON (.json)', extensions: ['json'] }] })).subscribe(file => { if (file) { this.planService.savePlan(file as string, this.plan); } }); } openPlan() { from(open({ multiple: false, filters: [ { name: "JSON (.json)", extensions: ['json'] } ] })).subscribe(file => { if (file) { this.planService.loadPlanNoSave(file as string).subscribe(plan => { this.plan.plan.length = 0; plan.plan.forEach(p => this.plan.plan.push(p)); }); } }); } loadBasePlan() { this.planService.loadBasePlan().subscribe(plan => { this.plan.plan.length = 0; plan.plan.forEach(p => this.plan.plan.push(p)); }) } addNote(event: MouseEvent, item: PlanElement) { event.preventDefault(); const dialogRef = this.dialog.open(EditNotesComponentDialog, { data: { note: item.notes } }) dialogRef.afterClosed().subscribe(note => { if(note) { if (item.notes !== note) { item.edited = true; } item.notes = note; } }) } } function isWorldAreaEvent(event: any): event is CdkDragDrop { return (event.previousContainer.data.length > 0 && 'connections_world_areas_keys' in event.previousContainer.data[0]); }