From 0d09bd534ac0128ec7a0c86c07a818092eaf5c30 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 17 May 2026 22:12:21 -0500 Subject: [PATCH] add drag and drop to rest of lists --- src/app/app.html | 2 +- .../components/module-list/module-list.html | 12 +- src/app/components/module-list/module-list.ts | 79 +++++---- src/app/components/module/module.html | 123 ++++++++++++++ src/app/components/module/module.ts | 44 +++-- src/app/components/processor/processor.html | 28 +++- src/app/components/route-list/route-list.html | 12 +- src/app/components/route-list/route-list.ts | 33 +++- src/app/components/route/route.html | 156 +++++++++++++++++- src/app/components/route/route.ts | 19 ++- src/app/services/lists.ts | 9 + 11 files changed, 448 insertions(+), 69 deletions(-) diff --git a/src/app/app.html b/src/app/app.html index 90dd221..2bd202a 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -52,7 +52,7 @@
Modules
-
+
@for (module of modules(); track module.id; let i = $index) { -
+
} diff --git a/src/app/components/module-list/module-list.ts b/src/app/components/module-list/module-list.ts index c4f6e98..c8cd391 100644 --- a/src/app/components/module-list/module-list.ts +++ b/src/app/components/module-list/module-list.ts @@ -1,21 +1,32 @@ -import { Component, inject, model } from '@angular/core'; +import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; +import { Component, inject, input, output } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { cloneDeep } from 'lodash-es'; import { ModuleConfig } from '../../models/config'; import { SchemaService } from '../../services/schema'; import { ModuleComponent } from '../module/module'; @Component({ selector: 'app-module-list', - imports: [MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule, ModuleComponent], + imports: [ + MatMenuModule, + MatIconModule, + MatButtonModule, + MatTooltipModule, + ModuleComponent, + CdkDrag, + CdkDropList, + ], templateUrl: './module-list.html', styleUrl: './module-list.css', }) export class ModuleListComponent { - modules = model(); + modules = input(); + updated = output(); public schemaService = inject(SchemaService); private snackBar = inject(MatSnackBar); @@ -25,46 +36,52 @@ export class ModuleListComponent { console.error('module is undefined, not updating'); return; } - this.modules.update((modules) => { - if (modules) { - modules[index].id = module.id; - modules[index].type = module.type; - if (module.params !== undefined) { - modules[index].params = module.params; - } - return [...modules]; - } - return modules; - }); + const currentModules = this.modules(); + if (currentModules === undefined) { + console.error('modules is undefined, cannot update'); + return; + } + currentModules[index] = cloneDeep(module); + this.updated.emit(cloneDeep(currentModules)); } addModule(moduleType: string) { const moduleTemplate = this.schemaService.getSkeletonForModule(moduleType); - this.modules.update((modules) => { - if (modules) { - if (!modules) { - modules = []; - } - modules?.push(moduleTemplate); - return [...modules]; - } - return modules; - }); + const currentModules = this.modules() || []; + if (currentModules === undefined) { + console.error('modules is undefined, cannot update'); + return; + } + currentModules.push(moduleTemplate); + this.updated.emit(cloneDeep(currentModules)); this.snackBar.open('Module Added', 'Dismiss', { duration: 3000, }); } deleteModule(index: number) { - this.modules.update((modules) => { - if (modules) { - modules?.splice(index, 1); - return [...modules]; - } - return []; - }); + const currentModules = this.modules(); + if (currentModules === undefined) { + console.error('modules is undefined, cannot delete'); + return; + } + currentModules.splice(index, 1); + this.updated.emit(cloneDeep(currentModules)); this.snackBar.open('Module Removed', 'Dismiss', { duration: 3000, }); } + + drop(event: CdkDragDrop) { + console.log('Drop event:', event); + if (event.previousContainer === event.container) { + const currentModules = this.modules(); + if (currentModules === undefined) { + console.error('modules is undefined, not updating'); + return; + } + moveItemInArray(currentModules, event.previousIndex, event.currentIndex); + this.updated.emit(cloneDeep(currentModules)); + } + } } diff --git a/src/app/components/module/module.html b/src/app/components/module/module.html index 1eb3a1b..68a135a 100644 --- a/src/app/components/module/module.html +++ b/src/app/components/module/module.html @@ -5,6 +5,14 @@ }} border-solid w-full h-full" >
+ @if (inDragList()) { +
+ drag_indicator +
+ }
keyboard_arrow_down
+ +
+
+
+ @if (inDragList()) { +
+ drag_indicator +
+ } +
+ keyboard_arrow_down + keyboard_arrow_up +
+
+ {{ schema().title || module()?.type }} +
+
+
+ close +
+
+
+
+ +
+
+
+ @if (inDragList()) { +
+ drag_indicator +
+ } +
+ keyboard_arrow_down + keyboard_arrow_up +
+
+ {{ schema().title || module()?.type }} +
+
+
+ close +
+
+
+
+
+
+
+ + + abc + @if (!formGroup.controls['id'].valid) { + +
{{ formGroup.controls['id'].errors | json }}
+ } +
+ +
+
+
+
+ @if (moduleConfigErrors().length > 0) { +
+
+ error + Module Errors +
+ @for (error of moduleConfigErrors(); track $index) { +
+ {{ error.error }} +
+ } +
+ } +
+
+
} diff --git a/src/app/components/module/module.ts b/src/app/components/module/module.ts index 48bd669..33dbac2 100644 --- a/src/app/components/module/module.ts +++ b/src/app/components/module/module.ts @@ -1,10 +1,12 @@ +import { CdkDragHandle, CdkDragPlaceholder, CdkDragPreview } from '@angular/cdk/drag-drop'; import { JsonPipe } from '@angular/common'; -import { Component, computed, inject, input, model, output, signal } from '@angular/core'; +import { Component, computed, inject, input, output, signal } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { cloneDeep } from 'lodash-es'; import { debounceTime, tap } from 'rxjs'; import { ModuleConfig } from '../../models/config'; import { ConfigService } from '../../services/config'; @@ -22,6 +24,9 @@ import { ParamsFormComponent } from '../params-form/params-form'; MatMenuModule, MatTooltipModule, JsonPipe, + CdkDragHandle, + CdkDragPreview, + CdkDragPlaceholder, ], templateUrl: './module.html', styleUrl: './module.css', @@ -38,8 +43,10 @@ export class ModuleComponent { return undefined; }); - module = model(); + inDragList = input(false); + module = input(); delete = output(); + updated = output(); params = computed(() => this.module()!.params); id = computed(() => this.module()!.id); @@ -77,18 +84,12 @@ export class ModuleComponent { }); this.formGroup.valueChanges.subscribe((value) => { - this.module.update((module) => { - if (module) { - module.id = value.id; - module.type = value.type; - return { - ...module, - id: module.id, - type: module.type, - }; - } - return undefined; - }); + const currentModule = this.module(); + if (currentModule) { + currentModule.id = value.id; + currentModule.type = value.type; + this.updated.emit(cloneDeep(currentModule)); + } }); if (this.id() !== undefined) { this.eventsService @@ -117,16 +118,11 @@ export class ModuleComponent { } paramsUpdated(params: any) { - this.module.update((module) => { - if (module !== undefined && module.params !== undefined) { - module.params = params; - return { - ...module, - params: module.params, - }; - } - return undefined; - }); + const currentModule = this.module(); + if (currentModule && currentModule.params !== undefined) { + currentModule.params = params; + this.updated.emit(cloneDeep(currentModule)); + } } deleteMe() { diff --git a/src/app/components/processor/processor.html b/src/app/components/processor/processor.html index d1f2b72..51dba9c 100644 --- a/src/app/components/processor/processor.html +++ b/src/app/components/processor/processor.html @@ -63,13 +63,37 @@
-
+ @if (inDragList()) { +
+ drag_indicator +
+ } +
{{ schema()?.title || processor()?.type }}
+
+ close +
+ @if (hasParams()) { +
+
+ +
+ }
} diff --git a/src/app/components/route-list/route-list.html b/src/app/components/route-list/route-list.html index 6c054db..565d87f 100644 --- a/src/app/components/route-list/route-list.html +++ b/src/app/components/route-list/route-list.html @@ -9,9 +9,14 @@
Routes
-
- @for (route of routes(); track $index; let i = $index) { -
+
+ @for (route of routes(); track route; let i = $index) { +
} diff --git a/src/app/components/route-list/route-list.ts b/src/app/components/route-list/route-list.ts index 821b452..738386b 100644 --- a/src/app/components/route-list/route-list.ts +++ b/src/app/components/route-list/route-list.ts @@ -1,4 +1,11 @@ -import { transferArrayItem } from '@angular/cdk/drag-drop'; +import { A11yModule } from '@angular/cdk/a11y'; +import { + CdkDrag, + CdkDragDrop, + CdkDropList, + moveItemInArray, + transferArrayItem, +} from '@angular/cdk/drag-drop'; import { Component, inject, input, output } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; @@ -12,7 +19,16 @@ import { RouteComponent } from '../route/route'; @Component({ selector: 'app-route-list', - imports: [RouteComponent, MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule], + imports: [ + RouteComponent, + MatMenuModule, + MatIconModule, + MatButtonModule, + MatTooltipModule, + CdkDrag, + CdkDropList, + A11yModule, + ], templateUrl: './route-list.html', styleUrl: './route-list.css', }) @@ -103,4 +119,17 @@ export class RouteListComponent { this.updated.emit(cloneDeep(routes)); } + + drop(event: CdkDragDrop) { + console.log('Drop event:', event); + if (event.previousContainer === event.container) { + const currentRoutes = this.routes(); + if (currentRoutes === undefined) { + console.error('routes is undefined, not updating'); + return; + } + moveItemInArray(currentRoutes, event.previousIndex, event.currentIndex); + this.updated.emit(cloneDeep(currentRoutes)); + } + } } diff --git a/src/app/components/route/route.html b/src/app/components/route/route.html index 55b27ae..68c01ce 100644 --- a/src/app/components/route/route.html +++ b/src/app/components/route/route.html @@ -6,6 +6,14 @@ }} border-solid w-full" >
+ @if (inDragList()) { +
+ drag_indicator +
+ }
chevron_right - @for (processor of processors(); track $index; let i = $index) { + @for (processor of processors(); track processor; let i = $index) {
} + +
+
+
+ @if (inDragList()) { +
+ drag_indicator +
+ } +
+ chevron_right +
+
Route
+
+ close +
+
+
+
+ +
+
+
+ @if (inDragList()) { +
+ drag_indicator +
+ } +
+ chevron_right +
+
Route
+
+ close +
+
+
+
+
+
+
+ + + @if (!formGroup.controls['input'].valid) { + +
{{ formGroup.controls['input'].errors | json }}
+ } +
+
+
+
+
+ @if (schemaService.processorTypes.length > 0) { +
+ add +
+ + @for (processorType of schemaService.processorTypes; track processorType) { + + } + + } +
Processors
+
+ @if (processors() !== undefined && processors()!.length > 0) { +
+ @for (processor of processors(); track processor; let i = $index) { +
+ +
+ } +
+ } +
+
+
+ @if (routeConfigErrors().length > 0) { +
+
+ error + Route Errors +
+ @for (error of routeConfigErrors(); track error.index) { +
+ {{ error.error }} +
+ } +
+ } +
+
+
} diff --git a/src/app/components/route/route.ts b/src/app/components/route/route.ts index 454e178..73b301d 100644 --- a/src/app/components/route/route.ts +++ b/src/app/components/route/route.ts @@ -1,4 +1,11 @@ -import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; +import { + CdkDrag, + CdkDragDrop, + CdkDragPlaceholder, + CdkDragPreview, + CdkDropList, + moveItemInArray, +} from '@angular/cdk/drag-drop'; import { JsonPipe } from '@angular/common'; import { Component, computed, inject, input, output, signal } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; @@ -28,6 +35,8 @@ import { ProcessorComponent } from '../processor/processor'; MatTooltipModule, CdkDrag, CdkDropList, + CdkDragPreview, + CdkDragPlaceholder, ], templateUrl: './route.html', styleUrl: './route.css', @@ -45,6 +54,7 @@ export class RouteComponent { return undefined; }); + inDragList = input(false); route = input(); moduleIds = input([]); delete = output(); @@ -83,8 +93,14 @@ export class RouteComponent { private configService = inject(ConfigService); public listsService = inject(ListsService); + public processorListId = signal(''); + indicatorColor = signal('gray'); + ngOnDestroy(): void { + this.listsService.removeProcessorList(this.path() + '/processors'); + } + ngOnInit(): void { this.formGroup.patchValue({ input: this.route()?.input, @@ -115,6 +131,7 @@ export class RouteComponent { this.indicatorColor.set('gray'); }); } + this.processorListId.set(this.listsService.registerProcessorList(this.path() + '/processors')); } isInError(): boolean { diff --git a/src/app/services/lists.ts b/src/app/services/lists.ts index e6da014..f65c830 100644 --- a/src/app/services/lists.ts +++ b/src/app/services/lists.ts @@ -18,6 +18,15 @@ export class ListsService { return id; } + removeProcessorList(path: string | undefined) { + if (path === undefined) { + return; + } + + const id = this.pathToId(path); + this.processorListIds = this.processorListIds.filter((listId) => listId !== id); + } + pathToId(path: string) { return path.replaceAll('/', '.'); }