diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts index 51dcfb1..98b425a 100644 --- a/src/app/editor/editor.component.ts +++ b/src/app/editor/editor.component.ts @@ -11,7 +11,7 @@ import { } from '@angular/core'; import hljs from 'highlight.js/lib/core'; import { ProtoMessage } from '../model/proto-message.model'; -import { ProtoFieldComponent } from '../proto-field/proto-field.component'; +import { ProtoFieldComponent } from './proto-field/proto-field.component'; import { DomSanitizer } from '@angular/platform-browser'; @Component({ diff --git a/src/app/list-field/list-field.component.scss b/src/app/editor/list-field/list-field.component.scss similarity index 100% rename from src/app/list-field/list-field.component.scss rename to src/app/editor/list-field/list-field.component.scss diff --git a/src/app/list-field/list-field.component.ts b/src/app/editor/list-field/list-field.component.ts similarity index 91% rename from src/app/list-field/list-field.component.ts rename to src/app/editor/list-field/list-field.component.ts index 0253f90..1115a8b 100644 --- a/src/app/list-field/list-field.component.ts +++ b/src/app/editor/list-field/list-field.component.ts @@ -10,7 +10,7 @@ import { } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { ListMessage } from '../model/proto-message.model'; +import { ListMessage } from '../../model/proto-message.model'; import { ProtoFieldComponent } from '../proto-field/proto-field.component'; @Component({ @@ -22,7 +22,7 @@ import { ProtoFieldComponent } from '../proto-field/proto-field.component'; MatIconModule, forwardRef(() => ProtoFieldComponent), ], - template: `

{{ label() }}

+ template: `

{{ label() }}

@if(values()) { @for(value of values(); track $index) {
key == null || key === ''; + +@Component({ + selector: 'app-map-field', + standalone: true, + imports: [ + CommonModule, + forwardRef(() => ProtoFieldComponent), + MatIconModule, + MatButtonModule, + ], + template: `

{{ label() }}

+ @if(valuePairs()) { @for(value of valuePairs(); track $index) { +
+ + + +
+ } } + `, + styleUrl: './map-field.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MapFieldComponent { + label = input(); + configuration = input.required(); + values = model>(); + + valuePairs = signal([]); + + private changedInternal = false; + + constructor() { + effect( + () => { + // TODO: Super hacky but can't really think of another way to keep these in sync + // without removing an entry when the key gets blanked. Would need an alternate + // design that updated on blur only perhaps + if (this.changedInternal) { + this.changedInternal = false; + return; + } + const values = this.values(); + if (values) { + this.valuePairs.set(Object.entries(values)); + } + }, + { allowSignalWrites: true } + ); + } + + add() { + const existingValues = this.valuePairs(); + if (existingValues) { + const newValues = [...existingValues]; + newValues.push([undefined, undefined]); + this.valuePairs.set(newValues); + } else { + this.valuePairs.set([[undefined, undefined]]); + } + } + + remove(index: number) { + const newValues = { ...this.values()! }; + const key = this.valuePairs()[index][0]; + delete newValues[key]; + this.changedInternal = true; + this.values.set(newValues); + + const newValuePairs = [...this.valuePairs()]; + newValuePairs.splice(index, 1); + this.valuePairs.set(newValuePairs); + } + + updateKey(index: number, key: string | number) { + const values = { ...this.values() }; + const valuePairs = [...this.valuePairs()]; + const pair = [...valuePairs[index]]; + if (!keyIsEmpty(pair[0])) { + delete values[pair[0]]; + } + if (!keyIsEmpty(key)) { + values[key] = pair[1]; + } + this.changedInternal = true; + this.values.set(values); + + pair[0] = key; + valuePairs[index] = pair; + this.valuePairs.set(valuePairs); + } + + updateValue(index: number, value: any) { + const valuePairs = [...this.valuePairs()]; + const pair = [...valuePairs[index]]; + if (!keyIsEmpty(pair[0])) { + let existing = { ...this.values()! }; + existing[pair[0]] = value; + this.changedInternal = true; + this.values.set(existing); + } + pair[1] = value; + valuePairs[index] = pair; + this.valuePairs.set(valuePairs); + } +} diff --git a/src/app/proto-field/proto-field.component.scss b/src/app/editor/proto-field/proto-field.component.scss similarity index 100% rename from src/app/proto-field/proto-field.component.scss rename to src/app/editor/proto-field/proto-field.component.scss diff --git a/src/app/proto-field/proto-field.component.ts b/src/app/editor/proto-field/proto-field.component.ts similarity index 73% rename from src/app/proto-field/proto-field.component.ts rename to src/app/editor/proto-field/proto-field.component.ts index b294cf0..53ccf08 100644 --- a/src/app/proto-field/proto-field.component.ts +++ b/src/app/editor/proto-field/proto-field.component.ts @@ -9,15 +9,17 @@ import { import { FormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; -import { ListFieldComponent } from '../list-field/list-field.component'; import { EnumMessage, ListMessage, + MapMessage, MessageConfiguration, MessageTypeEnum, -} from '../model/proto-message.model'; -import { MatInputModule } from '@angular/material/input'; +} from '../../model/proto-message.model'; +import { ListFieldComponent } from '../list-field/list-field.component'; +import { MapFieldComponent } from '../map-field/map-field.component'; @Component({ selector: 'app-proto-field', @@ -26,6 +28,7 @@ import { MatInputModule } from '@angular/material/input'; CommonModule, FormsModule, ListFieldComponent, + MapFieldComponent, MatCheckboxModule, MatFormFieldModule, MatSelectModule, @@ -49,7 +52,7 @@ import { MatInputModule } from '@angular/material/input'; {{ label() }} - @for(option of enumConfiguration()!.options; track + @for(option of enumConfiguration().options; track enumConfiguration()!.options) { None {{ option }} @@ -59,11 +62,16 @@ import { MatInputModule } from '@angular/material/input'; } @case (MessageTypeEnum.List) { - } @case (MessageTypeEnum.Map){} @case (MessageTypeEnum.Object) {} @case - (MessageTypeEnum.Raw) {}}`, + } @case (MessageTypeEnum.Map){ + + } @case (MessageTypeEnum.Object) {} @case (MessageTypeEnum.Raw) {}}`, styleUrl: './proto-field.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -72,19 +80,17 @@ export class ProtoFieldComponent { configuration = input.required(); value = model(); - protected enumConfiguration = computed(() => { - if (this.configuration().type === MessageTypeEnum.Enum) { - return this.configuration() as EnumMessage; - } - return; - }); + protected enumConfiguration = computed( + () => this.configuration() as EnumMessage + ); - protected listConfiguration = computed(() => { - if (this.configuration().type === MessageTypeEnum.List) { - return this.configuration() as ListMessage; - } - return; - }); + protected listConfiguration = computed( + () => this.configuration() as ListMessage + ); + + protected mapConfiguration = computed( + () => this.configuration() as MapMessage + ); protected readonly MessageTypeEnum = MessageTypeEnum; } diff --git a/src/app/list-field/list-field.component.spec.ts b/src/app/list-field/list-field.component.spec.ts deleted file mode 100644 index e69de29..0000000 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 6d9c439..c66c169 100644 --- a/src/app/proto-definition-selector/proto-definition-selector.component.ts +++ b/src/app/proto-definition-selector/proto-definition-selector.component.ts @@ -12,7 +12,7 @@ import { 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 { ProtoDefinitionService } from './proto-definition.service'; @Component({ selector: 'app-proto-definition-selector', diff --git a/src/app/proto-definition.service.spec.ts b/src/app/proto-definition-selector/proto-definition.service.spec.ts similarity index 98% rename from src/app/proto-definition.service.spec.ts rename to src/app/proto-definition-selector/proto-definition.service.spec.ts index 417cd48..1f0d305 100644 --- a/src/app/proto-definition.service.spec.ts +++ b/src/app/proto-definition-selector/proto-definition.service.spec.ts @@ -8,7 +8,7 @@ import { MessageTypeEnum, ObjectMessage, ProtoMessage, -} from './model/proto-message.model'; +} from '../model/proto-message.model'; import { ProtoDefinitionService } from './proto-definition.service'; let testProto = ` diff --git a/src/app/proto-definition.service.ts b/src/app/proto-definition-selector/proto-definition.service.ts similarity index 99% rename from src/app/proto-definition.service.ts rename to src/app/proto-definition-selector/proto-definition.service.ts index ed61208..b9f7a57 100644 --- a/src/app/proto-definition.service.ts +++ b/src/app/proto-definition-selector/proto-definition.service.ts @@ -14,7 +14,7 @@ import { ProtoMessage, StringMessage, UnknownProto, -} from './model/proto-message.model'; +} from '../model/proto-message.model'; @Injectable({ providedIn: 'root',