Add initial components to proto message field

This commit is contained in:
2024-06-29 11:31:56 +09:30
parent 3606c360b4
commit ddc20daaa5
14 changed files with 175 additions and 36 deletions

View File

@@ -24,7 +24,10 @@
[opened]="leftSideOpen()"
(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
@@ -51,8 +54,8 @@
Open Folder
</button>
</div>
}@else {
<app-editor [selectedMessage]="selectedMessage()"></app-editor>
}
}@else { @if(selectedMessage()) {
<app-editor [selectedMessage]="selectedMessage()!"></app-editor>
} }
</mat-sidenav-content>
</mat-sidenav-container>

View File

@@ -1,6 +1,6 @@
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
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 { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@@ -28,15 +28,15 @@ const mobileBreakpoints = [Breakpoints.Handset, Breakpoints.TabletPortrait];
styleUrl: './app.component.scss',
imports: [
CommonModule,
RouterOutlet,
MatSidenavModule,
EditorComponent,
FileTreeComponent,
MatButtonModule,
MatIconModule,
MatSidenavModule,
MatToolbarModule,
MatTreeModule,
FileTreeComponent,
MatIconModule,
EditorComponent,
ProtoDefinitionSelectorComponent,
RouterOutlet,
],
})
export class AppComponent {
@@ -52,6 +52,15 @@ export class AppComponent {
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) {
breakpointObserver
.observe(mobileBreakpoints)

View File

@@ -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>

View File

@@ -1,23 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component, ElementRef, effect, input, viewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
Component,
ElementRef,
effect,
input,
signal,
viewChild,
} from '@angular/core';
import hljs from 'highlight.js/lib/core';
import { ProtoMessage } from '../model/proto-message.model';
import { ProtoDefinitionService } from '../proto-definition.service';
import { ProtoFieldComponent } from '../proto-field/proto-field.component';
@Component({
selector: 'app-editor',
standalone: true,
imports: [CommonModule, FormsModule],
imports: [CommonModule, ProtoFieldComponent],
templateUrl: './editor.component.html',
styleUrl: './editor.component.scss',
})
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>();
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');
constructor(private protoDefinitionService: ProtoDefinitionService) {
constructor() {
effect(() => {
const element = this.code()?.nativeElement;
if (element) {

View File

@@ -1,3 +1,4 @@
<h2>{{ workspaceName() }}</h2>
<mat-tree [dataSource]="dataSource()" [treeControl]="treeControl">
<mat-tree-node
*matTreeNodeDef="let node"

View File

@@ -16,3 +16,9 @@
.mat-tree-node {
cursor: pointer;
}
h2 {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}

View File

@@ -1,12 +1,6 @@
import { FlatTreeControl } from '@angular/cdk/tree';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
output,
} from '@angular/core';
import { Component, computed, input, output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import {
@@ -31,11 +25,12 @@ interface FileNode {
@Component({
selector: 'app-file-tree',
standalone: true,
imports: [CommonModule, MatTreeModule, MatIconModule, MatButtonModule],
imports: [CommonModule, MatButtonModule, MatIconModule, MatTreeModule],
templateUrl: './file-tree.component.html',
styleUrl: './file-tree.component.scss',
})
export class FileTreeComponent {
workspaceName = input<string | null>();
files = input<FileOrFolder[]>([]);
fileSelected = output<FileOrFolder>();

View File

@@ -0,0 +1,3 @@
:host {
display: block;
}

View 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[]>();
}

View File

@@ -86,10 +86,9 @@ export const EnumMessage = (options: string[]) => ({
options,
});
export interface ProtoMessageField {
export interface ProtoMessageField<T extends MessageConfiguration> {
name: string;
configuration: MessageConfiguration;
value: any;
configuration: T;
}
export interface ProtoBase {
@@ -97,7 +96,7 @@ export interface ProtoBase {
}
export interface ProtoMessage extends ProtoBase {
values: ProtoMessageField[];
values: ProtoMessageField<any>[];
}
export interface ProtoEnum extends ProtoBase {
@@ -106,5 +105,5 @@ export interface ProtoEnum extends ProtoBase {
export const UnknownProto = (name: string): ProtoMessage => ({
name,
values: [{ name: 'Raw JSON', configuration: RawMessage(), value: '' }],
values: [{ name: 'Raw JSON', configuration: RawMessage() }],
});

View File

@@ -1,26 +1,23 @@
import { MediaMatcher } from '@angular/cdk/layout';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
HostBinding,
HostListener,
OnDestroy,
output,
signal,
viewChild,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatListModule } from '@angular/material/list';
import { ProtoMessage } from '../model/proto-message.model';
import { ProtoDefinitionService } from '../proto-definition.service';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-proto-definition-selector',
standalone: true,
imports: [CommonModule, MatListModule, MatButtonModule],
imports: [CommonModule, MatButtonModule, MatListModule],
template: `
<h2>Protobuf Definitions</h2>
<mat-action-list>

View File

@@ -164,7 +164,6 @@ export class ProtoDefinitionService {
convertedFields.values.push({
name: field.name,
configuration: type,
value: '',
});
} else {
console.error(`Failed to find type ${field.type}`);

View File

@@ -0,0 +1,3 @@
:host {
display: block;
}

View 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;
}