Add configuration editor for messages, monaco editor to string type
This commit is contained in:
@@ -17,11 +17,14 @@ import {
|
|||||||
MapMessage,
|
MapMessage,
|
||||||
MessageConfiguration,
|
MessageConfiguration,
|
||||||
MessageTypeEnum,
|
MessageTypeEnum,
|
||||||
|
NumericMessage,
|
||||||
ObjectMessage,
|
ObjectMessage,
|
||||||
|
StringMessage,
|
||||||
} from '../../model/proto-message.model';
|
} from '../../model/proto-message.model';
|
||||||
import { ListFieldComponent } from '../list-field/list-field.component';
|
import { ListFieldComponent } from '../list-field/list-field.component';
|
||||||
import { MapFieldComponent } from '../map-field/map-field.component';
|
import { MapFieldComponent } from '../map-field/map-field.component';
|
||||||
import { ObjectFieldComponent } from '../object-field/object-field.component';
|
import { ObjectFieldComponent } from '../object-field/object-field.component';
|
||||||
|
import { StringFieldComponent } from '../string-field/string-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-proto-field',
|
selector: 'app-proto-field',
|
||||||
@@ -36,16 +39,33 @@ import { ObjectFieldComponent } from '../object-field/object-field.component';
|
|||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
ObjectFieldComponent,
|
ObjectFieldComponent,
|
||||||
|
StringFieldComponent,
|
||||||
],
|
],
|
||||||
template: `@switch (configuration().type) { @case (MessageTypeEnum.String) {
|
template: `@switch (configuration().type) { @case (MessageTypeEnum.String) {
|
||||||
<mat-form-field>
|
<app-string-field
|
||||||
<mat-label>{{ label() }}</mat-label>
|
[label]="label()"
|
||||||
<input matInput [(ngModel)]="value" />
|
[configuration]="stringConfiguration()"
|
||||||
</mat-form-field>
|
[(value)]="value"
|
||||||
|
></app-string-field>
|
||||||
} @case (MessageTypeEnum.Numeric) {
|
} @case (MessageTypeEnum.Numeric) {
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>{{ label() }}</mat-label>
|
<mat-label>{{ label() }}</mat-label>
|
||||||
<input matInput type="number" [(ngModel)]="value" />
|
<input
|
||||||
|
#number="ngModel"
|
||||||
|
matInput
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
[min]="numericConfiguration().min ?? null"
|
||||||
|
[max]="numericConfiguration().max ?? null"
|
||||||
|
/>
|
||||||
|
<mat-hint *ngIf="number.hasError('min')"
|
||||||
|
>Number should not be less than
|
||||||
|
{{ numericConfiguration().min }}</mat-hint
|
||||||
|
>
|
||||||
|
<mat-hint *ngIf="number.hasError('max')"
|
||||||
|
>Number should not greater than
|
||||||
|
{{ numericConfiguration().max }}</mat-hint
|
||||||
|
>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
} @case (MessageTypeEnum.Boolean) {
|
} @case (MessageTypeEnum.Boolean) {
|
||||||
<p>
|
<p>
|
||||||
@@ -89,6 +109,14 @@ export class ProtoFieldComponent {
|
|||||||
configuration = input.required<MessageConfiguration>();
|
configuration = input.required<MessageConfiguration>();
|
||||||
value = model<any>();
|
value = model<any>();
|
||||||
|
|
||||||
|
protected stringConfiguration = computed(
|
||||||
|
() => this.configuration() as StringMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
protected numericConfiguration = computed(
|
||||||
|
() => this.configuration() as NumericMessage
|
||||||
|
);
|
||||||
|
|
||||||
protected enumConfiguration = computed(
|
protected enumConfiguration = computed(
|
||||||
() => this.configuration() as EnumMessage
|
() => this.configuration() as EnumMessage
|
||||||
);
|
);
|
||||||
|
|||||||
3
src/app/editor/string-field/string-field.component.css
Normal file
3
src/app/editor/string-field/string-field.component.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
57
src/app/editor/string-field/string-field.component.ts
Normal file
57
src/app/editor/string-field/string-field.component.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
input,
|
||||||
|
model,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { StringMessage } from '../../model/proto-message.model';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MonacoEditorModule } from 'ngx-monaco-editor-v2';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-string-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
MonacoEditorModule,
|
||||||
|
],
|
||||||
|
template: ` @if(configuration().textType === 'text') {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ label() }}</mat-label>
|
||||||
|
<input
|
||||||
|
#text="ngModel"
|
||||||
|
matInput
|
||||||
|
[(ngModel)]="value"
|
||||||
|
[maxlength]="configuration().maxLength ?? null"
|
||||||
|
/>
|
||||||
|
<mat-hint *ngIf="text.hasError('maxlength')"
|
||||||
|
>Text should be less than
|
||||||
|
{{ configuration().maxLength }} characters</mat-hint
|
||||||
|
>
|
||||||
|
</mat-form-field>
|
||||||
|
} @else {
|
||||||
|
<ngx-monaco-editor
|
||||||
|
[(ngModel)]="value"
|
||||||
|
[options]="editorOptions"
|
||||||
|
></ngx-monaco-editor>
|
||||||
|
}`,
|
||||||
|
styleUrl: './string-field.component.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class StringFieldComponent {
|
||||||
|
label = input();
|
||||||
|
configuration = input.required<StringMessage>();
|
||||||
|
value = model<string>();
|
||||||
|
|
||||||
|
protected editorOptions = {
|
||||||
|
theme: 'vs-dark',
|
||||||
|
language: 'sql',
|
||||||
|
automaticLayout: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,8 +9,11 @@ export enum MessageTypeEnum {
|
|||||||
Enum = 'enum',
|
Enum = 'enum',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StringMessageTextType = 'text' | 'sql';
|
||||||
|
|
||||||
export interface StringMessage extends MessageConfiguration {
|
export interface StringMessage extends MessageConfiguration {
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
|
textType: StringMessageTextType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BooleanMessage extends MessageConfiguration {}
|
export interface BooleanMessage extends MessageConfiguration {}
|
||||||
@@ -43,9 +46,13 @@ export interface MessageConfiguration {
|
|||||||
type: MessageTypeEnum;
|
type: MessageTypeEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StringMessage = (maxLength?: number): StringMessage => ({
|
export const StringMessage = (
|
||||||
|
maxLength?: number,
|
||||||
|
textType: StringMessageTextType = 'text'
|
||||||
|
): StringMessage => ({
|
||||||
type: MessageTypeEnum.String,
|
type: MessageTypeEnum.String,
|
||||||
maxLength,
|
maxLength,
|
||||||
|
textType,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BooleanMessage = (): BooleanMessage => ({
|
export const BooleanMessage = (): BooleanMessage => ({
|
||||||
@@ -114,3 +121,11 @@ export const UnknownProto = (
|
|||||||
fullName,
|
fullName,
|
||||||
values: [{ name: 'Raw JSON', configuration: RawMessage() }],
|
values: [{ name: 'Raw JSON', configuration: RawMessage() }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const EDITABLE_MESSAGE_TYPES = [
|
||||||
|
MessageTypeEnum.String,
|
||||||
|
MessageTypeEnum.Numeric,
|
||||||
|
MessageTypeEnum.List,
|
||||||
|
MessageTypeEnum.Map,
|
||||||
|
MessageTypeEnum.Object,
|
||||||
|
];
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ListMessage,
|
||||||
|
MapMessage,
|
||||||
|
MessageConfiguration,
|
||||||
|
MessageTypeEnum,
|
||||||
|
NumericMessage,
|
||||||
|
ObjectMessage,
|
||||||
|
ProtoMessageField,
|
||||||
|
StringMessage,
|
||||||
|
} from '../../../model/proto-message.model';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { ListEditorFieldComponent } from '../list-editor-field/list-editor-field.component';
|
||||||
|
import { MapEditorFieldComponent } from '../map-editor-field/map-editor-field.component';
|
||||||
|
import { ObjectEditorFieldComponent } from '../object-editor-field/object-editor-field.component';
|
||||||
|
import { StringEditorFieldComponent } from '../string-editor-field/string-editor-field.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-definition-editor-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ListEditorFieldComponent,
|
||||||
|
MapEditorFieldComponent,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatSelectModule,
|
||||||
|
ObjectEditorFieldComponent,
|
||||||
|
StringEditorFieldComponent,
|
||||||
|
],
|
||||||
|
template: ` @switch (fieldConfiguration().type) {
|
||||||
|
@case(MessageTypeEnum.String) {
|
||||||
|
<app-string-editor-field
|
||||||
|
[configuration]="stringConfiguration(fieldConfiguration())"
|
||||||
|
></app-string-editor-field>
|
||||||
|
} @case (MessageTypeEnum.Numeric) { @let configuration =
|
||||||
|
numericConfiguration(fieldConfiguration());
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Min</mat-label>
|
||||||
|
<input
|
||||||
|
#min="ngModel"
|
||||||
|
matInput
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="configuration.min"
|
||||||
|
[max]="configuration.max ?? null"
|
||||||
|
/>
|
||||||
|
<mat-hint *ngIf="min.hasError('max')"
|
||||||
|
>Min should not be greater than {{ configuration.max }}</mat-hint
|
||||||
|
>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Max</mat-label>
|
||||||
|
<input
|
||||||
|
#max="ngModel"
|
||||||
|
matInput
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="configuration.max"
|
||||||
|
[min]="configuration.min ?? null"
|
||||||
|
/>
|
||||||
|
<mat-hint *ngIf="max.hasError('min')"
|
||||||
|
>Max should not be less than {{ configuration.min }}</mat-hint
|
||||||
|
>
|
||||||
|
</mat-form-field>
|
||||||
|
} @case (MessageTypeEnum.List) {
|
||||||
|
<app-list-editor-field
|
||||||
|
[field]="listConfiguration(fieldConfiguration())"
|
||||||
|
></app-list-editor-field>
|
||||||
|
} @case (MessageTypeEnum.Map) {
|
||||||
|
<app-map-editor-field
|
||||||
|
[field]="mapConfiguration(fieldConfiguration())"
|
||||||
|
></app-map-editor-field>
|
||||||
|
} @case(MessageTypeEnum.Object) {
|
||||||
|
<app-object-editor-field
|
||||||
|
[field]="objectConfiguration(fieldConfiguration())"
|
||||||
|
></app-object-editor-field>
|
||||||
|
} }`,
|
||||||
|
styleUrl: './definition-editor-field.component.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DefinitionEditorFieldComponent {
|
||||||
|
fieldConfiguration = input.required<MessageConfiguration>();
|
||||||
|
|
||||||
|
protected stringConfiguration(configuration: MessageConfiguration) {
|
||||||
|
return configuration as StringMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected numericConfiguration(configuration: MessageConfiguration) {
|
||||||
|
return configuration as NumericMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected listConfiguration(configuration: MessageConfiguration) {
|
||||||
|
return configuration as ListMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected mapConfiguration(configuration: MessageConfiguration) {
|
||||||
|
return configuration as MapMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected objectConfiguration(configuration: MessageConfiguration) {
|
||||||
|
return configuration as ObjectMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly MessageTypeEnum = MessageTypeEnum;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import {
|
||||||
|
EDITABLE_MESSAGE_TYPES,
|
||||||
|
ListMessage,
|
||||||
|
MessageConfiguration,
|
||||||
|
MessageTypeEnum,
|
||||||
|
ObjectMessage,
|
||||||
|
ProtoMessage,
|
||||||
|
} from '../../model/proto-message.model';
|
||||||
|
import { DefinitionEditorFieldComponent } from './definition-editor-field/definition-editor-field.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-definition-editor',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatDialogModule,
|
||||||
|
DefinitionEditorFieldComponent,
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<h2 mat-dialog-title>{{ protoMessage.name }}</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
@for (field of editableMessages; track $index) {
|
||||||
|
<h3>{{ field.name }}</h3>
|
||||||
|
<app-definition-editor-field
|
||||||
|
[fieldConfiguration]="field.configuration"
|
||||||
|
></app-definition-editor-field>
|
||||||
|
}
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close>Close</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
`,
|
||||||
|
styleUrl: './definition-editor.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DefinitionEditorComponent {
|
||||||
|
protected editableMessages = this.protoMessage.values.filter((message) =>
|
||||||
|
this.filterMessageConfiguration(message.configuration)
|
||||||
|
);
|
||||||
|
|
||||||
|
private filterMessageConfiguration(
|
||||||
|
configuration: MessageConfiguration
|
||||||
|
): boolean {
|
||||||
|
if (configuration.type === MessageTypeEnum.List) {
|
||||||
|
return this.filterMessageConfiguration(
|
||||||
|
(configuration as ListMessage).subConfiguration
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (configuration.type === MessageTypeEnum.Object) {
|
||||||
|
// Ensure at least one nested message can be configured
|
||||||
|
return !!(configuration as ObjectMessage).messageDefinition.values.find(
|
||||||
|
(message) => this.filterMessageConfiguration(message.configuration)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Map can always be configured, as key needs to be a string or numeric type
|
||||||
|
return EDITABLE_MESSAGE_TYPES.includes(configuration.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) protected protoMessage: ProtoMessage) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
forwardRef,
|
||||||
|
input,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { DefinitionEditorFieldComponent } from '../definition-editor-field/definition-editor-field.component';
|
||||||
|
import { ListMessage } from '../../../model/proto-message.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-list-editor-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, forwardRef(() => DefinitionEditorFieldComponent)],
|
||||||
|
template: `<app-definition-editor-field
|
||||||
|
[fieldConfiguration]="field().subConfiguration"
|
||||||
|
></app-definition-editor-field>`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ListEditorFieldComponent {
|
||||||
|
field = input.required<ListMessage>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
forwardRef,
|
||||||
|
input,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { DefinitionEditorFieldComponent } from '../definition-editor-field/definition-editor-field.component';
|
||||||
|
import {
|
||||||
|
EDITABLE_MESSAGE_TYPES,
|
||||||
|
MapMessage,
|
||||||
|
MessageConfiguration,
|
||||||
|
} from '../../../model/proto-message.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-map-editor-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, forwardRef(() => DefinitionEditorFieldComponent)],
|
||||||
|
template: `
|
||||||
|
<h4>Key Configuration</h4>
|
||||||
|
<app-definition-editor-field
|
||||||
|
[fieldConfiguration]="field().keyConfiguration"
|
||||||
|
></app-definition-editor-field>
|
||||||
|
@if(isEditable(field().valueConfiguration)) {
|
||||||
|
<h4>Value Configuration</h4>
|
||||||
|
<app-definition-editor-field
|
||||||
|
[fieldConfiguration]="field().valueConfiguration"
|
||||||
|
></app-definition-editor-field>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class MapEditorFieldComponent {
|
||||||
|
field = input.required<MapMessage>();
|
||||||
|
|
||||||
|
protected isEditable(configuration: MessageConfiguration) {
|
||||||
|
return EDITABLE_MESSAGE_TYPES.includes(configuration.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
forwardRef,
|
||||||
|
input,
|
||||||
|
} from '@angular/core';
|
||||||
|
import {
|
||||||
|
EDITABLE_MESSAGE_TYPES,
|
||||||
|
ObjectMessage,
|
||||||
|
} from '../../../model/proto-message.model';
|
||||||
|
import { DefinitionEditorFieldComponent } from '../definition-editor-field/definition-editor-field.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-object-editor-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, forwardRef(() => DefinitionEditorFieldComponent)],
|
||||||
|
template: `
|
||||||
|
@for (field of editableFields(); track $index) {
|
||||||
|
<h4>{{ field.name }}</h4>
|
||||||
|
<app-definition-editor-field
|
||||||
|
[fieldConfiguration]="field.configuration"
|
||||||
|
></app-definition-editor-field>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ObjectEditorFieldComponent {
|
||||||
|
field = input.required<ObjectMessage>();
|
||||||
|
|
||||||
|
protected editableFields = computed(() =>
|
||||||
|
this.field().messageDefinition.values.filter((field) =>
|
||||||
|
EDITABLE_MESSAGE_TYPES.includes(field.configuration.type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { StringMessage } from '../../../model/proto-message.model';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-string-editor-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatSelectModule,
|
||||||
|
],
|
||||||
|
template: ` <mat-form-field>
|
||||||
|
<mat-label>Max Length</mat-label>
|
||||||
|
<input matInput type="number" [(ngModel)]="configuration().maxLength" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Field Type</mat-label>
|
||||||
|
<mat-select [(value)]="configuration().textType">
|
||||||
|
<mat-option value="text">Text</mat-option>
|
||||||
|
<mat-option value="sql">SQL</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>`,
|
||||||
|
styleUrl: './string-editor-field.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class StringEditorFieldComponent {
|
||||||
|
configuration = input.required<StringMessage>();
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
HostBinding,
|
HostBinding,
|
||||||
@@ -11,20 +12,29 @@ import {
|
|||||||
viewChild,
|
viewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule, MatSelectionList } from '@angular/material/list';
|
||||||
import { ProtoMessage } from '../model/proto-message.model';
|
import { ProtoMessage } from '../model/proto-message.model';
|
||||||
import { ProtoDefinitionService } from './proto-definition.service';
|
import { ProtoDefinitionService } from './proto-definition.service';
|
||||||
import { MatTreeModule } from '@angular/material/tree';
|
import { MatTreeModule } from '@angular/material/tree';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { writeTextFile } from '@tauri-apps/api/fs';
|
import { writeTextFile } from '@tauri-apps/api/fs';
|
||||||
import { save } from '@tauri-apps/api/dialog';
|
import { save } from '@tauri-apps/api/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { DefinitionEditorComponent } from './definition-editor/definition-editor.component';
|
||||||
|
|
||||||
declare const __TAURI__: any;
|
declare const __TAURI__: any;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-proto-definition-selector',
|
selector: 'app-proto-definition-selector',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, MatButtonModule, MatListModule, MatTreeModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatListModule,
|
||||||
|
MatTreeModule,
|
||||||
|
],
|
||||||
template: `
|
template: `
|
||||||
<h2>Protobuf Definitions</h2>
|
<h2>Protobuf Definitions</h2>
|
||||||
<button mat-button (click)="protoSelector.click()">
|
<button mat-button (click)="protoSelector.click()">
|
||||||
@@ -38,26 +48,38 @@ declare const __TAURI__: any;
|
|||||||
<button mat-button (click)="configurationSelector.click()">
|
<button mat-button (click)="configurationSelector.click()">
|
||||||
Import Configuration
|
Import Configuration
|
||||||
</button>
|
</button>
|
||||||
<!-- TODO: Make this a selection list to indicate which file is selected, and allow deselecting a file -->
|
<mat-selection-list [multiple]="false">
|
||||||
<mat-action-list>
|
|
||||||
@for (item of currentFiles(); track $index) {
|
@for (item of currentFiles(); track $index) {
|
||||||
<button mat-list-item (click)="selectProtoDefinition(item)">
|
<mat-list-option
|
||||||
|
[value]="item"
|
||||||
|
(selectedChange)="selectProtoDefinition(item)"
|
||||||
|
>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</button>
|
</mat-list-option>
|
||||||
}
|
}
|
||||||
</mat-action-list>
|
</mat-selection-list>
|
||||||
@if(selectedDefinition().length > 0) {
|
@if(selectedDefinition().length > 0) {
|
||||||
<h3>
|
<h3>
|
||||||
@if(selectedProtoFile()) { Messages in {{ selectedProtoFile() }} } @else
|
@if(selectedProtoFile()) { Messages in {{ selectedProtoFile() }} } @else
|
||||||
{Showing all messages}
|
{Showing all messages}
|
||||||
</h3>
|
</h3>
|
||||||
<mat-action-list>
|
<mat-selection-list #messageSelector [multiple]="false">
|
||||||
@for (item of selectedDefinition(); track $index) {
|
@for (item of selectedDefinition(); track $index) {
|
||||||
<button mat-list-item (click)="messageSelected.emit(item)">
|
<mat-list-option
|
||||||
{{ item.name }}
|
[value]="item"
|
||||||
</button>
|
(selectedChange)="messageSelected.emit(item)"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
matListItemIcon
|
||||||
|
mat-icon-button
|
||||||
|
(click)="editProtoMessage($event, item)"
|
||||||
|
>
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<div matListItemLine>{{ item.name }}</div>
|
||||||
|
</mat-list-option>
|
||||||
}
|
}
|
||||||
</mat-action-list>
|
</mat-selection-list>
|
||||||
}
|
}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -86,6 +108,7 @@ export class ProtoDefinitionSelectorComponent {
|
|||||||
'configurationSelector'
|
'configurationSelector'
|
||||||
);
|
);
|
||||||
protected exporter = viewChild<ElementRef<HTMLAnchorElement>>('exporter');
|
protected exporter = viewChild<ElementRef<HTMLAnchorElement>>('exporter');
|
||||||
|
protected messageSelector = viewChild<MatSelectionList>('messageSelector');
|
||||||
|
|
||||||
protected selectedDefinition = signal<ProtoMessage[]>([]);
|
protected selectedDefinition = signal<ProtoMessage[]>([]);
|
||||||
protected selectedProtoFile = signal<string | null>(null);
|
protected selectedProtoFile = signal<string | null>(null);
|
||||||
@@ -112,7 +135,8 @@ export class ProtoDefinitionSelectorComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private protoDefinitionService: ProtoDefinitionService,
|
private protoDefinitionService: ProtoDefinitionService,
|
||||||
private snackBar: MatSnackBar
|
private snackBar: MatSnackBar,
|
||||||
|
private dialog: MatDialog
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
protected async addDefinitionFiles() {
|
protected async addDefinitionFiles() {
|
||||||
@@ -209,6 +233,24 @@ export class ProtoDefinitionSelectorComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected editProtoMessage(event: MouseEvent, protoMessage: ProtoMessage) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.dialog
|
||||||
|
.open(DefinitionEditorComponent, {
|
||||||
|
data: protoMessage,
|
||||||
|
width: '40vw',
|
||||||
|
})
|
||||||
|
.afterClosed()
|
||||||
|
.subscribe(() => {
|
||||||
|
if (
|
||||||
|
protoMessage ===
|
||||||
|
this.messageSelector()?.selectedOptions.selected[0]?.value
|
||||||
|
) {
|
||||||
|
this.messageSelected.emit(structuredClone(protoMessage));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('dragover', ['$event'])
|
@HostListener('dragover', ['$event'])
|
||||||
onDrag(event: DragEvent) {
|
onDrag(event: DragEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ html {
|
|||||||
@include mat.select-theme(theme.$rose-theme);
|
@include mat.select-theme(theme.$rose-theme);
|
||||||
@include mat.snack-bar-theme(theme.$rose-theme);
|
@include mat.snack-bar-theme(theme.$rose-theme);
|
||||||
@include mat.button-toggle-theme(theme.$rose-theme);
|
@include mat.button-toggle-theme(theme.$rose-theme);
|
||||||
|
@include mat.dialog-theme(theme.$rose-theme);
|
||||||
@include custom-colours(theme.$rose-theme);
|
@include custom-colours(theme.$rose-theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user