Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Datasource Config UI #157

Merged
merged 11 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions erdblick_app/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ import {FloatLabelModule} from "primeng/floatlabel";
import {TabViewModule} from "primeng/tabview";
import {OnEnterClickDirective} from "./keyboard.service";
import {DropdownModule} from "primeng/dropdown";
import {
ArrayTypeComponent,
DatasourcesComponent,
MultiSchemaTypeComponent,
ObjectTypeComponent
} from "./datasources.component";
import {EditorService} from "./editor.service";
import {FormlyFieldConfig, FormlyModule} from "@ngx-formly/core";
import {ReactiveFormsModule} from '@angular/forms';
import {FormlyPrimeNGModule} from "@ngx-formly/primeng";
import {DataSourcesService} from "./datasources.service";
import {ProgressSpinnerModule} from "primeng/progressspinner";

export function initializeServices(styleService: StyleService, mapService: MapService, coordService: CoordinatesService) {
return async () => {
Expand All @@ -71,6 +83,50 @@ export function initializeServices(styleService: StyleService, mapService: MapSe
}
}

export function minItemsValidationMessage(error: any, field: FormlyFieldConfig) {
return `should NOT have fewer than ${field.props?.['minItems']} items`;
}

export function maxItemsValidationMessage(error: any, field: FormlyFieldConfig) {
return `should NOT have more than ${field.props?.['maxItems']} items`;
}

export function minLengthValidationMessage(error: any, field: FormlyFieldConfig) {
return `should NOT be shorter than ${field.props?.minLength} characters`;
}

export function maxLengthValidationMessage(error: any, field: FormlyFieldConfig) {
return `should NOT be longer than ${field.props?.maxLength} characters`;
}

export function minValidationMessage(error: any, field: FormlyFieldConfig) {
return `should be >= ${field.props?.min}`;
}

export function maxValidationMessage(error: any, field: FormlyFieldConfig) {
return `should be <= ${field.props?.max}`;
}

export function multipleOfValidationMessage(error: any, field: FormlyFieldConfig) {
return `should be multiple of ${field.props?.step}`;
}

export function exclusiveMinimumValidationMessage(error: any, field: FormlyFieldConfig) {
return `should be > ${field.props?.step}`;
}

export function exclusiveMaximumValidationMessage(error: any, field: FormlyFieldConfig) {
return `should be < ${field.props?.step}`;
}

export function constValidationMessage(error: any, field: FormlyFieldConfig) {
return `should be equal to constant "${field.props?.['const']}"`;
}

export function typeValidationMessage({ schemaType }: any) {
return `should be "${schemaType[0]}".`;
}

@NgModule({
declarations: [
AppComponent,
Expand All @@ -85,7 +141,11 @@ export function initializeServices(styleService: StyleService, mapService: MapSe
CoordinatesPanelComponent,
FeatureSearchComponent,
AlertDialogComponent,
DatasourcesComponent,
OnEnterClickDirective,
ArrayTypeComponent,
ObjectTypeComponent,
MultiSchemaTypeComponent,
HighlightSearch,
TreeTableFilterPatchDirective,
],
Expand Down Expand Up @@ -126,10 +186,36 @@ export function initializeServices(styleService: StyleService, mapService: MapSe
TabViewModule,
InputTextareaModule,
ButtonGroupModule,
TabViewModule,
BreadcrumbModule,
TableModule,
DropdownModule
DropdownModule,
TableModule,
ReactiveFormsModule,
FormlyPrimeNGModule,
FormlyModule.forRoot({
validationMessages: [
{name: 'required', message: 'This field is required'},
{name: 'type', message: typeValidationMessage},
{name: 'minLength', message: minLengthValidationMessage},
{name: 'maxLength', message: maxLengthValidationMessage},
{name: 'min', message: minValidationMessage},
{name: 'max', message: maxValidationMessage},
{name: 'multipleOf', message: multipleOfValidationMessage},
{name: 'exclusiveMinimum', message: exclusiveMinimumValidationMessage},
{name: 'exclusiveMaximum', message: exclusiveMaximumValidationMessage},
{name: 'minItems', message: minItemsValidationMessage},
{name: 'maxItems', message: maxItemsValidationMessage},
{name: 'uniqueItems', message: 'should NOT have duplicate items'},
{name: 'const', message: constValidationMessage},
{name: 'enum', message: `must be equal to one of the allowed values`},
],
types: [
{name: 'array', component: ArrayTypeComponent},
{name: 'object', component: ObjectTypeComponent},
{name: 'multischema', component: MultiSchemaTypeComponent}
],
}),
ProgressSpinnerModule
],
providers: [
{
Expand All @@ -147,6 +233,8 @@ export function initializeServices(styleService: StyleService, mapService: MapSe
SidePanelService,
FeatureSearchService,
ClipboardService,
EditorService,
DataSourcesService,
provideHttpClient(withInterceptorsFromDi()),
]
})
Expand Down
4 changes: 2 additions & 2 deletions erdblick_app/app/coordinates.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {HttpClient} from "@angular/common/http";
export class CoordinatesService {
mouseMoveCoordinates: BehaviorSubject<Cartographic | null> = new BehaviorSubject<Cartographic | null>(null);
mouseClickCoordinates: BehaviorSubject<Cartographic | null> = new BehaviorSubject<Cartographic | null>(null);
auxiliaryCoordinatesFun: Function | null = null;
auxiliaryTileIdsFun: Function | null = null;
auxiliaryCoordinatesFun: ((x: number, y: number)=>any) | null = null;
auxiliaryTileIdsFun: ((x: number, y: number, level: number)=>any) | null = null;

constructor(private httpClient: HttpClient,
public parametersService: ParametersService) {
Expand Down
232 changes: 232 additions & 0 deletions erdblick_app/app/datasources.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import {Component, ViewChild} from "@angular/core";
import {InfoMessageService} from "./info.service";
import {ParametersService} from "./parameters.service";
import {Subscription} from "rxjs";
import {Dialog} from "primeng/dialog";
import {EditorService} from "./editor.service";
import {JSONSchema7} from "json-schema";
import {DataSourcesService} from "./datasources.service";
import {FormGroup} from '@angular/forms';
import {FormlyFormOptions, FormlyFieldConfig, FieldType, FieldArrayType} from '@ngx-formly/core';
import {FormlyJsonschema} from '@ngx-formly/core/json-schema';

@Component({
selector: 'formly-multi-schema-type',
template: `
<div class="card mb-3">
<div class="card-body">
<legend *ngIf="props.label">{{ props.label }}</legend>
<p *ngIf="props.description">{{ props.description }}</p>
<div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
<formly-validation-message [field]="field"></formly-validation-message>
</div>
<formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
</div>
</div>
`,
})
export class MultiSchemaTypeComponent extends FieldType {}

@Component({
selector: 'formly-object-type',
template: `
<div class="mb-3">
<legend *ngIf="props.label">{{ props.label }}</legend>
<p *ngIf="props.description">{{ props.description }}</p>
<div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
<formly-validation-message [field]="field"></formly-validation-message>
</div>
<formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
</div>
`,
})
export class ObjectTypeComponent extends FieldType {}

@Component({
selector: 'formly-array-type',
template: `
<div class="mb-3">
<p-fieldset class="ds-fieldset" [legend]="props.label">
<p *ngIf="props.description">{{ props.description }}</p>

<div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
<formly-validation-message [field]="field"></formly-validation-message>
</div>

<div *ngFor="let field of field.fieldGroup; let i = index" class="row align-items-start">
<div style="display: flex; flex-direction: row; gap: 0.5em;">
<div *ngIf="field.props?.['removable'] !== false" class="col-2 text-right">
<p-button class="btn btn-danger" type="button" (click)="remove(i)">-</p-button>
</div>
<formly-field class="p-col" [field]="field"></formly-field>
</div>
<p-divider></p-divider>
</div>
<div class="d-flex flex-row-reverse">
<p-button class="btn btn-primary" type="button" (click)="add()">+</p-button>
</div>
</p-fieldset>
</div>
`,
})
export class ArrayTypeComponent extends FieldArrayType {}

@Component({
selector: 'datasources',
template: `
<!-- <p-dialog class="ds-config-dialog" header="DataSource Configuration" [(visible)]="dsService.configDialogVisible"-->
<!-- [modal]="false" (onShow)="dsService.getConfig()">-->
<!-- <p *ngIf="dsService.errorMessage">{{ dsService.errorMessage }}</p>-->
<!-- <div *ngIf="!dsService.loading" style="margin: 0.5em 0; display: flex; flex-direction: column; gap: 1em;">-->
<!-- <form [formGroup]="form" *ngIf="form && fields && !dsService.errorMessage" (ngSubmit)="postConfig()"-->
<!-- #formElement="ngForm">-->
<!-- <formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"></formly-form>-->
<!-- </form>-->
<!-- <div style="margin: 0.5em 0; display: flex; flex-direction: row; justify-content: center; gap: 1em;">-->
<!-- <p-button (click)="showConfigEditor()" [disabled]="false"-->
<!-- label="Open in Editor" icon="pi pi-pencil"></p-button>-->
<!-- <p-button (click)="submitForm()" [disabled]="(form && !form.valid)"-->
<!-- label="Apply" icon="pi pi-check"></p-button>-->
<!-- <div style="display: flex; flex-direction: row; align-content: center; gap: 0.5em;">-->
<!-- <p-button (click)="closeDatasources()" label="Close" icon="pi pi-times"></p-button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div *ngIf="dsService.loading">-->
<!-- <p-progressSpinner ariaLabel="loading"/>-->
<!-- </div>-->
<!-- </p-dialog>-->
<p-dialog header="DataSource Configuration Editor" [(visible)]="dsService.configDialogVisible" [modal]="false"
#editorDialog class="editor-dialog" (onShow)="loadConfigEditor()">
<p *ngIf="dsService.errorMessage">{{ dsService.errorMessage }}</p>
<div [ngClass]="{'loading': dsService.loading || dsService.errorMessage }">
<editor [loadFun]="loadEditedConfig" [saveFun]="saveEditedConfig"></editor>
</div>
<div class="spinner" *ngIf="dsService.loading">
<p-progressSpinner ariaLabel="loading"/>
</div>
<div style="margin: 0.5em 0; display: flex; flex-direction: row; align-content: center; justify-content: space-between;">
<div style="display: flex; flex-direction: row; align-content: center; gap: 0.5em;">
<p-button (click)="applyEditedDatasourceConfig()" label="Apply" icon="pi pi-check"
[disabled]="!wasModified"></p-button>
<p-button (click)="closeEditorDialog($event)" [label]='this.wasModified ? "Discard" : "Cancel"'
icon="pi pi-times"></p-button>
<div style="display: flex; flex-direction: column; align-content: center; justify-content: center; color: silver; font-size: medium;">
<div>Press <span style="color: grey">Ctrl-S/Cmd-S</span> to save changes</div>
<div>Press <span style="color: grey">Esc</span> to quit without saving</div>
</div>
</div>
</div>
</p-dialog>
`,
styles: [`
.loading {
visibility: collapse;
}
`]
})
export class DatasourcesComponent {

datasourcesEditorDialogVisible: boolean = false;
datasourceWasModified: boolean = false;
wasModified: boolean = false;
dataSourcesConfig: string = "";
form: FormGroup | undefined;
model: any = {};
options!: FormlyFormOptions;
fields!: FormlyFieldConfig[];
schema: JSONSchema7 = {};

@ViewChild('formElement') formElement!: HTMLFormElement;
@ViewChild('editorDialog') editorDialog: Dialog | undefined;

private editedConfigSourceSubscription: Subscription = new Subscription();
private savedConfigSourceSubscription: Subscription = new Subscription();

constructor(private messageService: InfoMessageService,
public parameterService: ParametersService,
private formlyJsonSchema: FormlyJsonschema,
public editorService: EditorService,
public dsService: DataSourcesService) {
this.parameterService.parameters.subscribe(parameters => {
return;
});

this.dsService.dataSourcesConfigJson.subscribe((config: any) => {
if (config && config["schema"] && config["model"]) {
this.schema = config["schema"];
this.model = config["model"];
this.dataSourcesConfig = JSON.stringify(this.model, null, 2);
this.editorService.editableData = this.dataSourcesConfig;
this.form = new FormGroup({});
this.options = {};
this.fields = [this.formlyJsonSchema.toFieldConfig(this.schema)];
this.dsService.loading = false;
this.editorService.updateEditorState.next(true);
}
});
}

loadConfigEditor() {
// this.datasourcesEditorDialogVisible = true;
// this.editorService.updateEditorState.next(true);
this.dsService.getConfig();
this.editedConfigSourceSubscription = this.editorService.editedStateData.subscribe(editedStyleSource => {
this.wasModified = editedStyleSource.replace(/\n+$/, '') !== this.dataSourcesConfig.replace(/\n+$/, '');
});
this.savedConfigSourceSubscription = this.editorService.editedSaveTriggered.subscribe(_ => {
this.applyEditedDatasourceConfig();
});
}

applyEditedDatasourceConfig() {
const configData = this.editorService.editedStateData.getValue().replace(/\n+$/, '');
if (!configData) {
this.messageService.showError(`Cannot apply an empty configuration definition!`);
return;
}
this.dsService.postConfig(configData);
this.dataSourcesConfig = configData;
this.wasModified = false;
}

closeEditorDialog(event: any) {
if (this.editorDialog !== undefined) {
if (this.wasModified) {
event.stopPropagation();
} else {
this.editorDialog.close(event);
}
}
this.editedConfigSourceSubscription.unsubscribe();
this.savedConfigSourceSubscription.unsubscribe();
}

discardConfigEdits() {
this.editorService.updateEditorState.next(false);
}

loadEditedConfig() {
return `${this.editorService.editableData}\n\n\n\n\n`;
}

saveEditedConfig() {
this.editorService.editedSaveTriggered.next(true);
}

closeDatasources() {
this.dsService.configDialogVisible = false;
}

submitForm() {
// if (this.form && this.form.valid) {
// this.formElement.submit();
// } else {
// alert("Form is invalid");
// }
}

postConfig() {
// this.dsService.postConfig(this.model);
}
}
Loading
Loading