mirror of
https://github.com/jwetzell/showbridge-webui.git
synced 2026-08-24 15:39:00 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55267ab4db | |||
| 0d09bd534a | |||
| a433b0e036 |
@@ -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
|
||||||
|
|||||||
Generated
+2
-2
@@ -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
@@ -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
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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('/', '.');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user