3 Commits

Author SHA1 Message Date
Joel Wetzell 55267ab4db 0.14.0 2026-05-17 22:12:41 -05:00
Joel Wetzell 0d09bd534a add drag and drop to rest of lists 2026-05-17 22:12:21 -05:00
Joel Wetzell a433b0e036 change job name 2026-05-17 21:01:23 -05:00
14 changed files with 452 additions and 73 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ on:
tags: tags:
- 'v*' - 'v*'
jobs: jobs:
build-publish: build-webui:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository - name: Check out repository
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@showbridge/webui", "name": "@showbridge/webui",
"version": "0.13.1", "version": "0.14.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@showbridge/webui", "name": "@showbridge/webui",
"version": "0.13.1", "version": "0.14.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@angular/cdk": "21.2.11", "@angular/cdk": "21.2.11",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@showbridge/webui", "name": "@showbridge/webui",
"version": "0.13.1", "version": "0.14.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
+1 -1
View File
@@ -52,7 +52,7 @@
<div class="grow overflow-y-auto"> <div class="grow overflow-y-auto">
<app-module-list <app-module-list
[modules]="config()?.modules" [modules]="config()?.modules"
(modulesChange)="modulesUpdated($event)" (updated)="modulesUpdated($event)"
></app-module-list> ></app-module-list>
<app-route-list <app-route-list
[routes]="config()?.routes" [routes]="config()?.routes"
@@ -18,14 +18,20 @@
} }
<div class="text-white">Modules</div> <div class="text-white">Modules</div>
</div> </div>
<div class="grow overflow-x-hidden overflow-y-auto gap-1.5"> <div
class="grow overflow-x-hidden overflow-y-auto gap-1.5"
cdkDropList
(cdkDropListDropped)="drop($event)"
[cdkDropListData]="modules()"
>
@for (module of modules(); track module.id; let i = $index) { @for (module of modules(); track module.id; let i = $index) {
<div class="m-2"> <div class="m-2" cdkDrag>
<app-module <app-module
[path]="`modules/${i}`" [path]="`modules/${i}`"
(moduleChange)="moduleUpdated(i, $event)"
[module]="module" [module]="module"
(updated)="moduleUpdated(i, $event)"
(delete)="deleteModule(i)" (delete)="deleteModule(i)"
[inDragList]="true"
></app-module> ></app-module>
</div> </div>
} }
+48 -31
View File
@@ -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 { 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 { ModuleConfig } from '../../models/config'; import { ModuleConfig } from '../../models/config';
import { SchemaService } from '../../services/schema'; import { SchemaService } from '../../services/schema';
import { ModuleComponent } from '../module/module'; import { ModuleComponent } from '../module/module';
@Component({ @Component({
selector: 'app-module-list', selector: 'app-module-list',
imports: [MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule, ModuleComponent], imports: [
MatMenuModule,
MatIconModule,
MatButtonModule,
MatTooltipModule,
ModuleComponent,
CdkDrag,
CdkDropList,
],
templateUrl: './module-list.html', templateUrl: './module-list.html',
styleUrl: './module-list.css', styleUrl: './module-list.css',
}) })
export class ModuleListComponent { export class ModuleListComponent {
modules = model<ModuleConfig[]>(); modules = input<ModuleConfig[]>();
updated = output<ModuleConfig[]>();
public schemaService = inject(SchemaService); public schemaService = inject(SchemaService);
private snackBar = inject(MatSnackBar); private snackBar = inject(MatSnackBar);
@@ -25,46 +36,52 @@ export class ModuleListComponent {
console.error('module is undefined, not updating'); console.error('module is undefined, not updating');
return; return;
} }
this.modules.update((modules) => { const currentModules = this.modules();
if (modules) { if (currentModules === undefined) {
modules[index].id = module.id; console.error('modules is undefined, cannot update');
modules[index].type = module.type; return;
if (module.params !== undefined) { }
modules[index].params = module.params; currentModules[index] = cloneDeep(module);
} this.updated.emit(cloneDeep(currentModules));
return [...modules];
}
return modules;
});
} }
addModule(moduleType: string) { addModule(moduleType: string) {
const moduleTemplate = this.schemaService.getSkeletonForModule(moduleType); const moduleTemplate = this.schemaService.getSkeletonForModule(moduleType);
this.modules.update((modules) => { const currentModules = this.modules() || [];
if (modules) { if (currentModules === undefined) {
if (!modules) { console.error('modules is undefined, cannot update');
modules = []; return;
} }
modules?.push(moduleTemplate); currentModules.push(moduleTemplate);
return [...modules]; this.updated.emit(cloneDeep(currentModules));
}
return modules;
});
this.snackBar.open('Module Added', 'Dismiss', { this.snackBar.open('Module Added', 'Dismiss', {
duration: 3000, duration: 3000,
}); });
} }
deleteModule(index: number) { deleteModule(index: number) {
this.modules.update((modules) => { const currentModules = this.modules();
if (modules) { if (currentModules === undefined) {
modules?.splice(index, 1); console.error('modules is undefined, cannot delete');
return [...modules]; return;
} }
return []; currentModules.splice(index, 1);
}); this.updated.emit(cloneDeep(currentModules));
this.snackBar.open('Module Removed', 'Dismiss', { this.snackBar.open('Module Removed', 'Dismiss', {
duration: 3000, duration: 3000,
}); });
} }
drop(event: CdkDragDrop<ModuleConfig[] | undefined>) {
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));
}
}
} }
+123
View File
@@ -5,6 +5,14 @@
}} border-solid w-full h-full" }} border-solid w-full h-full"
> >
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
@if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="flex items-center"> <div class="flex items-center">
<mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator" <mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator"
>keyboard_arrow_down</mat-icon >keyboard_arrow_down</mat-icon
@@ -66,4 +74,119 @@
} }
</div> </div>
</div> </div>
<div *cdkDragPreview class="flex items-start m-2 w-fit">
<div
class="flex flex-col bg-gray-800 border-2 {{
isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-full h-full"
>
<div class="flex items-center justify-center">
@if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="flex items-center">
<mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator"
>keyboard_arrow_down</mat-icon
>
<mat-icon [style.color]="outputIndicatorColor()" matTooltip="Output Indicator"
>keyboard_arrow_up</mat-icon
>
</div>
<div class="text-white">
{{ schema().title || module()?.type }}
</div>
<div class="grow"></div>
<div
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
(click)="delete.emit()"
>
<mat-icon class="text-red-500!">close</mat-icon>
</div>
</div>
</div>
</div>
<div *cdkDragPlaceholder class="flex items-start m-2 w-fit">
<div
class="flex flex-col bg-gray-800 border-2 {{
isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-full h-full opacity-30"
>
<div class="flex items-center justify-center">
@if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="flex items-center">
<mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator"
>keyboard_arrow_down</mat-icon
>
<mat-icon [style.color]="outputIndicatorColor()" matTooltip="Output Indicator"
>keyboard_arrow_up</mat-icon
>
</div>
<div class="text-white">
{{ schema().title || module()?.type }}
</div>
<div class="grow"></div>
<div
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
(click)="delete.emit()"
>
<mat-icon class="text-red-500!">close</mat-icon>
</div>
</div>
<hr class="bg-gray-600 h-0.5 border-0" />
<div>
<form [formGroup]="formGroup" class="h-full">
<div class="m-2">
<div class="flex items-center m-1">
<label class="mr-2 text-white"> ID: </label>
<input
type="text"
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[placeholder]="'module-1'"
[formControlName]="'id'"
/>
<mat-icon class="text-white!" matTooltip="String">abc</mat-icon>
@if (!formGroup.controls['id'].valid) {
<!-- TODO(jwetzell): better error displaying -->
<div class="ml-2 text-red-500">{{ formGroup.controls['id'].errors | json }}</div>
}
</div>
<app-params-form
[paramsSchema]="schema()?.properties?.params"
[data]="params()"
(updated)="paramsUpdated($event)"
></app-params-form>
</div>
</form>
</div>
<div>
@if (moduleConfigErrors().length > 0) {
<div class="m-2 p-2 border-2 border-red-500">
<div class="flex items-center mb-2 text-red-500">
<mat-icon>error</mat-icon>
<span class="ml-1">Module Errors</span>
</div>
@for (error of moduleConfigErrors(); track $index) {
<div class="ml-4 mb-1 text-white">
{{ error.error }}
</div>
}
</div>
}
</div>
</div>
</div>
} }
+20 -24
View File
@@ -1,10 +1,12 @@
import { CdkDragHandle, CdkDragPlaceholder, CdkDragPreview } 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 { 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 { ModuleConfig } from '../../models/config'; import { ModuleConfig } from '../../models/config';
import { ConfigService } from '../../services/config'; import { ConfigService } from '../../services/config';
@@ -22,6 +24,9 @@ import { ParamsFormComponent } from '../params-form/params-form';
MatMenuModule, MatMenuModule,
MatTooltipModule, MatTooltipModule,
JsonPipe, JsonPipe,
CdkDragHandle,
CdkDragPreview,
CdkDragPlaceholder,
], ],
templateUrl: './module.html', templateUrl: './module.html',
styleUrl: './module.css', styleUrl: './module.css',
@@ -38,8 +43,10 @@ export class ModuleComponent {
return undefined; return undefined;
}); });
module = model<ModuleConfig>(); inDragList = input<boolean>(false);
module = input<ModuleConfig>();
delete = output<void>(); delete = output<void>();
updated = output<ModuleConfig>();
params = computed(() => this.module()!.params); params = computed(() => this.module()!.params);
id = computed(() => this.module()!.id); id = computed(() => this.module()!.id);
@@ -77,18 +84,12 @@ export class ModuleComponent {
}); });
this.formGroup.valueChanges.subscribe((value) => { this.formGroup.valueChanges.subscribe((value) => {
this.module.update((module) => { const currentModule = this.module();
if (module) { if (currentModule) {
module.id = value.id; currentModule.id = value.id;
module.type = value.type; currentModule.type = value.type;
return { this.updated.emit(cloneDeep(currentModule));
...module, }
id: module.id,
type: module.type,
};
}
return undefined;
});
}); });
if (this.id() !== undefined) { if (this.id() !== undefined) {
this.eventsService this.eventsService
@@ -117,16 +118,11 @@ export class ModuleComponent {
} }
paramsUpdated(params: any) { paramsUpdated(params: any) {
this.module.update((module) => { const currentModule = this.module();
if (module !== undefined && module.params !== undefined) { if (currentModule && currentModule.params !== undefined) {
module.params = params; currentModule.params = params;
return { this.updated.emit(cloneDeep(currentModule));
...module, }
params: module.params,
};
}
return undefined;
});
} }
deleteMe() { deleteMe() {
+26 -2
View File
@@ -63,13 +63,37 @@
<div <div
class="flex flex-col bg-gray-800 border-2 {{ class="flex flex-col bg-gray-800 border-2 {{
isInError() ? 'border-red-500' : 'border-gray-600' isInError() ? 'border-red-500' : 'border-gray-600'
}} border-dotted w-fit h-full" }} border-solid w-fit h-full opacity-30"
> >
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div class="grow ml-1 mr-3 text-transparent"> @if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="grow ml-1 mr-3 text-white">
{{ schema()?.title || processor()?.type }} {{ schema()?.title || processor()?.type }}
</div> </div>
<div
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
(click)="delete.emit()"
>
<mat-icon class="text-red-500!">close</mat-icon>
</div>
</div> </div>
@if (hasParams()) {
<hr class="bg-gray-600 h-0.5 border-0" />
<div>
<app-params-form
[paramsSchema]="schema()?.properties?.params"
[data]="params()"
(updated)="paramsUpdated($event)"
></app-params-form>
</div>
}
</div> </div>
</div> </div>
} }
@@ -9,9 +9,14 @@
</div> </div>
<div class="text-white">Routes</div> <div class="text-white">Routes</div>
</div> </div>
<div class="grow overflow-x-hidden overflow-y-auto gap-1.5"> <div
@for (route of routes(); track $index; let i = $index) { class="grow overflow-x-hidden overflow-y-auto gap-1.5"
<div class="m-2"> cdkDropList
(cdkDropListDropped)="drop($event)"
[cdkDropListData]="routes()"
>
@for (route of routes(); track route; let i = $index) {
<div class="m-2" cdkDrag>
<app-route <app-route
[path]="`routes/${i}`" [path]="`routes/${i}`"
(updated)="routeUpdated(i, $event)" (updated)="routeUpdated(i, $event)"
@@ -19,6 +24,7 @@
[moduleIds]="moduleIds() || []" [moduleIds]="moduleIds() || []"
(delete)="deleteRoute(i)" (delete)="deleteRoute(i)"
(moveProcessor)="moveProcessorBetweenRoutes($event)" (moveProcessor)="moveProcessorBetweenRoutes($event)"
[inDragList]="true"
></app-route> ></app-route>
</div> </div>
} }
+31 -2
View File
@@ -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 { 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';
@@ -12,7 +19,16 @@ import { RouteComponent } from '../route/route';
@Component({ @Component({
selector: 'app-route-list', selector: 'app-route-list',
imports: [RouteComponent, MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule], imports: [
RouteComponent,
MatMenuModule,
MatIconModule,
MatButtonModule,
MatTooltipModule,
CdkDrag,
CdkDropList,
A11yModule,
],
templateUrl: './route-list.html', templateUrl: './route-list.html',
styleUrl: './route-list.css', styleUrl: './route-list.css',
}) })
@@ -103,4 +119,17 @@ export class RouteListComponent {
this.updated.emit(cloneDeep(routes)); this.updated.emit(cloneDeep(routes));
} }
drop(event: CdkDragDrop<RouteConfig[] | undefined>) {
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));
}
}
} }
+154 -2
View File
@@ -6,6 +6,14 @@
}} border-solid w-full" }} border-solid w-full"
> >
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
@if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="flex items-center"> <div class="flex items-center">
<mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator" <mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator"
>chevron_right</mat-icon >chevron_right</mat-icon
@@ -66,12 +74,12 @@
<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')" [id]="processorListId()"
[cdkDropListConnectedTo]="listsService.processorListIds" [cdkDropListConnectedTo]="listsService.processorListIds"
(cdkDropListDropped)="drop($event)" (cdkDropListDropped)="drop($event)"
[cdkDropListData]="route()?.processors" [cdkDropListData]="route()?.processors"
> >
@for (processor of processors(); track $index; let i = $index) { @for (processor of processors(); track processor; let i = $index) {
<div cdkDrag> <div cdkDrag>
<app-processor <app-processor
[path]="path() + '/processors/' + i" [path]="path() + '/processors/' + i"
@@ -115,4 +123,148 @@
</div> </div>
} }
</ng-template> </ng-template>
<div *cdkDragPreview class="flex items-start m-2 w-fit">
<div
class="flex flex-col bg-gray-800 border-2 {{
isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-full"
>
<div class="flex items-center justify-end">
@if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="flex items-center">
<mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator"
>chevron_right</mat-icon
>
</div>
<div class="grow text-left ml-2 text-white">Route</div>
<div
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
(click)="delete.emit()"
>
<mat-icon class="text-red-500!">close</mat-icon>
</div>
</div>
</div>
</div>
<div *cdkDragPlaceholder class="flex items-start m-2 w-fit">
<div
class="flex flex-col bg-gray-800 border-2 {{
isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-full opacity-30"
>
<div class="flex items-center justify-end">
@if (inDragList()) {
<div
cdkDragHandle
class="flex items-center justify-center bg-transparent hover:bg-gray-700 hover:cursor-move text-blue-400"
>
<mat-icon>drag_indicator</mat-icon>
</div>
}
<div class="flex items-center">
<mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator"
>chevron_right</mat-icon
>
</div>
<div class="grow text-left ml-2 text-white">Route</div>
<div
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
(click)="delete.emit()"
>
<mat-icon class="text-red-500!">close</mat-icon>
</div>
</div>
<hr class="bg-gray-600 h-0.5 border-0" />
<div>
<form [formGroup]="formGroup">
<div class="m-2">
<div class="flex items-center m-1">
<label class="mr-2 text-white"> Input: </label>
<select
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm w-full"
formControlName="input"
>
@for (option of moduleIds(); track option) {
<option [value]="option">
{{ option }}
</option>
}
</select>
@if (!formGroup.controls['input'].valid) {
<!-- TODO(jwetzell): better error displaying -->
<div class="ml-2 text-red-500">{{ formGroup.controls['input'].errors | json }}</div>
}
</div>
</div>
</form>
<div>
<div class="flex items-center">
@if (schemaService.processorTypes.length > 0) {
<div
matTooltip="Add Processor"
class="flex items-center justify-center w-10 h-10 bg-transparent hover:bg-gray-700 hover:cursor-pointer text-blue-400"
[matMenuTriggerFor]="addProcessorMenu"
>
<mat-icon>add</mat-icon>
</div>
<mat-menu #addProcessorMenu="matMenu">
@for (processorType of schemaService.processorTypes; track processorType) {
<button mat-menu-item (click)="addProcessor(processorType.type)">
<span>{{ processorType.name }}</span>
</button>
}
</mat-menu>
}
<div class="text-md text-white text-center">Processors</div>
</div>
@if (processors() !== undefined && processors()!.length > 0) {
<div
class="border-2 border-gray-500 m-2"
cdkDropList
[id]="processorListId()"
[cdkDropListConnectedTo]="listsService.processorListIds"
(cdkDropListDropped)="drop($event)"
[cdkDropListData]="route()?.processors"
>
@for (processor of processors(); track processor; let i = $index) {
<div cdkDrag>
<app-processor
[path]="path() + '/processors/' + i"
(updated)="processorUpdated(i, $event)"
[processor]="processor"
(delete)="deleteProcessor(i)"
[inDragList]="true"
></app-processor>
</div>
}
</div>
}
</div>
</div>
<div>
@if (routeConfigErrors().length > 0) {
<div class="m-2 p-2 border-2 border-red-500">
<div class="flex items-center mb-2 text-red-500">
<mat-icon>error</mat-icon>
<span class="ml-1">Route Errors</span>
</div>
@for (error of routeConfigErrors(); track error.index) {
<div class="ml-4 mb-1 text-white">
{{ error.error }}
</div>
}
</div>
}
</div>
</div>
</div>
} }
+18 -1
View File
@@ -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 { JsonPipe } from '@angular/common';
import { Component, computed, inject, input, 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';
@@ -28,6 +35,8 @@ import { ProcessorComponent } from '../processor/processor';
MatTooltipModule, MatTooltipModule,
CdkDrag, CdkDrag,
CdkDropList, CdkDropList,
CdkDragPreview,
CdkDragPlaceholder,
], ],
templateUrl: './route.html', templateUrl: './route.html',
styleUrl: './route.css', styleUrl: './route.css',
@@ -45,6 +54,7 @@ export class RouteComponent {
return undefined; return undefined;
}); });
inDragList = input<boolean>(false);
route = input<RouteConfig>(); route = input<RouteConfig>();
moduleIds = input<string[]>([]); moduleIds = input<string[]>([]);
delete = output<void>(); delete = output<void>();
@@ -83,8 +93,14 @@ export class RouteComponent {
private configService = inject(ConfigService); private configService = inject(ConfigService);
public listsService = inject(ListsService); public listsService = inject(ListsService);
public processorListId = signal<string>('');
indicatorColor = signal<string>('gray'); indicatorColor = signal<string>('gray');
ngOnDestroy(): void {
this.listsService.removeProcessorList(this.path() + '/processors');
}
ngOnInit(): void { ngOnInit(): void {
this.formGroup.patchValue({ this.formGroup.patchValue({
input: this.route()?.input, input: this.route()?.input,
@@ -115,6 +131,7 @@ export class RouteComponent {
this.indicatorColor.set('gray'); this.indicatorColor.set('gray');
}); });
} }
this.processorListId.set(this.listsService.registerProcessorList(this.path() + '/processors'));
} }
isInError(): boolean { isInError(): boolean {
+9
View File
@@ -18,6 +18,15 @@ export class ListsService {
return id; 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) { pathToId(path: string) {
return path.replaceAll('/', '.'); return path.replaceAll('/', '.');
} }