Add monaco editor for json editing and diff view (#4)

Reviewed-on: #4
This commit is contained in:
2024-07-17 21:58:09 +09:30
parent 1654824839
commit 12c36be93d
10 changed files with 100 additions and 31 deletions

View File

@@ -22,6 +22,7 @@ mat-sidenav-content {
app-editor {
flex: 1;
width: 100%;
}
.mat-sidenav.mat-drawer-end,

View File

@@ -1,6 +1,6 @@
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { CommonModule } from '@angular/common';
import { Component, computed, signal } from '@angular/core';
import { Component, computed, effect, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';

View File

@@ -13,6 +13,7 @@ import {
MAT_ICON_DEFAULT_OPTIONS,
MatIconRegistry,
} from '@angular/material/icon';
import { provideMonacoEditor } from 'ngx-monaco-editor-v2';
export const appConfig: ApplicationConfig = {
providers: [
@@ -41,5 +42,6 @@ export const appConfig: ApplicationConfig = {
provide: MAT_ICON_DEFAULT_OPTIONS,
useValue: { fontSet: 'material-symbols-rounded' },
},
provideMonacoEditor(),
],
};

View File

@@ -14,13 +14,36 @@
></app-proto-field>
} }
</div>
@if(showRaw()) {
<h2>Preview</h2>
<div class="actions">
<button mat-flat-button (click)="copyToClipboard()">Copy</button>
</div>
<pre><code #code></code></pre>
}
<div class="preview">
<h2>
Preview
<mat-button-toggle-group [(value)]="previewType">
<mat-button-toggle value="raw">Raw</mat-button-toggle>
<mat-button-toggle value="edit">Edit</mat-button-toggle>
@if(selectedFile()) {
<mat-button-toggle value="diff">Diff</mat-button-toggle>
}
</mat-button-toggle-group>
<button mat-flat-button (click)="copyToClipboard()">Copy</button>
</h2>
@switch (previewType()) { @case ('raw') {
<pre><code #code></code></pre>
} @case ('edit') {
<ngx-monaco-editor
#monaco
[options]="editorOptions"
[ngModel]="serialisedValues()"
(ngModelChange)="updateValuesFromText($event)"
></ngx-monaco-editor>
} @case ('diff') {
<ngx-monaco-diff-editor
[options]="editorOptions"
[originalModel]="originalModel()"
[modifiedModel]="currentModel()"
></ngx-monaco-diff-editor>
} }
</div>
}
<a #downloader [href]="saveHref()" [download]="downloadName()"></a>

View File

@@ -2,10 +2,11 @@
padding: var(--mat-sidenav-container-shape);
display: flex;
flex-direction: column;
box-sizing: border-box;
}
pre,
.editor-items {
.editor-items,
.preview {
flex: 1;
overflow: auto;
}
@@ -14,11 +15,16 @@ pre,
padding: 16px;
}
.actions {
flex: 0 0 auto;
}
.proto-title {
display: flex;
justify-content: space-between;
}
mat-button-toggle-group {
width: fit-content;
}
ngx-monaco-editor,
ngx-monaco-diff-editor {
height: 100%;
}

View File

@@ -18,13 +18,26 @@ import hljs from 'highlight.js/lib/core';
import { ProtoMessage } from '../model/proto-message.model';
import { ProtoFieldComponent } from './proto-field/proto-field.component';
import { save } from '@tauri-apps/api/dialog';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MonacoEditorModule } from 'ngx-monaco-editor-v2';
import { FormsModule } from '@angular/forms';
declare const __TAURI__: any;
type PreviewType = 'raw' | 'edit' | 'diff';
@Component({
selector: 'app-editor',
standalone: true,
imports: [CommonModule, ProtoFieldComponent, MatButtonModule, MatIconModule],
imports: [
CommonModule,
ProtoFieldComponent,
MatButtonModule,
MatButtonToggleModule,
MatIconModule,
MonacoEditorModule,
FormsModule,
],
templateUrl: './editor.component.html',
styleUrl: './editor.component.scss',
})
@@ -34,11 +47,16 @@ export class EditorComponent {
indentSize = input<number>(2);
showRaw = input<boolean>(true);
protected editorOptions = {
theme: 'vs-dark',
language: 'json',
automaticLayout: true,
};
protected values = signal<any>(undefined);
protected downloadName = computed(
() => this.selectedFile() ?? `${this.selectedMessage().name}.json`
);
private serialisedValues = computed(() =>
protected serialisedValues = computed(() =>
JSON.stringify(this.values(), undefined, this.indentSize())
);
protected saveHref = computed(() => {
@@ -48,6 +66,16 @@ export class EditorComponent {
return URL.createObjectURL(blob);
});
protected downloader = viewChild<ElementRef<HTMLAnchorElement>>('downloader');
protected previewType = signal<PreviewType>('raw');
protected currentFileContents = signal<string>('');
protected originalModel = computed(() => ({
code: this.currentFileContents(),
language: 'json',
}));
protected currentModel = computed(() => ({
code: this.serialisedValues(),
language: 'json',
}));
private code = viewChild<ElementRef<HTMLElement>>('code');
@@ -85,24 +113,23 @@ export class EditorComponent {
const selectedFile = this.selectedFile();
if (selectedFile) {
const fileContents = await readTextFile(selectedFile);
try {
const parsed = JSON.parse(fileContents);
this.values.set(parsed);
} catch (err) {
console.error(
`Failed to parse contents of file ${selectedFile}`,
err
);
this.snackBar.open(
`Failed to parse contents of file ${selectedFile}, please check the file is correctly formatted`,
'Dismiss',
{ duration: 5000 }
);
}
this.currentFileContents.set(fileContents);
this.updateValuesFromText(fileContents, selectedFile);
}
});
}
protected updateValuesFromText(text: string, selectedFile?: string) {
try {
const parsed = JSON.parse(text);
this.values.set(parsed);
} catch (err) {
if (selectedFile) {
console.error(`Failed to parse contents of file ${selectedFile}`, err);
}
}
}
protected updateValue(key: string, value: any) {
const existingValue = { ...this.values() };
existingValue[key] = value;

View File

@@ -30,6 +30,7 @@ html {
@include mat.list-theme(theme.$rose-theme);
@include mat.select-theme(theme.$rose-theme);
@include mat.snack-bar-theme(theme.$rose-theme);
@include mat.button-toggle-theme(theme.$rose-theme);
@include custom-colours(theme.$rose-theme);
}