allow processors to be moved across routes

This commit is contained in:
Joel Wetzell
2026-05-17 20:54:29 -05:00
parent 623838de3e
commit 3f0c0cf45c
6 changed files with 173 additions and 100 deletions
+1 -1
View File
@@ -57,7 +57,7 @@
<app-route-list <app-route-list
[routes]="config()?.routes" [routes]="config()?.routes"
[moduleIds]="moduleIds()" [moduleIds]="moduleIds()"
(routesChange)="routesUpdated($event)" (updated)="routesUpdated($event)"
></app-route-list> ></app-route-list>
</div> </div>
<div class="w-1/2 overflow-y-hidden"> <div class="w-1/2 overflow-y-hidden">
+17 -15
View File
@@ -1,5 +1,5 @@
import { CdkDragHandle, CdkDragPlaceholder, CdkDragPreview } from '@angular/cdk/drag-drop'; import { CdkDragHandle, CdkDragPlaceholder, CdkDragPreview } from '@angular/cdk/drag-drop';
import { Component, computed, inject, input, model, output } from '@angular/core'; import { Component, computed, inject, input, output } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { ProcessorConfig } from '../../models/config'; import { ProcessorConfig } from '../../models/config';
@@ -21,7 +21,7 @@ import { ParamsFormComponent } from '../params-form/params-form';
}) })
export class ProcessorComponent { export class ProcessorComponent {
path = input<string>(''); path = input<string>('');
processor = model<ProcessorConfig>(); processor = input<ProcessorConfig>();
delete = output<void>(); delete = output<void>();
inDragList = input<boolean>(false); inDragList = input<boolean>(false);
@@ -34,6 +34,8 @@ export class ProcessorComponent {
: undefined; : undefined;
}); });
updated = output<ProcessorConfig>();
hasParams = computed(() => { hasParams = computed(() => {
const schema = this.schema(); const schema = this.schema();
return ( return (
@@ -45,20 +47,20 @@ export class ProcessorComponent {
private schemaService = inject(SchemaService); private schemaService = inject(SchemaService);
paramsUpdated(params: any) { paramsUpdated(params: any) {
this.processor.update((processor) => { console.log('Params updated:', params);
if (processor !== undefined) { const currentProcessor = this.processor();
if (params !== undefined) { if (currentProcessor !== undefined) {
return { if (params !== undefined) {
...processor, this.updated.emit({
params: params, ...currentProcessor,
}; params: params,
} });
return { } else {
...processor, this.updated.emit({
}; ...currentProcessor,
});
} }
return undefined; }
});
} }
deleteMe() { deleteMe() {
@@ -14,10 +14,11 @@
<div class="m-2"> <div class="m-2">
<app-route <app-route
[path]="`routes/${i}`" [path]="`routes/${i}`"
(routeChange)="routeUpdated(i, $event)" (updated)="routeUpdated(i, $event)"
[route]="route" [route]="route"
[moduleIds]="moduleIds() || []" [moduleIds]="moduleIds() || []"
(delete)="deleteRoute(i)" (delete)="deleteRoute(i)"
(moveProcessor)="moveProcessorBetweenRoutes($event)"
></app-route> ></app-route>
</div> </div>
} }
+65 -26
View File
@@ -1,9 +1,11 @@
import { Component, inject, input, model } from '@angular/core'; import { transferArrayItem } from '@angular/cdk/drag-drop';
import { Component, inject, input, output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { cloneDeep } from 'lodash-es';
import { RouteConfig } from '../../models/config'; import { RouteConfig } from '../../models/config';
import { SchemaService } from '../../services/schema'; import { SchemaService } from '../../services/schema';
import { RouteComponent } from '../route/route'; import { RouteComponent } from '../route/route';
@@ -15,20 +17,21 @@ import { RouteComponent } from '../route/route';
styleUrl: './route-list.css', styleUrl: './route-list.css',
}) })
export class RouteListComponent { export class RouteListComponent {
routes = model<RouteConfig[]>(); routes = input<RouteConfig[]>();
moduleIds = input<string[]>(); moduleIds = input<string[]>();
updated = output<RouteConfig[]>();
public schemaService = inject(SchemaService); public schemaService = inject(SchemaService);
private snackBar = inject(MatSnackBar); private snackBar = inject(MatSnackBar);
deleteRoute(index: number) { deleteRoute(index: number) {
this.routes.update((routes) => { const currentRoutes = this.routes();
if (routes) { if (currentRoutes === undefined) {
routes?.splice(index, 1); console.error('routes is undefined, cannot delete');
return [...routes]; return;
} }
return routes; currentRoutes.splice(index, 1);
}); this.updated.emit(cloneDeep(currentRoutes));
this.snackBar.open('Route Removed', 'Dismiss', { this.snackBar.open('Route Removed', 'Dismiss', {
duration: 3000, duration: 3000,
}); });
@@ -39,29 +42,65 @@ export class RouteListComponent {
console.error('route is undefined, not updating'); console.error('route is undefined, not updating');
return; return;
} }
this.routes.update((routes) => { const currentRoutes = this.routes();
if (routes) { if (currentRoutes === undefined) {
routes[index].input = route.input; console.error('routes is undefined, cannot update');
if (route.processors) { return;
routes[index].processors = [...route.processors]; }
} currentRoutes[index] = cloneDeep(route);
return [...routes]; this.updated.emit(cloneDeep(currentRoutes));
}
return routes;
});
} }
addRoute() { addRoute() {
const routeTemplate = this.schemaService.getSkeletonForRoute(); const routeTemplate = this.schemaService.getSkeletonForRoute();
this.routes.update((routes) => { const currentRoutes = this.routes() || [];
if (!routes) { if (currentRoutes === undefined) {
routes = []; console.error('routes is undefined, cannot update');
} return;
routes?.push(routeTemplate); }
return [...routes]; currentRoutes.push(routeTemplate);
}); this.updated.emit(cloneDeep(currentRoutes));
this.snackBar.open('Route Added', 'Dismiss', { this.snackBar.open('Route Added', 'Dismiss', {
duration: 3000, duration: 3000,
}); });
} }
moveProcessorBetweenRoutes(event: {
fromRouteIndex: number;
toRouteIndex: number;
fromProcessorIndex: number;
toProcessorIndex: number;
}) {
if (event.fromRouteIndex === event.toRouteIndex) {
console.error(
'this should be handled by the route component, not moving processor between routes',
);
return;
}
const routes = this.routes();
if (routes === undefined) {
console.error('routes is undefined, cannot move processor');
return;
}
const fromRoute = routes[event.fromRouteIndex];
const toRoute = routes[event.toRouteIndex];
if (fromRoute === undefined || toRoute === undefined) {
console.error('fromRoute or toRoute is undefined, cannot move processor');
return;
}
const fromProcessors = fromRoute.processors;
const toProcessors = toRoute.processors;
if (fromProcessors === undefined || toProcessors === undefined) {
console.error('fromProcessors or toProcessors is undefined, cannot move processor');
return;
}
transferArrayItem(
fromProcessors,
toProcessors,
event.fromProcessorIndex,
event.toProcessorIndex,
);
this.updated.emit(cloneDeep(routes));
}
} }
+3 -1
View File
@@ -66,6 +66,8 @@
<div <div
class="border-2 border-gray-500 m-2" class="border-2 border-gray-500 m-2"
cdkDropList cdkDropList
[id]="listsService.registerProcessorList(path() + '/processors')"
[cdkDropListConnectedTo]="listsService.processorListIds"
(cdkDropListDropped)="drop($event)" (cdkDropListDropped)="drop($event)"
[cdkDropListData]="route()?.processors" [cdkDropListData]="route()?.processors"
> >
@@ -73,7 +75,7 @@
<div cdkDrag> <div cdkDrag>
<app-processor <app-processor
[path]="path() + '/processors/' + i" [path]="path() + '/processors/' + i"
(processorChange)="processorUpdated(i, $event)" (updated)="processorUpdated(i, $event)"
[processor]="processor" [processor]="processor"
(delete)="deleteProcessor(i)" (delete)="deleteProcessor(i)"
[inDragList]="true" [inDragList]="true"
+85 -56
View File
@@ -1,12 +1,13 @@
import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { JsonPipe } from '@angular/common'; 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 { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { cloneDeep } from 'lodash-es';
import { debounceTime, tap } from 'rxjs'; import { debounceTime, tap } from 'rxjs';
import { ProcessorConfig, RouteConfig } from '../../models/config'; import { ProcessorConfig, RouteConfig } from '../../models/config';
import { ConfigService } from '../../services/config'; import { ConfigService } from '../../services/config';
@@ -44,9 +45,16 @@ export class RouteComponent {
return undefined; return undefined;
}); });
route = model<RouteConfig>(); route = input<RouteConfig>();
moduleIds = input<string[]>([]); moduleIds = input<string[]>([]);
delete = output<void>(); delete = output<void>();
updated = output<RouteConfig>();
moveProcessor = output<{
fromRouteIndex: number;
toRouteIndex: number;
fromProcessorIndex: number;
toProcessorIndex: number;
}>();
processors = computed(() => { processors = computed(() => {
const route = this.route(); const route = this.route();
@@ -83,15 +91,14 @@ export class RouteComponent {
}); });
this.formGroup.valueChanges.subscribe((value) => { this.formGroup.valueChanges.subscribe((value) => {
this.route.update((route) => { const currentRoute = this.route();
if (route) { if (currentRoute === undefined) {
route.input = value.input; console.error('route is undefined, not updating');
return { return;
...route, }
input: route.input, this.updated.emit({
}; ...currentRoute,
} input: value.input,
return undefined;
}); });
}); });
@@ -120,18 +127,18 @@ export class RouteComponent {
addProcessor(processorType: string) { addProcessor(processorType: string) {
const processorTemplate = this.schemaService.getSkeletonForProcessor(processorType); const processorTemplate = this.schemaService.getSkeletonForProcessor(processorType);
this.route.update((route) => { const currentRoute = this.route();
if (route) { if (currentRoute === undefined) {
const processors = route.processors || []; console.error('route is undefined, not updating');
return;
processors.push(processorTemplate); }
return { const processors = currentRoute.processors || [];
...route, processors.push(processorTemplate);
processors: [...processors], this.updated.emit({
}; ...currentRoute,
} processors: cloneDeep(processors),
return route;
}); });
this.snackBar.open('Processor Added', 'Dismiss', { this.snackBar.open('Processor Added', 'Dismiss', {
duration: 3000, duration: 3000,
}); });
@@ -142,53 +149,75 @@ export class RouteComponent {
console.error('processor is undefined, not updating'); console.error('processor is undefined, not updating');
return; return;
} }
this.route.update((route) => { const currentRoute = this.route();
if (route && route.processors) { if (currentRoute === undefined) {
route.processors[index].type = processor.type; console.error('route is undefined, not updating');
if (processor.params !== undefined) { return;
route.processors[index].params = processor.params; }
} if (currentRoute.processors === undefined) {
return { console.error('route processors is undefined, not updating');
...route, return;
processors: [...route.processors], }
}; currentRoute.processors[index].type = processor.type;
} if (processor.params !== undefined) {
return route; currentRoute.processors[index].params = processor.params;
}
this.updated.emit({
...currentRoute,
processors: cloneDeep(currentRoute.processors),
}); });
} }
deleteProcessor(index: number) { deleteProcessor(index: number) {
this.route.update((route) => { const currentRoute = this.route();
if (route && route.processors) { if (currentRoute === undefined) {
route?.processors?.splice(index, 1); console.error('route is undefined, not updating');
return { return;
...route, }
processors: [...route.processors], if (currentRoute.processors === undefined) {
}; console.error('route processors is undefined, not updating');
} return;
return route; }
currentRoute.processors.splice(index, 1);
this.updated.emit({
...currentRoute,
processors: cloneDeep(currentRoute.processors),
}); });
this.snackBar.open('Processor Removed', 'Dismiss', { this.snackBar.open('Processor Removed', 'Dismiss', {
duration: 3000, duration: 3000,
}); });
} }
drop(event: CdkDragDrop<ProcessorConfig[] | undefined>) { drop(event: CdkDragDrop<ProcessorConfig[] | undefined>) {
// TODO(jwetzell): support moving between routes
if (event.previousContainer === event.container) { if (event.previousContainer === event.container) {
const processors = this.route()?.processors; const currentRoute = this.route();
if (processors !== undefined) { if (currentRoute === undefined) {
moveItemInArray(processors, event.previousIndex, event.currentIndex); console.error('route is undefined, not updating');
this.route.update((route) => { return;
if (route) {
return {
...route,
processors: [...processors],
};
}
return route;
});
} }
const processors = currentRoute.processors;
if (processors === undefined) {
console.error('route processors is undefined, cannot move processor');
return;
}
moveItemInArray(processors, event.previousIndex, event.currentIndex);
this.updated.emit({
...currentRoute,
processors: cloneDeep(processors),
});
} else {
const fromRouteIndex = parseInt(event.previousContainer.id.split('.')[1], 10);
const toRouteIndex = parseInt(event.container.id.split('.')[1], 10);
const fromProcessorIndex = event.previousIndex;
const toProcessorIndex = event.currentIndex;
this.moveProcessor.emit({
fromRouteIndex,
toRouteIndex,
fromProcessorIndex,
toProcessorIndex,
});
} }
} }
} }