@@ -20,7 +20,14 @@
|
|||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
"polyfills": [],
|
"polyfills": [],
|
||||||
"tsConfig": "tsconfig.app.json",
|
"tsConfig": "tsconfig.app.json",
|
||||||
"assets": ["src/assets"],
|
"assets": [
|
||||||
|
"src/assets",
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "node_modules/monaco-editor",
|
||||||
|
"output": "/assets/monaco/"
|
||||||
|
}
|
||||||
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
"highlight.js/styles/atom-one-dark.min.css"
|
"highlight.js/styles/atom-one-dark.min.css"
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
"@angular/router": "^18.1.0",
|
"@angular/router": "^18.1.0",
|
||||||
"@tauri-apps/api": "^1.6.0",
|
"@tauri-apps/api": "^1.6.0",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
|
"monaco-editor": "^0.50.0",
|
||||||
|
"ngx-monaco-editor-v2": "^18.0.1",
|
||||||
"protobufjs": "^7.3.2",
|
"protobufjs": "^7.3.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ mat-sidenav-content {
|
|||||||
|
|
||||||
app-editor {
|
app-editor {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-sidenav.mat-drawer-end,
|
.mat-sidenav.mat-drawer-end,
|
||||||
|
|||||||
@@ -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, computed, signal } from '@angular/core';
|
import { Component, computed, effect, 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';
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
MAT_ICON_DEFAULT_OPTIONS,
|
MAT_ICON_DEFAULT_OPTIONS,
|
||||||
MatIconRegistry,
|
MatIconRegistry,
|
||||||
} from '@angular/material/icon';
|
} from '@angular/material/icon';
|
||||||
|
import { provideMonacoEditor } from 'ngx-monaco-editor-v2';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
@@ -41,5 +42,6 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provide: MAT_ICON_DEFAULT_OPTIONS,
|
provide: MAT_ICON_DEFAULT_OPTIONS,
|
||||||
useValue: { fontSet: 'material-symbols-rounded' },
|
useValue: { fontSet: 'material-symbols-rounded' },
|
||||||
},
|
},
|
||||||
|
provideMonacoEditor(),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,13 +14,36 @@
|
|||||||
></app-proto-field>
|
></app-proto-field>
|
||||||
} }
|
} }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(showRaw()) {
|
@if(showRaw()) {
|
||||||
<h2>Preview</h2>
|
<div class="preview">
|
||||||
<div class="actions">
|
<h2>
|
||||||
<button mat-flat-button (click)="copyToClipboard()">Copy</button>
|
Preview
|
||||||
</div>
|
<mat-button-toggle-group [(value)]="previewType">
|
||||||
<pre><code #code></code></pre>
|
<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>
|
<a #downloader [href]="saveHref()" [download]="downloadName()"></a>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
padding: var(--mat-sidenav-container-shape);
|
padding: var(--mat-sidenav-container-shape);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre,
|
.editor-items,
|
||||||
.editor-items {
|
.preview {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
@@ -14,11 +15,16 @@ pre,
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proto-title {
|
.proto-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
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 { ProtoMessage } from '../model/proto-message.model';
|
||||||
import { ProtoFieldComponent } from './proto-field/proto-field.component';
|
import { ProtoFieldComponent } from './proto-field/proto-field.component';
|
||||||
import { save } from '@tauri-apps/api/dialog';
|
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;
|
declare const __TAURI__: any;
|
||||||
|
|
||||||
|
type PreviewType = 'raw' | 'edit' | 'diff';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-editor',
|
selector: 'app-editor',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ProtoFieldComponent, MatButtonModule, MatIconModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ProtoFieldComponent,
|
||||||
|
MatButtonModule,
|
||||||
|
MatButtonToggleModule,
|
||||||
|
MatIconModule,
|
||||||
|
MonacoEditorModule,
|
||||||
|
FormsModule,
|
||||||
|
],
|
||||||
templateUrl: './editor.component.html',
|
templateUrl: './editor.component.html',
|
||||||
styleUrl: './editor.component.scss',
|
styleUrl: './editor.component.scss',
|
||||||
})
|
})
|
||||||
@@ -34,11 +47,16 @@ export class EditorComponent {
|
|||||||
indentSize = input<number>(2);
|
indentSize = input<number>(2);
|
||||||
showRaw = input<boolean>(true);
|
showRaw = input<boolean>(true);
|
||||||
|
|
||||||
|
protected editorOptions = {
|
||||||
|
theme: 'vs-dark',
|
||||||
|
language: 'json',
|
||||||
|
automaticLayout: true,
|
||||||
|
};
|
||||||
protected values = signal<any>(undefined);
|
protected values = signal<any>(undefined);
|
||||||
protected downloadName = computed(
|
protected downloadName = computed(
|
||||||
() => this.selectedFile() ?? `${this.selectedMessage().name}.json`
|
() => this.selectedFile() ?? `${this.selectedMessage().name}.json`
|
||||||
);
|
);
|
||||||
private serialisedValues = computed(() =>
|
protected serialisedValues = computed(() =>
|
||||||
JSON.stringify(this.values(), undefined, this.indentSize())
|
JSON.stringify(this.values(), undefined, this.indentSize())
|
||||||
);
|
);
|
||||||
protected saveHref = computed(() => {
|
protected saveHref = computed(() => {
|
||||||
@@ -48,6 +66,16 @@ export class EditorComponent {
|
|||||||
return URL.createObjectURL(blob);
|
return URL.createObjectURL(blob);
|
||||||
});
|
});
|
||||||
protected downloader = viewChild<ElementRef<HTMLAnchorElement>>('downloader');
|
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');
|
private code = viewChild<ElementRef<HTMLElement>>('code');
|
||||||
|
|
||||||
@@ -85,24 +113,23 @@ export class EditorComponent {
|
|||||||
const selectedFile = this.selectedFile();
|
const selectedFile = this.selectedFile();
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
const fileContents = await readTextFile(selectedFile);
|
const fileContents = await readTextFile(selectedFile);
|
||||||
try {
|
this.currentFileContents.set(fileContents);
|
||||||
const parsed = JSON.parse(fileContents);
|
this.updateValuesFromText(fileContents, selectedFile);
|
||||||
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 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
protected updateValue(key: string, value: any) {
|
||||||
const existingValue = { ...this.values() };
|
const existingValue = { ...this.values() };
|
||||||
existingValue[key] = value;
|
existingValue[key] = value;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ html {
|
|||||||
@include mat.list-theme(theme.$rose-theme);
|
@include mat.list-theme(theme.$rose-theme);
|
||||||
@include mat.select-theme(theme.$rose-theme);
|
@include mat.select-theme(theme.$rose-theme);
|
||||||
@include mat.snack-bar-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);
|
@include custom-colours(theme.$rose-theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user