Set Save button on top, enable it only when devfile changed (#7015)

* Set Save button on top, enable it only when devfile changed

* Use snackbar to display parse errors

* Do not alert devfile modified when user clicks Save

* Update UI static files
This commit is contained in:
Philippe Martin
2023-08-03 14:51:46 +02:00
committed by GitHub
parent 6c1c8b2a19
commit 84bfb227c8
7 changed files with 41 additions and 20 deletions

View File

@@ -11,6 +11,6 @@
<body class="mat-typography"> <body class="mat-typography">
<div id="loading">Loading, please wait...</div> <div id="loading">Loading, please wait...</div>
<app-root></app-root> <app-root></app-root>
<script src="runtime.1289ea0acffcdc5e.js" type="module"></script><script src="polyfills.8b3b37cedaf377c3.js" type="module"></script><script src="main.a562f09a5f00f55f.js" type="module"></script> <script src="runtime.1289ea0acffcdc5e.js" type="module"></script><script src="polyfills.8b3b37cedaf377c3.js" type="module"></script><script src="main.1f6f2714e5bbe400.js" type="module"></script>
</body></html> </body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ describe('devfile editor errors handling', () => {
it('fails when YAML is not valid', () => { it('fails when YAML is not valid', () => {
cy.init(); cy.init();
cy.setDevfile("wrong yaml content"); cy.setDevfile("wrong yaml content");
cy.getByDataCy("yaml-error").should('contain.text', 'error parsing devfile YAML'); cy.get(".cdk-overlay-container").should('contain.text', 'error parsing devfile YAML');
}); });
it('fails when adding a container with an already used name', () => { it('fails when adding a container with an already used name', () => {

View File

@@ -3,6 +3,7 @@
<span class="spacer"></span> <span class="spacer"></span>
<span class="topright">Work in progress</span> <span class="topright">Work in progress</span>
<a mat-icon-button href="https://github.com/feloy/devfile-builder" target="_blank"><mat-icon svgIcon="github"></mat-icon></a> <a mat-icon-button href="https://github.com/feloy/devfile-builder" target="_blank"><mat-icon svgIcon="github"></mat-icon></a>
<button style="top: -8px" data-cy="yaml-send" matTooltip="Save Devfile to disk" mat-flat-button color="warn" disabled="{{!(state.modified|async)}}" (click)="onSave(input.value)">Save</button>
</mat-toolbar> </mat-toolbar>
<main> <main>
@@ -13,13 +14,11 @@
<mat-tab data-cy="tab-yaml" label="{{tabNames[0]}}"> <mat-tab data-cy="tab-yaml" label="{{tabNames[0]}}">
<div class="tab-content"> <div class="tab-content">
<div *ngIf="errorMessage" data-cy="yaml-error" class="error-message">{{errorMessage}}</div>
<mat-form-field appearance="outline" class="full-width"> <mat-form-field appearance="outline" class="full-width">
<mat-label>Devfile YAML</mat-label> <mat-label>Devfile YAML</mat-label>
<textarea data-cy="yaml-input" matInput #input id="input" rows="20" [value]="devfileYaml"></textarea> <textarea data-cy="yaml-input" matInput #input id="input" rows="20" [value]="devfileYaml"></textarea>
</mat-form-field> </mat-form-field>
<button data-cy="yaml-send" matTooltip="Save Devfile to disk" mat-flat-button color="primary" (click)="onSave(input.value)">Save</button> <button data-cy="yaml-save" matTooltip="Apply changes to other tabs" mat-flat-button color="primary" (click)="onApply(input.value)">Apply</button>
<button data-cy="yaml-save" matTooltip="Apply changes to other tabs" mat-flat-button color="normal" (click)="onApply(input.value)">Apply</button>
<button data-cy="yaml-clear" matTooltip="Clear Devfile content" mat-flat-button color="normal" (click)="clear()">Clear</button> <button data-cy="yaml-clear" matTooltip="Clear Devfile content" mat-flat-button color="normal" (click)="clear()">Clear</button>
</div> </div>
</mat-tab> </mat-tab>

View File

@@ -33,7 +33,6 @@ export class AppComponent implements OnInit {
]; ];
protected mermaidContent: string = ""; protected mermaidContent: string = "";
protected devfileYaml: string = ""; protected devfileYaml: string = "";
protected errorMessage: string = "";
private snackBarRef: MatSnackBarRef<ConfirmComponent> | null = null; private snackBarRef: MatSnackBarRef<ConfirmComponent> | null = null;
constructor( constructor(
@@ -42,7 +41,7 @@ export class AppComponent implements OnInit {
private wasmGo: DevstateService, private wasmGo: DevstateService,
private odoApi: OdoapiService, private odoApi: OdoapiService,
private mermaid: MermaidService, private mermaid: MermaidService,
private state: StateService, protected state: StateService,
private sse: SseService, private sse: SseService,
private telemetry: TelemetryService, private telemetry: TelemetryService,
private snackbar: MatSnackBar private snackbar: MatSnackBar
@@ -63,7 +62,7 @@ export class AppComponent implements OnInit {
devfile.subscribe({ devfile.subscribe({
next: (devfile) => { next: (devfile) => {
if (devfile.content != undefined) { if (devfile.content != undefined) {
this.propagateChange(devfile.content, false); this.propagateChange(devfile.content, false, true);
} }
} }
}); });
@@ -88,6 +87,10 @@ export class AppComponent implements OnInit {
}); });
this.sse.subscribeTo(['DevfileUpdated']).subscribe(event => { this.sse.subscribeTo(['DevfileUpdated']).subscribe(event => {
const newDevfile: DevfileContent = JSON.parse(event.data);
if (!this.state.isUpdated(newDevfile.content)) {
return;
}
if (this.snackBarRef != null) { if (this.snackBarRef != null) {
this.snackBarRef.afterDismissed().subscribe(() => {}); this.snackBarRef.afterDismissed().subscribe(() => {});
this.snackBarRef.dismiss(); this.snackBarRef.dismiss();
@@ -98,9 +101,8 @@ export class AppComponent implements OnInit {
yesLabel: "Update" yesLabel: "Update"
}}); }});
this.snackBarRef.onAction().subscribe(() => { this.snackBarRef.onAction().subscribe(() => {
let newDevfile: DevfileContent = JSON.parse(event.data);
if (newDevfile.content != undefined) { if (newDevfile.content != undefined) {
this.propagateChange(newDevfile.content, false); this.propagateChange(newDevfile.content, false, true);
} }
this.snackBarRef = null; this.snackBarRef = null;
}); });
@@ -123,35 +125,34 @@ export class AppComponent implements OnInit {
}) })
} }
propagateChange(content: string, saveToApi: boolean){ propagateChange(content: string, saveToApi: boolean, fromApi: boolean){
const result = this.wasmGo.setDevfileContent(content); const result = this.wasmGo.setDevfileContent(content);
result.subscribe({ result.subscribe({
next: (value) => { next: (value) => {
this.errorMessage = ''; this.state.changeDevfileYaml(value, fromApi);
this.state.changeDevfileYaml(value);
if (saveToApi) { if (saveToApi) {
this.odoApi.saveDevfile(value.content).subscribe({ this.odoApi.saveDevfile(value.content).subscribe({
next: () => {}, next: () => {},
error: (error) => { error: (error) => {
this.errorMessage = error.error.message; this.snackbar.open(error.error.message, "ok");
} }
}); });
} }
}, },
error: (error) => { error: (error) => {
this.errorMessage = error.error.message; this.snackbar.open(error.error.message, "ok");
} }
}); });
} }
onSave(content: string) { onSave(content: string) {
this.telemetry.track("[ui] save devfile to disk"); this.telemetry.track("[ui] save devfile to disk");
this.propagateChange(content, true); this.propagateChange(content, true, true);
} }
onApply(content: string) { onApply(content: string) {
this.telemetry.track("[ui] change devfile from textarea"); this.telemetry.track("[ui] change devfile from textarea");
this.propagateChange(content, false); this.propagateChange(content, false, false);
} }
clear() { clear() {
@@ -159,7 +160,7 @@ export class AppComponent implements OnInit {
this.telemetry.track("[ui] clear devfile"); this.telemetry.track("[ui] clear devfile");
this.wasmGo.clearDevfileContent().subscribe({ this.wasmGo.clearDevfileContent().subscribe({
next: (value) => { next: (value) => {
this.propagateChange(value.content, false); this.propagateChange(value.content, false, false);
} }
}); });
} }

View File

@@ -8,11 +8,32 @@ import { DevfileContent } from '../api-gen';
}) })
export class StateService { export class StateService {
private savedDevfile: string = "";
private _state = new BehaviorSubject<DevfileContent | null>(null); private _state = new BehaviorSubject<DevfileContent | null>(null);
public state = this._state.asObservable(); public state = this._state.asObservable();
changeDevfileYaml(newValue: DevfileContent) { private _modified = new BehaviorSubject<boolean | null>(null);
public modified = this._modified.asObservable();
changeDevfileYaml(newValue: DevfileContent, fromDisk: boolean = false) {
this._state.next(newValue); this._state.next(newValue);
if (fromDisk) {
this.savedDevfile = newValue.content;
}
if (this.savedDevfile == "") {
this.savedDevfile = newValue.content;
}
if (this.savedDevfile == newValue.content) {
this._modified.next(false);
} else {
this._modified.next(true);
}
}
isUpdated(devfile: string): boolean {
return devfile != this.savedDevfile;
} }
getDragAndDropEnabled(): boolean { getDragAndDropEnabled(): boolean {