Add initial components to proto message field
This commit is contained in:
@@ -24,7 +24,10 @@
|
|||||||
[opened]="leftSideOpen()"
|
[opened]="leftSideOpen()"
|
||||||
(closed)="leftSideOpen.set(false)"
|
(closed)="leftSideOpen.set(false)"
|
||||||
>
|
>
|
||||||
<app-file-tree [files]="files()"></app-file-tree>
|
<app-file-tree
|
||||||
|
[workspaceName]="directoryName()"
|
||||||
|
[files]="files()"
|
||||||
|
></app-file-tree>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
}
|
}
|
||||||
<mat-sidenav
|
<mat-sidenav
|
||||||
@@ -51,8 +54,8 @@
|
|||||||
Open Folder
|
Open Folder
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}@else {
|
}@else { @if(selectedMessage()) {
|
||||||
<app-editor [selectedMessage]="selectedMessage()"></app-editor>
|
<app-editor [selectedMessage]="selectedMessage()!"></app-editor>
|
||||||
}
|
} }
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, signal } from '@angular/core';
|
import { Component, computed, signal } from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -28,15 +28,15 @@ const mobileBreakpoints = [Breakpoints.Handset, Breakpoints.TabletPortrait];
|
|||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
RouterOutlet,
|
EditorComponent,
|
||||||
MatSidenavModule,
|
FileTreeComponent,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatSidenavModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
MatTreeModule,
|
MatTreeModule,
|
||||||
FileTreeComponent,
|
|
||||||
MatIconModule,
|
|
||||||
EditorComponent,
|
|
||||||
ProtoDefinitionSelectorComponent,
|
ProtoDefinitionSelectorComponent,
|
||||||
|
RouterOutlet,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
@@ -52,6 +52,15 @@ export class AppComponent {
|
|||||||
this.breakpointObserver.isMatched(mobileBreakpoints)
|
this.breakpointObserver.isMatched(mobileBreakpoints)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected directoryName = computed(() => {
|
||||||
|
const directory = this.selectedDirectory();
|
||||||
|
if (directory) {
|
||||||
|
const directorySplit = directory.split('/');
|
||||||
|
return directorySplit[directorySplit.length - 1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
constructor(private breakpointObserver: BreakpointObserver) {
|
constructor(private breakpointObserver: BreakpointObserver) {
|
||||||
breakpointObserver
|
breakpointObserver
|
||||||
.observe(mobileBreakpoints)
|
.observe(mobileBreakpoints)
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
<!-- <div>@for (item of items; track $index) {} @switch () {}</div> -->
|
<div>
|
||||||
|
@for (item of selectedMessage().values; track $index) {
|
||||||
|
<app-proto-field
|
||||||
|
[configuration]="item.configuration"
|
||||||
|
[(value)]="values()[$index]"
|
||||||
|
></app-proto-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<pre><code class="language-json" #code>{{fileContents()}}</code></pre>
|
<pre><code class="language-json" #code>{{fileContents()}}</code></pre>
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, ElementRef, effect, input, viewChild } from '@angular/core';
|
import {
|
||||||
import { FormsModule } from '@angular/forms';
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
effect,
|
||||||
|
input,
|
||||||
|
signal,
|
||||||
|
viewChild,
|
||||||
|
} from '@angular/core';
|
||||||
import hljs from 'highlight.js/lib/core';
|
import hljs from 'highlight.js/lib/core';
|
||||||
import { ProtoMessage } from '../model/proto-message.model';
|
import { ProtoMessage } from '../model/proto-message.model';
|
||||||
import { ProtoDefinitionService } from '../proto-definition.service';
|
import { ProtoFieldComponent } from '../proto-field/proto-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-editor',
|
selector: 'app-editor',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule],
|
imports: [CommonModule, ProtoFieldComponent],
|
||||||
templateUrl: './editor.component.html',
|
templateUrl: './editor.component.html',
|
||||||
styleUrl: './editor.component.scss',
|
styleUrl: './editor.component.scss',
|
||||||
})
|
})
|
||||||
export class EditorComponent {
|
export class EditorComponent {
|
||||||
|
// TODO: This needs to be reworked so we have a local property and implement some kind of auto-save
|
||||||
fileContents = input<string>();
|
fileContents = input<string>();
|
||||||
selectedMessage = input<ProtoMessage>();
|
selectedMessage = input.required<ProtoMessage>();
|
||||||
|
// TODO: This needs to start with the parsed file contents, and get updated when the code value changes
|
||||||
|
protected values = signal([]);
|
||||||
private code = viewChild<ElementRef<HTMLElement>>('code');
|
private code = viewChild<ElementRef<HTMLElement>>('code');
|
||||||
|
|
||||||
constructor(private protoDefinitionService: ProtoDefinitionService) {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const element = this.code()?.nativeElement;
|
const element = this.code()?.nativeElement;
|
||||||
if (element) {
|
if (element) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<h2>{{ workspaceName() }}</h2>
|
||||||
<mat-tree [dataSource]="dataSource()" [treeControl]="treeControl">
|
<mat-tree [dataSource]="dataSource()" [treeControl]="treeControl">
|
||||||
<mat-tree-node
|
<mat-tree-node
|
||||||
*matTreeNodeDef="let node"
|
*matTreeNodeDef="let node"
|
||||||
|
|||||||
@@ -16,3 +16,9 @@
|
|||||||
.mat-tree-node {
|
.mat-tree-node {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { FlatTreeControl } from '@angular/cdk/tree';
|
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import { Component, computed, input, output } from '@angular/core';
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
computed,
|
|
||||||
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 {
|
import {
|
||||||
@@ -31,11 +25,12 @@ interface FileNode {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-file-tree',
|
selector: 'app-file-tree',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, MatTreeModule, MatIconModule, MatButtonModule],
|
imports: [CommonModule, MatButtonModule, MatIconModule, MatTreeModule],
|
||||||
templateUrl: './file-tree.component.html',
|
templateUrl: './file-tree.component.html',
|
||||||
styleUrl: './file-tree.component.scss',
|
styleUrl: './file-tree.component.scss',
|
||||||
})
|
})
|
||||||
export class FileTreeComponent {
|
export class FileTreeComponent {
|
||||||
|
workspaceName = input<string | null>();
|
||||||
files = input<FileOrFolder[]>([]);
|
files = input<FileOrFolder[]>([]);
|
||||||
|
|
||||||
fileSelected = output<FileOrFolder>();
|
fileSelected = output<FileOrFolder>();
|
||||||
|
|||||||
3
src/app/list-field/list-field.component.scss
Normal file
3
src/app/list-field/list-field.component.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
34
src/app/list-field/list-field.component.ts
Normal file
34
src/app/list-field/list-field.component.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
input,
|
||||||
|
model,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { ProtoFieldComponent } from '../proto-field/proto-field.component';
|
||||||
|
import {
|
||||||
|
EnumMessage,
|
||||||
|
ListMessage,
|
||||||
|
ProtoMessageField,
|
||||||
|
} from '../model/proto-message.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-list-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, MatButton, MatIconModule, ProtoFieldComponent],
|
||||||
|
template: ` @for(value of values(); track values()) {
|
||||||
|
<app-proto-field
|
||||||
|
[configuration]="configuration().subConfiguration"
|
||||||
|
[value]="value"
|
||||||
|
></app-proto-field>
|
||||||
|
}
|
||||||
|
<button mat-icon-button><mat-icon>add</mat-icon></button>`,
|
||||||
|
styleUrl: './list-field.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ListFieldComponent {
|
||||||
|
configuration = input.required<ListMessage>();
|
||||||
|
values = model<any[]>();
|
||||||
|
}
|
||||||
@@ -86,10 +86,9 @@ export const EnumMessage = (options: string[]) => ({
|
|||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface ProtoMessageField {
|
export interface ProtoMessageField<T extends MessageConfiguration> {
|
||||||
name: string;
|
name: string;
|
||||||
configuration: MessageConfiguration;
|
configuration: T;
|
||||||
value: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProtoBase {
|
export interface ProtoBase {
|
||||||
@@ -97,7 +96,7 @@ export interface ProtoBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ProtoMessage extends ProtoBase {
|
export interface ProtoMessage extends ProtoBase {
|
||||||
values: ProtoMessageField[];
|
values: ProtoMessageField<any>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProtoEnum extends ProtoBase {
|
export interface ProtoEnum extends ProtoBase {
|
||||||
@@ -106,5 +105,5 @@ export interface ProtoEnum extends ProtoBase {
|
|||||||
|
|
||||||
export const UnknownProto = (name: string): ProtoMessage => ({
|
export const UnknownProto = (name: string): ProtoMessage => ({
|
||||||
name,
|
name,
|
||||||
values: [{ name: 'Raw JSON', configuration: RawMessage(), value: '' }],
|
values: [{ name: 'Raw JSON', configuration: RawMessage() }],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
import { MediaMatcher } from '@angular/cdk/layout';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
HostBinding,
|
HostBinding,
|
||||||
HostListener,
|
HostListener,
|
||||||
OnDestroy,
|
|
||||||
output,
|
output,
|
||||||
signal,
|
signal,
|
||||||
viewChild,
|
viewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } 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 { MatButtonModule } from '@angular/material/button';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-proto-definition-selector',
|
selector: 'app-proto-definition-selector',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, MatListModule, MatButtonModule],
|
imports: [CommonModule, MatButtonModule, MatListModule],
|
||||||
template: `
|
template: `
|
||||||
<h2>Protobuf Definitions</h2>
|
<h2>Protobuf Definitions</h2>
|
||||||
<mat-action-list>
|
<mat-action-list>
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ export class ProtoDefinitionService {
|
|||||||
convertedFields.values.push({
|
convertedFields.values.push({
|
||||||
name: field.name,
|
name: field.name,
|
||||||
configuration: type,
|
configuration: type,
|
||||||
value: '',
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(`Failed to find type ${field.type}`);
|
console.error(`Failed to find type ${field.type}`);
|
||||||
|
|||||||
3
src/app/proto-field/proto-field.component.scss
Normal file
3
src/app/proto-field/proto-field.component.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
74
src/app/proto-field/proto-field.component.ts
Normal file
74
src/app/proto-field/proto-field.component.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
input,
|
||||||
|
model,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { ListFieldComponent } from '../list-field/list-field.component';
|
||||||
|
import {
|
||||||
|
EnumMessage,
|
||||||
|
ListMessage,
|
||||||
|
MessageConfiguration,
|
||||||
|
MessageTypeEnum,
|
||||||
|
} from '../model/proto-message.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-proto-field',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ListFieldComponent,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
],
|
||||||
|
template: `@switch (configuration().type) { @case (MessageTypeEnum.String) {
|
||||||
|
<input [(ngModel)]="value" />
|
||||||
|
} @case (MessageTypeEnum.Numeric) {
|
||||||
|
<input type="number" [(ngModel)]="value" />
|
||||||
|
} @case (MessageTypeEnum.Boolean) {
|
||||||
|
<mat-checkbox [(ngModel)]="value"></mat-checkbox>
|
||||||
|
} @case(MessageTypeEnum.Enum) {
|
||||||
|
<mat-select [(value)]="value">
|
||||||
|
@for(option of enumConfiguration()!.options; track
|
||||||
|
enumConfiguration()!.options) {
|
||||||
|
<mat-option [value]="option"></mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
} @case (MessageTypeEnum.List) {
|
||||||
|
<app-list-field
|
||||||
|
[configuration]="listConfiguration()!"
|
||||||
|
[(values)]="value"
|
||||||
|
></app-list-field>
|
||||||
|
} @case (MessageTypeEnum.Map){} @case (MessageTypeEnum.Object) {} @case
|
||||||
|
(MessageTypeEnum.Raw) {}}`,
|
||||||
|
styleUrl: './proto-field.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ProtoFieldComponent {
|
||||||
|
configuration = input.required<MessageConfiguration>();
|
||||||
|
value = model<any>();
|
||||||
|
|
||||||
|
protected enumConfiguration = computed(() => {
|
||||||
|
if (this.configuration().type === MessageTypeEnum.Enum) {
|
||||||
|
return this.configuration() as EnumMessage;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected listConfiguration = computed(() => {
|
||||||
|
if (this.configuration().type === MessageTypeEnum.List) {
|
||||||
|
return this.configuration() as ListMessage;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected readonly MessageTypeEnum = MessageTypeEnum;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user