From b78d4aea696a79ee28bd6d5c0ba0b9227327033a Mon Sep 17 00:00:00 2001 From: vato007 Date: Thu, 18 Jul 2024 22:07:16 +0930 Subject: [PATCH] Add configuration editor for messages, monaco editor to string type --- .../proto-field/proto-field.component.ts | 38 +++++- .../string-field/string-field.component.css | 3 + .../string-field/string-field.component.ts | 57 +++++++++ src/app/model/proto-message.model.ts | 17 ++- .../definition-editor-field.component.css | 3 + .../definition-editor-field.component.ts | 109 ++++++++++++++++++ .../definition-editor.component.scss | 0 .../definition-editor.component.ts | 66 +++++++++++ .../list-editor-field.component.ts | 22 ++++ .../map-editor-field.component.ts | 39 +++++++ .../object-editor-field.component.ts | 37 ++++++ .../string-editor-field.component.scss | 3 + .../string-editor-field.component.ts | 35 ++++++ .../proto-definition-selector.component.ts | 68 ++++++++--- src/styles.scss | 1 + 15 files changed, 479 insertions(+), 19 deletions(-) create mode 100644 src/app/editor/string-field/string-field.component.css create mode 100644 src/app/editor/string-field/string-field.component.ts create mode 100644 src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.css create mode 100644 src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.ts create mode 100644 src/app/proto-definition-selector/definition-editor/definition-editor.component.scss create mode 100644 src/app/proto-definition-selector/definition-editor/definition-editor.component.ts create mode 100644 src/app/proto-definition-selector/definition-editor/list-editor-field/list-editor-field.component.ts create mode 100644 src/app/proto-definition-selector/definition-editor/map-editor-field/map-editor-field.component.ts create mode 100644 src/app/proto-definition-selector/definition-editor/object-editor-field/object-editor-field.component.ts create mode 100644 src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.scss create mode 100644 src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.ts diff --git a/src/app/editor/proto-field/proto-field.component.ts b/src/app/editor/proto-field/proto-field.component.ts index 2ec8ca4..5f1bab2 100644 --- a/src/app/editor/proto-field/proto-field.component.ts +++ b/src/app/editor/proto-field/proto-field.component.ts @@ -17,11 +17,14 @@ import { MapMessage, MessageConfiguration, MessageTypeEnum, + NumericMessage, ObjectMessage, + StringMessage, } from '../../model/proto-message.model'; import { ListFieldComponent } from '../list-field/list-field.component'; import { MapFieldComponent } from '../map-field/map-field.component'; import { ObjectFieldComponent } from '../object-field/object-field.component'; +import { StringFieldComponent } from '../string-field/string-field.component'; @Component({ selector: 'app-proto-field', @@ -36,16 +39,33 @@ import { ObjectFieldComponent } from '../object-field/object-field.component'; MatSelectModule, MatInputModule, ObjectFieldComponent, + StringFieldComponent, ], template: `@switch (configuration().type) { @case (MessageTypeEnum.String) { - - {{ label() }} - - + } @case (MessageTypeEnum.Numeric) { {{ label() }} - + + Number should not be less than + {{ numericConfiguration().min }} + Number should not greater than + {{ numericConfiguration().max }} } @case (MessageTypeEnum.Boolean) {

@@ -89,6 +109,14 @@ export class ProtoFieldComponent { configuration = input.required(); value = model(); + protected stringConfiguration = computed( + () => this.configuration() as StringMessage + ); + + protected numericConfiguration = computed( + () => this.configuration() as NumericMessage + ); + protected enumConfiguration = computed( () => this.configuration() as EnumMessage ); diff --git a/src/app/editor/string-field/string-field.component.css b/src/app/editor/string-field/string-field.component.css new file mode 100644 index 0000000..c7acb4b --- /dev/null +++ b/src/app/editor/string-field/string-field.component.css @@ -0,0 +1,3 @@ +mat-form-field { + width: 100%; +} diff --git a/src/app/editor/string-field/string-field.component.ts b/src/app/editor/string-field/string-field.component.ts new file mode 100644 index 0000000..eba7b12 --- /dev/null +++ b/src/app/editor/string-field/string-field.component.ts @@ -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') { + + {{ label() }} + + Text should be less than + {{ configuration().maxLength }} characters + + } @else { + + }`, + styleUrl: './string-field.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class StringFieldComponent { + label = input(); + configuration = input.required(); + value = model(); + + protected editorOptions = { + theme: 'vs-dark', + language: 'sql', + automaticLayout: true, + }; +} diff --git a/src/app/model/proto-message.model.ts b/src/app/model/proto-message.model.ts index ea71afd..6e2a26f 100644 --- a/src/app/model/proto-message.model.ts +++ b/src/app/model/proto-message.model.ts @@ -9,8 +9,11 @@ export enum MessageTypeEnum { Enum = 'enum', } +export type StringMessageTextType = 'text' | 'sql'; + export interface StringMessage extends MessageConfiguration { maxLength?: number; + textType: StringMessageTextType; } export interface BooleanMessage extends MessageConfiguration {} @@ -43,9 +46,13 @@ export interface MessageConfiguration { type: MessageTypeEnum; } -export const StringMessage = (maxLength?: number): StringMessage => ({ +export const StringMessage = ( + maxLength?: number, + textType: StringMessageTextType = 'text' +): StringMessage => ({ type: MessageTypeEnum.String, maxLength, + textType, }); export const BooleanMessage = (): BooleanMessage => ({ @@ -114,3 +121,11 @@ export const UnknownProto = ( fullName, values: [{ name: 'Raw JSON', configuration: RawMessage() }], }); + +export const EDITABLE_MESSAGE_TYPES = [ + MessageTypeEnum.String, + MessageTypeEnum.Numeric, + MessageTypeEnum.List, + MessageTypeEnum.Map, + MessageTypeEnum.Object, +]; diff --git a/src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.css b/src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.css new file mode 100644 index 0000000..9ee20ad --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.css @@ -0,0 +1,3 @@ +mat-form-field { + display: block; +} diff --git a/src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.ts b/src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.ts new file mode 100644 index 0000000..9ea50dc --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/definition-editor-field/definition-editor-field.component.ts @@ -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) { + + } @case (MessageTypeEnum.Numeric) { @let configuration = + numericConfiguration(fieldConfiguration()); + + Min + + Min should not be greater than {{ configuration.max }} + + + Max + + Max should not be less than {{ configuration.min }} + + } @case (MessageTypeEnum.List) { + + } @case (MessageTypeEnum.Map) { + + } @case(MessageTypeEnum.Object) { + + } }`, + styleUrl: './definition-editor-field.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DefinitionEditorFieldComponent { + fieldConfiguration = input.required(); + + 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; +} diff --git a/src/app/proto-definition-selector/definition-editor/definition-editor.component.scss b/src/app/proto-definition-selector/definition-editor/definition-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/proto-definition-selector/definition-editor/definition-editor.component.ts b/src/app/proto-definition-selector/definition-editor/definition-editor.component.ts new file mode 100644 index 0000000..c1e0987 --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/definition-editor.component.ts @@ -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: ` +

{{ protoMessage.name }}

+ + @for (field of editableMessages; track $index) { +

{{ field.name }}

+ + } +
+ + + + `, + 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) {} +} diff --git a/src/app/proto-definition-selector/definition-editor/list-editor-field/list-editor-field.component.ts b/src/app/proto-definition-selector/definition-editor/list-editor-field/list-editor-field.component.ts new file mode 100644 index 0000000..78efc83 --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/list-editor-field/list-editor-field.component.ts @@ -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: ``, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ListEditorFieldComponent { + field = input.required(); +} diff --git a/src/app/proto-definition-selector/definition-editor/map-editor-field/map-editor-field.component.ts b/src/app/proto-definition-selector/definition-editor/map-editor-field/map-editor-field.component.ts new file mode 100644 index 0000000..ff189a1 --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/map-editor-field/map-editor-field.component.ts @@ -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: ` +

Key Configuration

+ + @if(isEditable(field().valueConfiguration)) { +

Value Configuration

+ + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MapEditorFieldComponent { + field = input.required(); + + protected isEditable(configuration: MessageConfiguration) { + return EDITABLE_MESSAGE_TYPES.includes(configuration.type); + } +} diff --git a/src/app/proto-definition-selector/definition-editor/object-editor-field/object-editor-field.component.ts b/src/app/proto-definition-selector/definition-editor/object-editor-field/object-editor-field.component.ts new file mode 100644 index 0000000..7c2129a --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/object-editor-field/object-editor-field.component.ts @@ -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) { +

{{ field.name }}

+ + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ObjectEditorFieldComponent { + field = input.required(); + + protected editableFields = computed(() => + this.field().messageDefinition.values.filter((field) => + EDITABLE_MESSAGE_TYPES.includes(field.configuration.type) + ) + ); +} diff --git a/src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.scss b/src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.scss new file mode 100644 index 0000000..9ee20ad --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.scss @@ -0,0 +1,3 @@ +mat-form-field { + display: block; +} diff --git a/src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.ts b/src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.ts new file mode 100644 index 0000000..79449ff --- /dev/null +++ b/src/app/proto-definition-selector/definition-editor/string-editor-field/string-editor-field.component.ts @@ -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: ` + Max Length + + + + Field Type + + Text + SQL + + `, + styleUrl: './string-editor-field.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class StringEditorFieldComponent { + configuration = input.required(); +} diff --git a/src/app/proto-definition-selector/proto-definition-selector.component.ts b/src/app/proto-definition-selector/proto-definition-selector.component.ts index 16b968e..4d43a16 100644 --- a/src/app/proto-definition-selector/proto-definition-selector.component.ts +++ b/src/app/proto-definition-selector/proto-definition-selector.component.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, ElementRef, HostBinding, @@ -11,20 +12,29 @@ import { viewChild, } from '@angular/core'; 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 { ProtoDefinitionService } from './proto-definition.service'; import { MatTreeModule } from '@angular/material/tree'; import { MatSnackBar } from '@angular/material/snack-bar'; import { writeTextFile } from '@tauri-apps/api/fs'; 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; @Component({ selector: 'app-proto-definition-selector', standalone: true, - imports: [CommonModule, MatButtonModule, MatListModule, MatTreeModule], + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + MatListModule, + MatTreeModule, + ], template: `

Protobuf Definitions

- - + @for (item of currentFiles(); track $index) { - + } - + @if(selectedDefinition().length > 0) {

@if(selectedProtoFile()) { Messages in {{ selectedProtoFile() }} } @else {Showing all messages}

- + @for (item of selectedDefinition(); track $index) { - + + +
{{ item.name }}
+
} -
+ } >('exporter'); + protected messageSelector = viewChild('messageSelector'); protected selectedDefinition = signal([]); protected selectedProtoFile = signal(null); @@ -112,7 +135,8 @@ export class ProtoDefinitionSelectorComponent { constructor( private protoDefinitionService: ProtoDefinitionService, - private snackBar: MatSnackBar + private snackBar: MatSnackBar, + private dialog: MatDialog ) {} 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']) onDrag(event: DragEvent) { event.preventDefault(); diff --git a/src/styles.scss b/src/styles.scss index 9878d19..6bb4c59 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -31,6 +31,7 @@ html { @include mat.select-theme(theme.$rose-theme); @include mat.snack-bar-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); }