@@ -20,7 +20,14 @@
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": ["src/assets"],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/monaco-editor",
|
||||
"output": "/assets/monaco/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
"highlight.js/styles/atom-one-dark.min.css"
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
"@angular/router": "^18.1.0",
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"highlight.js": "^11.10.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"ngx-monaco-editor-v2": "^18.0.1",
|
||||
"protobufjs": "^7.3.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"tslib": "^2.6.3",
|
||||
|
||||
@@ -22,6 +22,7 @@ mat-sidenav-content {
|
||||
|
||||
app-editor {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-sidenav.mat-drawer-end,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user