flatter layout

This commit is contained in:
Joel Wetzell
2026-03-06 18:37:32 -06:00
parent 76ee05c4c1
commit 79b84460b0
20 changed files with 396 additions and 281 deletions
+5 -1
View File
@@ -1,6 +1,10 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners()], providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(appRoutes),
],
}; };
+4 -3
View File
@@ -18,13 +18,14 @@
</div> </div>
} }
</mat-toolbar> </mat-toolbar>
<div class="flex-grow h-full overflow-hidden"> <div class="flex-grow h-full overflow-y-auto">
@if (configService.currentlyShownConfig() && schemaService.schemasLoaded()) { <router-outlet></router-outlet>
<!-- @if (configService.currentlyShownConfig() && schemaService.schemasLoaded()) {
<app-config [(config)]="configService.currentlyShownConfig"></app-config> <app-config [(config)]="configService.currentlyShownConfig"></app-config>
} @else { } @else {
<div class="flex items-center justify-center mt-28"> <div class="flex items-center justify-center mt-28">
<mat-spinner></mat-spinner> <mat-spinner></mat-spinner>
</div> </div>
} } -->
</div> </div>
</div> </div>
+14
View File
@@ -0,0 +1,14 @@
import { Routes } from '@angular/router';
import { App } from './app';
export const appRoutes: Routes = [
{
path: '',
redirectTo: 'config',
pathMatch: 'full',
},
{
path: 'config',
loadComponent: () => import('./config/config.component').then((m) => m.ConfigComponent),
},
];
+3 -4
View File
@@ -13,28 +13,27 @@ import { SchemaService } from '../services/schema.service';
import { SettingsService } from '../services/settings.service'; import { SettingsService } from '../services/settings.service';
import { ConfigComponent } from './config/config.component'; import { ConfigComponent } from './config/config.component';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import { RouterOutlet } from '@angular/router';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [ imports: [
ConfigComponent,
MatToolbarModule, MatToolbarModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatIconModule, MatIconModule,
TimeagoModule, TimeagoModule,
MatMenuModule, MatMenuModule,
MatButtonModule, MatButtonModule,
], RouterOutlet
],
templateUrl: './app.html', templateUrl: './app.html',
styleUrl: './app.css', styleUrl: './app.css',
}) })
export class App { export class App {
public schemaService = inject(SchemaService); public schemaService = inject(SchemaService);
public configService = inject(ConfigService); public configService = inject(ConfigService);
private settingsService = inject(SettingsService);
private snackBar = inject(MatSnackBar); private snackBar = inject(MatSnackBar);
private dialog = inject(MatDialog);
constructor() { constructor() {
effect(() => { effect(() => {
if (this.schemaService.schemasLoaded()) { if (this.schemaService.schemasLoaded()) {
+2 -2
View File
@@ -3,7 +3,7 @@
<div cdkDropList (cdkDropListDropped)="dropItem($event)" [cdkDropListData]="arrayValue"> <div cdkDropList (cdkDropListDropped)="dropItem($event)" [cdkDropListData]="arrayValue">
@for (item of arrayValue; track trackByIndex(i, item); let i = $index) { @for (item of arrayValue; track trackByIndex(i, item); let i = $index) {
<div cdkDrag class="flex my-2 mr-2 items-center"> <div cdkDrag class="flex my-2 mr-2 items-center">
<label for="item-{{ i }}" class="text-gray-300 mr-1 hover:cursor-move" cdkDragHandle <label for="item-{{ i }}" class="text-white mr-1 hover:cursor-move" cdkDragHandle
>{{ i }}:</label >{{ i }}:</label
> >
<!-- @if (paramInfo?.schema.items.type === 'object') { <!-- @if (paramInfo?.schema.items.type === 'object') {
@@ -17,7 +17,7 @@
<!-- TODO(jwetzell): add input validation using info from schema --> <!-- TODO(jwetzell): add input validation using info from schema -->
<input <input
id="item-{{ i }}" id="item-{{ i }}"
class="pl-1 border-2 border-gray-200 border-solid rounded-sm text-gray-200" class="pl-1 border-2 border-gray-200 border-solid rounded-sm text-white"
[(ngModel)]="arrayValue[i]" [(ngModel)]="arrayValue[i]"
[placeholder]="paramInfo?.placeholder" [placeholder]="paramInfo?.placeholder"
[ngModelOptions]="{ standalone: true }" [ngModelOptions]="{ standalone: true }"
+4 -78
View File
@@ -1,78 +1,4 @@
@if (config()) { @if (config() && schemaService.schemasLoaded()) {
<div class="flex h-full flex-row p-2 gap-1.5"> <app-module-list [modules]="config()?.modules" (modulesChange)="modulesUpdated($event)"></app-module-list>
<div class="flex flex-col min-w-1/6 border-2 rounded-lg border-gray-500"> <app-route-list [routes]="config()?.routes" [moduleIds]="moduleIds()" (routesChange)="routesUpdated($event)"></app-route-list>
<div class="flex items-center"> }
@if (schemaService.moduleTypes.length > 0) {
<div
matTooltip="Add Module"
class="flex items-center justify-center w-10 h-10 rounded-full bg-transparent hover:bg-gray-700 hover:cursor-pointer text-blue-400 border-blue-400 border-2 m-2"
[matMenuTriggerFor]="addModuleMenu"
>
<mat-icon>add</mat-icon>
</div>
<mat-menu #addModuleMenu="matMenu">
@for (moduleType of schemaService.moduleTypes; track moduleType) {
<button mat-menu-item (click)="addModule(moduleType.type)">
<span>{{ moduleType.name }}</span>
</button>
}
</mat-menu>
}
<div class="text-2xl text-white flex-grow text-center">Modules</div>
<div class="w-10 h-10 m-2"></div>
</div>
<div class="flex-grow h-full overflow-x-hidden overflow-y-auto gap-1.5">
@for (module of modules(); track module; let i = $index) {
<div>
<app-module
[path]="`modules/${i}`"
(moduleChange)="moduleUpdated(i, $event)"
[module]="module"
(delete)="deleteModule(i)"
></app-module>
</div>
}
</div>
</div>
<div class="flex flex-col flex-grow border-2 border-gray-500 rounded-lg">
<div class="flex items-center">
<div
matTooltip="Add Route"
class="flex items-center justify-center w-10 h-10 rounded-full bg-transparent hover:bg-gray-700 hover:cursor-pointer text-blue-400 border-blue-400 border-2 m-2"
(click)="addRoute()"
>
<mat-icon>add</mat-icon>
</div>
<div class="text-2xl text-white text-center flex-grow">Routes</div>
<div class="w-10 h-10 m-2"></div>
</div>
<div
class="flex-grow h-full overflow-x-hidden overflow-y-auto flex flex-row flex-wrap gap-1.5 content-baselinerounded-sm"
>
@for (route of routes(); track route; let i = $index) {
<div>
<app-route
[path]="`routes/${i}`"
(routeChange)="routeUpdated(i, $event)"
[route]="route"
[moduleIds]="moduleIds()"
(delete)="deleteRoute(i)"
></app-route>
</div>
}
</div>
</div>
</div>
}
<ng-template #addModuleMenu>
@if (schemaService.moduleTypes.length > 0) {
<div cdkMenu class="context-menu">
@for (moduleType of schemaService.moduleTypes; track moduleType) {
<button cdkMenuItem class="context-menu-item" (click)="addModule(moduleType.type)">
<span>{{ moduleType.name }}</span>
</button>
}
</div>
}
</ng-template>
+18 -91
View File
@@ -5,25 +5,26 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { Config, ModuleConfiguration, RouteConfiguration } from '../../models/config.models'; import { Config, ModuleConfiguration, RouteConfiguration } from '../../models/config.models';
import { SchemaService } from '../../services/schema.service'; import { SchemaService } from '../../services/schema.service';
import { ModuleComponent } from '../module/module.component';
import { RouteComponent } from '../route/route.component';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { ModuleListComponent } from '../module-list/module-list.component';
import { RouteListComponent } from '../route-list.component/route-list.component';
import { ConfigService } from '../../services/config.service';
@Component({ @Component({
selector: 'app-config', selector: 'app-config',
imports: [ imports: [
MatMenuModule, MatMenuModule,
MatIconModule, MatIconModule,
ModuleComponent,
RouteComponent,
MatButtonModule, MatButtonModule,
MatTooltipModule, MatTooltipModule,
ModuleListComponent,
RouteListComponent,
], ],
templateUrl: './config.component.html', templateUrl: './config.component.html',
styleUrl: './config.component.css', styleUrl: './config.component.css',
}) })
export class ConfigComponent { export class ConfigComponent {
config = model<Config>(); config = computed<Config | undefined>(() => this.configService.currentlyShownConfig());
modules = computed(() => this.config()?.modules ?? []); modules = computed(() => this.config()?.modules ?? []);
routes = computed(() => this.config()?.routes ?? []); routes = computed(() => this.config()?.routes ?? []);
@@ -36,109 +37,35 @@ export class ConfigComponent {
return []; return [];
}); });
public configService = inject(ConfigService);
public schemaService = inject(SchemaService); public schemaService = inject(SchemaService);
private snackBar = inject(MatSnackBar); modulesUpdated(modules: ModuleConfiguration[] | undefined) {
if (modules === undefined) {
deleteModule(index: number) { console.error('modules is undefined, not updating');
this.config.update((config) => {
if (config && config.modules) {
config?.modules?.splice(index, 1);
return {
...config,
modules: config.modules,
};
}
return config;
});
this.snackBar.open('Module Removed', 'Dismiss', {
duration: 3000,
});
}
deleteRoute(index: number) {
this.config.update((config) => {
if (config && config.routes) {
config?.routes?.splice(index, 1);
return {
...config,
routes: config.routes,
};
}
return config;
});
this.snackBar.open('Route Removed', 'Dismiss', {
duration: 3000,
});
}
moduleUpdated(index: number, module: ModuleConfiguration | undefined) {
if (module === undefined) {
console.error('module is undefined, not updating');
return; return;
} }
this.config.update((config) => { this.configService.currentlyShownConfig.update((config) => {
if (config && config.modules) { if (config) {
config.modules[index].id = module.id;
config.modules[index].type = module.type;
if (module.params !== undefined) {
config.modules[index].params = module.params;
}
return { return {
...config, ...config,
modules: config.modules, modules: modules,
}; };
} }
return config; return config;
}); });
} }
routeUpdated(index: number, route: RouteConfiguration | undefined) { routesUpdated(routes: RouteConfiguration[] | undefined) {
if (route === undefined) { if (routes === undefined) {
console.error('route is undefined, not updating'); console.error('routes is undefined, not updating');
return; return;
} }
this.config.update((config) => { this.configService.currentlyShownConfig.update((config) => {
if (config && config.routes) {
config.routes[index].input = route.input;
config.routes[index].output = route.output;
return {
...config,
routes: config.routes,
};
}
return config;
});
}
addModule(moduleType: string) {
const moduleTemplate = this.schemaService.getSkeletonForModule(moduleType);
this.config.update((config) => {
if (config) { if (config) {
if (!config.modules) {
config.modules = [];
}
config.modules?.push(moduleTemplate);
return { return {
...config, ...config,
modules: config.modules, routes: routes,
};
}
return config;
});
}
addRoute() {
const routeTemplate = this.schemaService.getSkeletonForRoute();
this.config.update((config) => {
if (config) {
if (!config.routes) {
config.routes = [];
}
config.routes?.push(routeTemplate);
return {
...config,
routes: config.routes,
}; };
} }
return config; return config;
@@ -0,0 +1,45 @@
<div class="flex flex-col">
<div class="flex items-center">
@if (schemaService.moduleTypes.length > 0) {
<div
matTooltip="Add Module"
class="flex items-center justify-center w-10 h-10 bg-transparent hover:bg-gray-700 hover:cursor-pointer text-blue-400"
[matMenuTriggerFor]="addModuleMenu"
>
<mat-icon>add</mat-icon>
</div>
<mat-menu #addModuleMenu="matMenu">
@for (moduleType of schemaService.moduleTypes; track moduleType) {
<button mat-menu-item (click)="addModule(moduleType.type)">
<span>{{ moduleType.name }}</span>
</button>
}
</mat-menu>
}
<div class="text-white">Modules</div>
</div>
<div class="flex-grow overflow-x-hidden overflow-y-auto gap-1.5">
@for (module of modules(); track module; let i = $index) {
<div class="m-2">
<app-module
[path]="`modules/${i}`"
(moduleChange)="moduleUpdated(i, $event)"
[module]="module"
(delete)="deleteModule(i)"
></app-module>
</div>
}
</div>
</div>
<ng-template #addModuleMenu>
@if (schemaService.moduleTypes.length > 0) {
<div cdkMenu class="context-menu">
@for (moduleType of schemaService.moduleTypes; track moduleType) {
<button cdkMenuItem class="context-menu-item" (click)="addModule(moduleType.type)">
<span>{{ moduleType.name }}</span>
</button>
}
</div>
}
</ng-template>
@@ -0,0 +1,77 @@
import { Component, inject, model } from '@angular/core';
import { ModuleComponent } from '../module/module.component';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ModuleConfiguration } from '../../models/config.models';
import { SchemaService } from '../../services/schema.service';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouteComponent } from '../route/route.component';
@Component({
selector: 'app-module-list',
imports: [
MatMenuModule,
MatIconModule,
MatButtonModule,
MatTooltipModule,
ModuleComponent
],
templateUrl: './module-list.component.html',
styleUrl: './module-list.component.css',
})
export class ModuleListComponent {
modules = model<ModuleConfiguration[]>();
public schemaService = inject(SchemaService);
private snackBar = inject(MatSnackBar);
moduleUpdated(index: number, module: ModuleConfiguration | undefined) {
if (module === undefined) {
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;
});
}
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;
});
this.snackBar.open('Module Added', 'Dismiss', {
duration: 3000,
});
}
deleteModule(index: number) {
this.modules.update((modules) => {
if (modules) {
modules?.splice(index, 1);
return [...modules];
}
return [];
});
this.snackBar.open('Module Removed', 'Dismiss', {
duration: 3000,
});
}
}
+12 -9
View File
@@ -1,13 +1,12 @@
@if (module() && schema()) { @if (module() && schema()) {
<div class="flex items-start m-2 w-fit">
<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-200' isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-fit h-full" }} border-solid w-full h-full"
> >
<div class="flex items-center justify-center bg-gray-600"> <div class="flex items-center justify-center border-gray-600 border-b-2">
<div class="flex-grow ml-1 mr-3 text-gray-200"> <div class="flex-grow ml-1 mr-3 text-white">
{{ schema().title }} {{ schema().title || module()?.type }}
</div> </div>
<div class="flex items-center justify-center" (click)="delete.emit()"> <div class="flex items-center justify-center" (click)="delete.emit()">
<mat-icon class="!text-red-500">close</mat-icon> <mat-icon class="!text-red-500">close</mat-icon>
@@ -17,13 +16,18 @@
<form [formGroup]="formGroup" class="h-full"> <form [formGroup]="formGroup" class="h-full">
<div class="m-2"> <div class="m-2">
<div class="flex items-center m-1"> <div class="flex items-center m-1">
<label class="mr-2 text-gray-300"> id: </label> <label class="mr-2 text-white"> id: </label>
<input <input
type="text" type="text"
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[placeholder]="'module-1'" [placeholder]="'module-1'"
[formControlName]="'id'" [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> </div>
<app-params-form <app-params-form
[paramsSchema]="schema()?.properties?.params" [paramsSchema]="schema()?.properties?.params"
@@ -34,5 +38,4 @@
</form> </form>
</div> </div>
</div> </div>
</div>
} }
+6 -2
View File
@@ -1,11 +1,13 @@
import { Component, computed, inject, input, model, output } from '@angular/core'; import { Component, computed, inject, input, model, output } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } 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 { ModuleConfiguration } from '../../models/config.models'; import { ModuleConfiguration } from '../../models/config.models';
import { SchemaService } from '../../services/schema.service'; import { SchemaService } from '../../services/schema.service';
import { ParamsFormComponent } from '../params-form/params-form.component'; import { ParamsFormComponent } from '../params-form/params-form.component';
import { JsonPipe } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
@Component({ @Component({
selector: 'app-module', selector: 'app-module',
@@ -15,6 +17,8 @@ import { ParamsFormComponent } from '../params-form/params-form.component';
ParamsFormComponent, ParamsFormComponent,
ReactiveFormsModule, ReactiveFormsModule,
MatMenuModule, MatMenuModule,
MatTooltipModule,
JsonPipe,
], ],
templateUrl: './module.component.html', templateUrl: './module.component.html',
styleUrl: './module.component.css', styleUrl: './module.component.css',
@@ -34,7 +38,7 @@ export class ModuleComponent {
}); });
formGroup: FormGroup = new FormGroup({ formGroup: FormGroup = new FormGroup({
id: new FormControl(''), id: new FormControl('', [Validators.required]),
type: new FormControl(''), type: new FormControl(''),
}); });
+22 -19
View File
@@ -1,6 +1,7 @@
@if (paramsSchema && paramsFormInfo) { @if (paramsSchema && paramsFormInfo) {
@if (paramsOptions.length > 1) { @if (paramsOptions.length > 1) {
<mat-tab-group <mat-tab-group
dynamicHeight
[selectedIndex]="paramsOptionsSelectedIndex" [selectedIndex]="paramsOptionsSelectedIndex"
(selectedTabChange)="paramsOptionsTabSelected($event)" (selectedTabChange)="paramsOptionsTabSelected($event)"
> >
@@ -14,7 +15,7 @@
@for (key of paramKeys(); track key) { @for (key of paramKeys(); track key) {
<div class="flex items-center m-1"> <div class="flex items-center m-1">
@if (getParamInfo(key); as paramInfo) { @if (getParamInfo(key); as paramInfo) {
<label class="mr-2 text-gray-300"> {{ paramInfo.display }}: </label> <label class="mr-2 text-white"> {{ paramInfo.display }}: </label>
@switch (paramInfo.type) { @switch (paramInfo.type) {
@case ('boolean') { @case ('boolean') {
<input type="checkbox" [formControlName]="paramInfo.key" /> <input type="checkbox" [formControlName]="paramInfo.key" />
@@ -24,12 +25,12 @@
<input <input
type="number" type="number"
[placeholder]="paramInfo.placeholder" [placeholder]="paramInfo.placeholder"
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
/> />
} @else { } @else {
<select <select
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
> >
@for (option of paramInfo.options; track option) { @for (option of paramInfo.options; track option) {
@@ -39,19 +40,19 @@
} }
</select> </select>
} }
<mat-icon class="!text-gray-200">123</mat-icon> <mat-icon class="!text-white">123</mat-icon>
} }
@case ('integer') { @case ('integer') {
@if (!paramInfo.options) { @if (!paramInfo.options) {
<input <input
type="number" type="number"
[placeholder]="paramInfo.placeholder" [placeholder]="paramInfo.placeholder"
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
/> />
} @else { } @else {
<select <select
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
> >
@for (option of paramInfo.options; track option) { @for (option of paramInfo.options; track option) {
@@ -61,19 +62,19 @@
} }
</select> </select>
} }
<mat-icon class="!text-gray-200" matTooltip="Number">123</mat-icon> <mat-icon class="!text-white" matTooltip="Number">123</mat-icon>
} }
@case ('string') { @case ('string') {
@if (!paramInfo.options) { @if (!paramInfo.options) {
<input <input
type="text" type="text"
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[placeholder]="paramInfo.placeholder" [placeholder]="paramInfo.placeholder"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
/> />
} @else { } @else {
<select <select
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
> >
@for (option of paramInfo.options; track option) { @for (option of paramInfo.options; track option) {
@@ -83,42 +84,44 @@
} }
</select> </select>
} }
<mat-icon class="!text-gray-200" matTooltip="String">abc</mat-icon> <mat-icon class="!text-white" matTooltip="String">abc</mat-icon>
} }
@case ('array') { @case ('array') {
<app-array-form <app-array-form
[paramFormControl]="paramsFormInfo.formGroup.get(paramInfo.key)" [paramFormControl]="paramsFormInfo.formGroup.get(paramInfo.key)"
[paramInfo]="paramInfo" [paramInfo]="paramInfo"
></app-array-form> ></app-array-form>
<mat-icon class="!text-gray-200" matTooltip="Item list">data_array</mat-icon> <mat-icon class="!text-white" matTooltip="Item list">data_array</mat-icon>
} }
@case ('object') { @case ('object') {
<!-- TODO(jwetzell): figure out how to display objects--> <!-- TODO(jwetzell): figure out how to display objects-->
<input <input
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
[placeholder]="paramInfo.placeholder" [placeholder]="paramInfo.placeholder"
/> />
<mat-icon class="!text-gray-200" matTooltip="JSON">data_object</mat-icon> <mat-icon class="!text-white" matTooltip="JSON">data_object</mat-icon>
} }
@default { @default {
<input <input
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm" class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
[formControlName]="paramInfo.key" [formControlName]="paramInfo.key"
/> />
} }
} }
@if (paramInfo.hint) {
<div class="flex items-center justify-center">
<mat-icon [matTooltip]="paramInfo.hint" class="ml-2 !text-white">
help_outline
</mat-icon>
</div>
}
@if (!paramsFormInfo.formGroup.controls[key].valid) { @if (!paramsFormInfo.formGroup.controls[key].valid) {
<!-- TODO(jwetzell): better error displaying --> <!-- TODO(jwetzell): better error displaying -->
<div class="ml-2 text-red-500"> <div class="ml-2 text-red-500">
{{ paramsFormInfo.formGroup.controls[key].errors | json }} {{ paramsFormInfo.formGroup.controls[key].errors | json }}
</div> </div>
} }
<div class="flex items-center justify-center">
<mat-icon [matTooltip]="paramInfo.hint" class="ml-2 !text-gray-200">
help_outline
</mat-icon>
</div>
} }
</div> </div>
} }
+4 -4
View File
@@ -2,12 +2,12 @@
<div class="flex items-start m-2 w-fit"> <div class="flex items-start m-2 w-fit">
<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-200' isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-fit h-full" }} border-solid w-fit h-full"
> >
<div class="flex items-center justify-center bg-gray-600"> <div class="flex items-center justify-center border-gray-600 border-b-2">
<div class="flex-grow ml-1 mr-3 text-gray-200"> <div class="flex-grow ml-1 mr-3 text-white">
{{ processor()?.type }} {{ schema()?.title || processor()?.type }}
</div> </div>
<div class="flex items-center justify-center" (click)="delete.emit()"> <div class="flex items-center justify-center" (click)="delete.emit()">
<mat-icon class="!text-red-500">close</mat-icon> <mat-icon class="!text-red-500">close</mat-icon>
@@ -0,0 +1,25 @@
<div class="flex flex-col">
<div class="flex items-center">
<div
matTooltip="Add Route"
class="flex items-center justify-center w-10 h-10 bg-transparent hover:bg-gray-700 hover:cursor-pointer text-blue-400"
(click)="addRoute()"
>
<mat-icon>add</mat-icon>
</div>
<div class="text-white">Routes</div>
</div>
<div class="flex-grow overflow-x-hidden overflow-y-auto gap-1.5">
@for (route of routes(); track route; let i = $index) {
<div class="m-2">
<app-route
[path]="`routes/${i}`"
(routeChange)="routeUpdated(i, $event)"
[route]="route"
[moduleIds]="moduleIds() || []"
(delete)="deleteRoute(i)"
></app-route>
</div>
}
</div>
</div>
@@ -0,0 +1,72 @@
import { Component, inject, input, model } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ModuleConfiguration, RouteConfiguration } from '../../models/config.models';
import { SchemaService } from '../../services/schema.service';
import { RouteComponent } from '../route/route.component';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
@Component({
selector: 'app-route-list',
imports: [
RouteComponent,
MatMenuModule,
MatIconModule,
MatButtonModule,
MatTooltipModule,
],
templateUrl: './route-list.component.html',
styleUrl: './route-list.component.css',
})
export class RouteListComponent {
routes = model<RouteConfiguration[]>();
moduleIds = input<string[]>();
public schemaService = inject(SchemaService);
private snackBar = inject(MatSnackBar);
deleteRoute(index: number) {
this.routes.update((routes) => {
if (routes) {
routes?.splice(index, 1);
return [...routes];
}
return routes;
});
this.snackBar.open('Route Removed', 'Dismiss', {
duration: 3000,
});
}
routeUpdated(index: number, route: RouteConfiguration | undefined) {
if (route === undefined) {
console.error('route is undefined, not updating');
return;
}
this.routes.update((routes) => {
if (routes) {
routes[index].input = route.input;
routes[index].output = route.output;
return [...routes];
}
return routes;
});
}
addRoute() {
const routeTemplate = this.schemaService.getSkeletonForRoute();
this.routes.update((routes) => {
if (!routes) {
routes = [];
}
routes?.push(routeTemplate);
return [...routes];
});
this.snackBar.open('Route Added', 'Dismiss', {
duration: 3000,
});
}
}
+63 -64
View File
@@ -1,77 +1,76 @@
@if (route()) { @if (route()) {
<div class="flex items-start m-2 w-fit"> <!-- <div class="flex items-start m-2 w-fit border-2 border-red-500"> -->
<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-200' isInError() ? 'border-red-500' : 'border-gray-600'
}} border-solid w-fit h-full mr-2" }} border-solid w-full h-full"
> >
<div class="flex items-center justify-end bg-gray-600"> <div class="flex items-center justify-end border-gray-600 border-b-2">
<div class="flex items-center justify-center" (click)="delete.emit()"> <div class="flex-grow text-left ml-2 text-white">Route {{ index() }}</div>
<mat-icon class="!text-red-500">close</mat-icon> <div class="flex items-center justify-center hover:bg-gray-700" (click)="delete.emit()">
</div> <mat-icon class="!text-red-500">close</mat-icon>
</div> </div>
</div>
<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>
<form [formGroup]="formGroup" class="h-full"> <div class="flex items-center">
<div class="m-2"> @if (schemaService.processorTypes.length > 0) {
<div class="flex items-center m-1"> <div
<label class="mr-2 text-gray-300"> input: </label> matTooltip="Add Processor"
<select class="flex items-center justify-center w-10 h-10 bg-transparent hover:bg-gray-700 hover:cursor-pointer text-blue-400"
class="pl-1 border-2 border-gray-200 text-gray-200 border-solid rounded-sm w-full" [matMenuTriggerFor]="addProcessorMenu"
formControlName="input" >
> <mat-icon>add</mat-icon>
@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>
</div> <mat-menu #addProcessorMenu="matMenu">
</form> @for (processorType of schemaService.processorTypes; track processorType) {
<div> <button mat-menu-item (click)="addProcessor(processorType.type)">
<div class="text-md text-gray-300 text-center">Processors</div> <span>{{ processorType.name }}</span>
<div class="border-2 border-gray-500 m-2 rounded-sm"> </button>
@if (processors() === undefined || processors()?.length === 0) {
<div class="w-full h-6"></div>
} @else {
@for (processor of processors(); track processor; let i = $index) {
<div cdkDrag>
<app-processor
[path]="path() + '/processors/' + i"
(processorChange)="processorUpdated(i, $event)"
[processor]="processor"
(delete)="deleteProcessor(i)"
></app-processor>
</div>
} }
} </mat-menu>
</div> }
<div class="text-md text-white text-center">Processors</div>
</div> </div>
</div> @if (processors() !== undefined && processors()!.length > 0) {
<div class="flex mb-2"> <div class="border-2 border-gray-500 m-2">
@if (schemaService.moduleTypes.length > 0) { @for (processor of processors(); track processor; let i = $index) {
<div <div cdkDrag>
matTooltip="Add Module" <app-processor
class="flex items-center justify-center w-full h-10 rounded-lg bg-gray-200 hover:cursor-pointer hover:bg-gray-300 m-2" [path]="path() + '/processors/' + i"
[matMenuTriggerFor]="addProcessorMenu" (processorChange)="processorUpdated(i, $event)"
> [processor]="processor"
<mat-icon>add</mat-icon> (delete)="deleteProcessor(i)"
</div> ></app-processor>
<mat-menu #addProcessorMenu="matMenu"> </div>
@for (processorType of schemaService.processorTypes; track processorType) {
<button mat-menu-item (click)="addProcessor(processorType.type)">
<span>{{ processorType.type }}</span>
</button>
} }
</mat-menu> </div>
} }
</div> </div>
</div> </div>
</div> </div>
<!-- </div> -->
<ng-template #addProcessorMenu> <ng-template #addProcessorMenu>
@if (schemaService.processorTypes.length > 0) { @if (schemaService.processorTypes.length > 0) {
+16
View File
@@ -8,6 +8,7 @@ import { SchemaService } from '../../services/schema.service';
import { JsonPipe } from '@angular/common'; import { JsonPipe } from '@angular/common';
import { ProcessorComponent } from '../processor/processor.component'; import { ProcessorComponent } from '../processor/processor.component';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
@Component({ @Component({
selector: 'app-route', selector: 'app-route',
@@ -18,12 +19,24 @@ import { MatSnackBar } from '@angular/material/snack-bar';
MatMenuModule, MatMenuModule,
JsonPipe, JsonPipe,
ProcessorComponent, ProcessorComponent,
MatTooltipModule,
], ],
templateUrl: './route.component.html', templateUrl: './route.component.html',
styleUrl: './route.component.css', styleUrl: './route.component.css',
}) })
export class RouteComponent { export class RouteComponent {
path = input<string>(''); path = input<string>('');
index = computed(() => {
const path = this.path();
if (path) {
const parts = path.split('/');
const lastPart = parts[parts.length - 1];
return parseInt(lastPart, 10);
}
return undefined;
});
route = model<RouteConfiguration>(); route = model<RouteConfiguration>();
moduleIds = input<string[]>([]); moduleIds = input<string[]>([]);
delete = output<void>(); delete = output<void>();
@@ -85,6 +98,9 @@ export class RouteComponent {
} }
return route; return route;
}); });
this.snackBar.open('Processor Added', 'Dismiss', {
duration: 3000,
});
} }
processorUpdated(index: number, processor: ProcessorConfiguration | undefined) { processorUpdated(index: number, processor: ProcessorConfiguration | undefined) {
+4 -4
View File
@@ -225,12 +225,12 @@ export class SchemaService {
.filter((definition) => definition.properties?.type?.const !== undefined) .filter((definition) => definition.properties?.type?.const !== undefined)
.map((definition) => { .map((definition) => {
return { return {
name: definition['title'], name: definition['title'] || definition.properties?.type?.const,
type: definition.properties?.type?.const, type: definition.properties?.type?.const,
schema: definition, schema: definition,
}; };
}), }),
['name'], ['type'],
); );
} }
} }
@@ -247,12 +247,12 @@ export class SchemaService {
.filter((definition) => definition.properties?.type?.const !== undefined) .filter((definition) => definition.properties?.type?.const !== undefined)
.map((definition) => { .map((definition) => {
return { return {
name: definition['title'], name: definition['title'] || definition.properties?.type?.const,
type: definition.properties?.type?.const, type: definition.properties?.type?.const,
schema: definition, schema: definition,
}; };
}), }),
['name'], ['type'],
); );
} }
} }