diff --git a/angular.json b/angular.json
index 9644e41..8fe5af1 100644
--- a/angular.json
+++ b/angular.json
@@ -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"
diff --git a/bun.lockb b/bun.lockb
index 9546164..2ff8378 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index c9995b0..c17eb63 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 2fa6f54..63431fe 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -22,6 +22,7 @@ mat-sidenav-content {
app-editor {
flex: 1;
+ width: 100%;
}
.mat-sidenav.mat-drawer-end,
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index e69a5de..828cbe6 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -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';
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index 7262144..08229d7 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -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(),
],
};
diff --git a/src/app/editor/editor.component.html b/src/app/editor/editor.component.html
index a8772d5..5e58df2 100644
--- a/src/app/editor/editor.component.html
+++ b/src/app/editor/editor.component.html
@@ -14,13 +14,36 @@
>
} }
-
@if(showRaw()) {
-
Preview
-
-
-
-
-}
+
+
+ Preview
+
+ Raw
+ Edit
+ @if(selectedFile()) {
+ Diff
+ }
+
+
+
+ @switch (previewType()) { @case ('raw') {
+
+ } @case ('edit') {
+
+ } @case ('diff') {
+
+ } }
+
+}
diff --git a/src/app/editor/editor.component.scss b/src/app/editor/editor.component.scss
index e708522..e386a09 100644
--- a/src/app/editor/editor.component.scss
+++ b/src/app/editor/editor.component.scss
@@ -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%;
+}
diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts
index a44cc77..aea79c5 100644
--- a/src/app/editor/editor.component.ts
+++ b/src/app/editor/editor.component.ts
@@ -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(2);
showRaw = input(true);
+ protected editorOptions = {
+ theme: 'vs-dark',
+ language: 'json',
+ automaticLayout: true,
+ };
protected values = signal(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>('downloader');
+ protected previewType = signal('raw');
+ protected currentFileContents = signal('');
+ protected originalModel = computed(() => ({
+ code: this.currentFileContents(),
+ language: 'json',
+ }));
+ protected currentModel = computed(() => ({
+ code: this.serialisedValues(),
+ language: 'json',
+ }));
private code = viewChild>('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;
diff --git a/src/styles.scss b/src/styles.scss
index b730084..9878d19 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -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);
}