Browse Source

Feature/schemas ui (#434)

* Better schema UI
pull/436/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
4dc5b137bb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      frontend/app/features/schemas/declarations.ts
  2. 64
      frontend/app/features/schemas/module.ts
  3. 37
      frontend/app/features/schemas/pages/schema/schema-edit-form.component.html
  4. 5
      frontend/app/features/schemas/pages/schema/schema-edit-form.component.scss
  5. 15
      frontend/app/features/schemas/pages/schema/schema-edit-form.component.ts
  6. 59
      frontend/app/features/schemas/pages/schema/schema-export-form.component.html
  7. 23
      frontend/app/features/schemas/pages/schema/schema-export-form.component.scss
  8. 25
      frontend/app/features/schemas/pages/schema/schema-export-form.component.ts
  9. 36
      frontend/app/features/schemas/pages/schema/schema-fields.component.html
  10. 22
      frontend/app/features/schemas/pages/schema/schema-fields.component.scss
  11. 60
      frontend/app/features/schemas/pages/schema/schema-fields.component.ts
  12. 98
      frontend/app/features/schemas/pages/schema/schema-page.component.html
  13. 49
      frontend/app/features/schemas/pages/schema/schema-page.component.scss
  14. 42
      frontend/app/features/schemas/pages/schema/schema-page.component.ts
  15. 34
      frontend/app/features/schemas/pages/schema/schema-preview-urls-form.component.html
  16. 6
      frontend/app/features/schemas/pages/schema/schema-preview-urls-form.component.scss
  17. 19
      frontend/app/features/schemas/pages/schema/schema-preview-urls-form.component.ts
  18. 43
      frontend/app/features/schemas/pages/schema/schema-scripts-form.component.html
  19. 23
      frontend/app/features/schemas/pages/schema/schema-scripts-form.component.scss
  20. 19
      frontend/app/features/schemas/pages/schema/schema-scripts-form.component.ts
  21. 21
      frontend/app/features/settings/pages/more/more-page.component.html
  22. 5
      frontend/app/features/settings/pages/more/more-page.component.scss
  23. 2
      frontend/app/framework/angular/panel.component.html
  24. 4
      frontend/app/shared/services/help.service.spec.ts
  25. 2
      frontend/app/shared/services/help.service.ts

1
frontend/app/features/schemas/declarations.ts

@ -34,6 +34,7 @@ export * from './pages/schema/field-wizard.component';
export * from './pages/schema/field.component'; export * from './pages/schema/field.component';
export * from './pages/schema/schema-edit-form.component'; export * from './pages/schema/schema-edit-form.component';
export * from './pages/schema/schema-export-form.component'; export * from './pages/schema/schema-export-form.component';
export * from './pages/schema/schema-fields.component';
export * from './pages/schema/schema-page.component'; export * from './pages/schema/schema-page.component';
export * from './pages/schema/schema-preview-urls-form.component'; export * from './pages/schema/schema-preview-urls-form.component';
export * from './pages/schema/schema-scripts-form.component'; export * from './pages/schema/schema-scripts-form.component';

64
frontend/app/features/schemas/module.ts

@ -39,6 +39,7 @@ import {
ReferencesValidationComponent, ReferencesValidationComponent,
SchemaEditFormComponent, SchemaEditFormComponent,
SchemaExportFormComponent, SchemaExportFormComponent,
SchemaFieldsComponent,
SchemaFormComponent, SchemaFormComponent,
SchemaPageComponent, SchemaPageComponent,
SchemaPreviewUrlsFormComponent, SchemaPreviewUrlsFormComponent,
@ -57,18 +58,64 @@ const routes: Routes = [
children: [ children: [
{ {
path: ':schemaName', path: ':schemaName',
component: SchemaPageComponent,
canActivate: [SchemaMustExistGuard], canActivate: [SchemaMustExistGuard],
component: SchemaPageComponent,
children: [ children: [
{ {
path: 'help', path: '',
component: HelpComponent, redirectTo: 'fields'
data: { },
helpPage: '05-integrated/schemas' {
} path: 'fields',
children: [
{
path: 'help',
component: HelpComponent,
data: {
helpPage: '05-integrated/schemas'
}
}
]
},
{
path: 'scripts',
children: [
{
path: 'help',
component: HelpComponent,
data: {
helpPage: '05-integrated/scripts'
}
}
]
},
{
path: 'json',
children: [
{
path: 'help',
component: HelpComponent,
data: {
helpPage: '05-integrated/schema-json'
}
}
]
},
{
path: 'more',
children: [
{
path: 'help',
component: HelpComponent,
data: {
helpPage: '05-integrated/preview'
}
}
]
} }
] ]
}] }
]
} }
]; ];
@ -90,8 +137,8 @@ const routes: Routes = [
DateTimeUIComponent, DateTimeUIComponent,
DateTimeValidationComponent, DateTimeValidationComponent,
FieldComponent, FieldComponent,
FieldFormComponent,
FieldFormCommonComponent, FieldFormCommonComponent,
FieldFormComponent,
FieldFormUIComponent, FieldFormUIComponent,
FieldFormValidationComponent, FieldFormValidationComponent,
FieldWizardComponent, FieldWizardComponent,
@ -105,6 +152,7 @@ const routes: Routes = [
ReferencesValidationComponent, ReferencesValidationComponent,
SchemaEditFormComponent, SchemaEditFormComponent,
SchemaExportFormComponent, SchemaExportFormComponent,
SchemaFieldsComponent,
SchemaFormComponent, SchemaFormComponent,
SchemaPageComponent, SchemaPageComponent,
SchemaPreviewUrlsFormComponent, SchemaPreviewUrlsFormComponent,

37
frontend/app/features/schemas/pages/schema/schema-edit-form.component.html

@ -1,46 +1,45 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()"> <form [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<sqx-modal-dialog (close)="emitComplete()"> <div class="card">
<ng-container title> <div class="card-header">Common</div>
Edit Schema
</ng-container>
<ng-container content> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="schemaName">Name</label> <label for="schemaName">Name</label>
<input type="text" class="form-control" id="schemaName" readonly [ngModel]="schema.name" [ngModelOptions]="standalone" /> <input type="text" class="form-control" id="schemaName" readonly [ngModel]="schema.name" [ngModelOptions]="standalone" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="schemaLabel">Label</label> <label for="schemaLabel">Label</label>
<sqx-control-errors for="label" [submitted]="editForm.submitted | async"></sqx-control-errors> <sqx-control-errors for="label" [submitted]="editForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="schemaLabel" formControlName="label" /> <input type="text" class="form-control" id="schemaLabel" formControlName="label" />
</div>
<sqx-form-hint>If the label is defined it will be shown in the UI instead of the schema name.</sqx-form-hint>
</div>
<div class="form-group"> <div class="form-group">
<label for="schemaHints">Hints</label> <label for="schemaHints">Hints</label>
<sqx-control-errors for="hints" [submitted]="editForm.submitted | async"></sqx-control-errors> <sqx-control-errors for="hints" [submitted]="editForm.submitted | async"></sqx-control-errors>
<textarea type="text" class="form-control" id="schemaHints" formControlName="hints" rows="4"></textarea> <textarea type="text" class="form-control" id="schemaHints" formControlName="hints" rows="4"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="schemaTags">Tags</label> <label for="schemaTags">Tags</label>
<sqx-control-errors for="tags" [submitted]="editForm.submitted | async"></sqx-control-errors> <sqx-control-errors for="tags" [submitted]="editForm.submitted | async"></sqx-control-errors>
<sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor> <sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor>
<sqx-form-hint>Tags to annotate your schema for automation processes.</sqx-form-hint> <sqx-form-hint>Tags to annotate your schema for automation processes.</sqx-form-hint>
</div> </div>
</ng-container> </div>
<div class="card-footer">
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">Save</button> <button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">Save</button>
</ng-container> </div>
</sqx-modal-dialog> </div>
</form> </form>

5
frontend/app/features/schemas/pages/schema/schema-edit-form.component.scss

@ -1,6 +1,11 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
.card-header,
.card-footer {
padding: 1.25rem;
}
textarea { textarea {
resize: none; resize: none;
} }

15
frontend/app/features/schemas/pages/schema/schema-edit-form.component.ts

@ -5,10 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { import {
DialogService,
EditSchemaForm, EditSchemaForm,
SchemaDetailsDto, SchemaDetailsDto,
SchemasState SchemasState
@ -22,9 +23,6 @@ import {
export class SchemaEditFormComponent implements OnInit { export class SchemaEditFormComponent implements OnInit {
public readonly standalone = { standalone: true }; public readonly standalone = { standalone: true };
@Output()
public complete = new EventEmitter();
@Input() @Input()
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -33,6 +31,7 @@ export class SchemaEditFormComponent implements OnInit {
public isEditable = false; public isEditable = false;
constructor( constructor(
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly schemasState: SchemasState private readonly schemasState: SchemasState
) { ) {
@ -45,10 +44,6 @@ export class SchemaEditFormComponent implements OnInit {
this.editForm.setEnabled(this.isEditable); this.editForm.setEnabled(this.isEditable);
} }
public emitComplete() {
this.complete.emit();
}
public saveSchema() { public saveSchema() {
if (!this.isEditable) { if (!this.isEditable) {
return; return;
@ -59,7 +54,9 @@ export class SchemaEditFormComponent implements OnInit {
if (value) { if (value) {
this.schemasState.update(this.schema, value) this.schemasState.update(this.schema, value)
.subscribe(() => { .subscribe(() => {
this.emitComplete(); this.dialogs.notifyInfo('Schema saved successfully.');
this.editForm.submitCompleted({ noReset: true });
}, error => { }, error => {
this.editForm.submitFailed(error); this.editForm.submitFailed(error);
}); });

59
frontend/app/features/schemas/pages/schema/schema-export-form.component.html

@ -1,36 +1,31 @@
<form [formGroup]="synchronizeForm.form" (submit)="synchronizeSchema()"> <form class="inner-form" [formGroup]="synchronizeForm.form" (submit)="synchronize()">
<sqx-modal-dialog (close)="emitComplete()" large="true"> <div class="inner-header" *ngIf="isEditable">
<ng-container title> <div class="row align-items-center">
Export Schema <div class="col"></div>
</ng-container> <div class="col-auto">
<div class="form-inline">
<ng-container content> <div class="form-check pr-4">
<sqx-json-editor [noBorder]="true" formControlName="json"></sqx-json-editor> <input class="form-check-input" type="checkbox" id="fieldsDelete" formControlName="fieldsDelete" />
</ng-container> <label class="form-check-label" for="fieldsDelete">
Delete fields
<ng-container footer *ngIf="schema.canSynchronize"> </label>
<div class="row align-items-center"> </div>
<div class="col">
<div class="form-inline"> <div class="form-check">
<div class="form-check pr-4"> <input class="form-check-input" type="checkbox" id="fieldsRecreate" formControlName="fieldsRecreate" />
<input class="form-check-input" type="checkbox" id="fieldsDelete" formControlName="fieldsDelete" /> <label class="form-check-label" for="fieldsRecreate">
<label class="form-check-label" for="fieldsDelete"> Recreate fields
Delete fields </label>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="fieldsRecreate" formControlName="fieldsRecreate" />
<label class="form-check-label" for="fieldsRecreate">
Recreate fields
</label>
</div>
</div> </div>
</div> </div>
<div class="col-auto">
<button type="submit" class="btn btn-success" [disabled]="synchronizeForm.submitted | async">Synchronize</button>
</div>
</div> </div>
</ng-container> <div class="col-auto">
</sqx-modal-dialog> <button type="submit" class="btn btn-primary" [disabled]="synchronizeForm.submitted | async">Synchronize</button>
</div>
</div>
</div>
<div class="inner-main">
<sqx-json-editor [noBorder]="true" formControlName="json"></sqx-json-editor>
</div>
</form> </form>

23
frontend/app/features/schemas/pages/schema/schema-export-form.component.scss

@ -1,15 +1,24 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
:host ::ng-deep { :host,
.modal-body { .inner-form,
padding: 0; .inner-main {
} @include flex-box;
@include flex-grow(1);
@include flex-direction(column);
}
.modal-content { .inner-header,
height: 100%; .inner-main {
} position: relative;
}
.inner-header {
padding: 1rem $panel-padding;
}
:host ::ng-deep {
.editor { .editor {
@include absolute(0, 0, 0, 0); @include absolute(0, 0, 0, 0);
} }

25
frontend/app/features/schemas/pages/schema/schema-export-form.component.ts

@ -5,10 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { import {
DialogService,
SchemaDetailsDto, SchemaDetailsDto,
SchemasState, SchemasState,
SynchronizeSchemaForm SynchronizeSchemaForm
@ -20,25 +21,31 @@ import {
templateUrl: './schema-export-form.component.html' templateUrl: './schema-export-form.component.html'
}) })
export class SchemaExportFormComponent implements OnChanges { export class SchemaExportFormComponent implements OnChanges {
@Output()
public complete = new EventEmitter();
@Input() @Input()
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
public synchronizeForm = new SynchronizeSchemaForm(this.formBuilder); public synchronizeForm = new SynchronizeSchemaForm(this.formBuilder);
public isEditable = false;
constructor( constructor(
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly schemasState: SchemasState private readonly schemasState: SchemasState
) { ) {
} }
public ngOnChanges() { public ngOnChanges() {
this.isEditable = this.schema.canUpdateScripts;
this.synchronizeForm.form.get('json')!.setValue(this.schema.export()); this.synchronizeForm.form.get('json')!.setValue(this.schema.export());
} }
public synchronizeSchema() { public synchronize() {
if (!this.isEditable) {
return;
}
const value = this.synchronizeForm.submit(); const value = this.synchronizeForm.submit();
if (value) { if (value) {
@ -50,14 +57,12 @@ export class SchemaExportFormComponent implements OnChanges {
this.schemasState.synchronize(this.schema, request) this.schemasState.synchronize(this.schema, request)
.subscribe(() => { .subscribe(() => {
this.synchronizeForm.submitCompleted(); this.dialogs.notifyInfo('Schema synchronized successfully.');
this.synchronizeForm.submitCompleted({ noReset: true });
}, error => { }, error => {
this.synchronizeForm.submitFailed(error); this.synchronizeForm.submitFailed(error);
}); });
} }
} }
public emitComplete() {
this.complete.emit();
}
} }

36
frontend/app/features/schemas/pages/schema/schema-fields.component.html

@ -0,0 +1,36 @@
<div class="fields">
<div class="table-items-row table-items-row-empty" *ngIf="schema && schema.fields.length === 0">
No field created yet.
<button type="button" class="btn btn-success btn-sm ml-2" (click)="addFieldDialog.show()" *ngIf="schema.canAddField">
<i class="icon icon-plus"></i> Add Field
</button>
</div>
<ng-container *ngIf="patternsState.patterns | async; let patterns">
<div
cdkDropList
[cdkDropListDisabled]="!schema.canOrderFields"
[cdkDropListData]="schema.fields"
(cdkDropListDropped)="sortFields($event)">
<div *ngFor="let field of schema.fields; trackBy: trackByFieldFn"
class="table-drag"
cdkDrag
cdkDragLockAxis="y">
<sqx-field [field]="field" [schema]="schema" [patterns]="patterns">
<i cdkDragHandle class="icon-drag2 drag-handle"></i>
</sqx-field>
</div>
</div>
<button type="button" class="btn btn-success field-button" (click)="addFieldDialog.show()" *ngIf="schema.canAddField">
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">Add Field</div>
</button>
</ng-container>
</div>
<ng-container *sqxModal="addFieldDialog">
<sqx-field-wizard [schema]="schema"
(complete)="addFieldDialog.hide()">
</sqx-field-wizard>
</ng-container>

22
frontend/app/features/schemas/pages/schema/schema-fields.component.scss

@ -0,0 +1,22 @@
@import '_vars';
@import '_mixins';
.fields {
padding: $panel-padding;
}
.field-button {
& {
@include circle(5.25rem);
@include box-shadow(0, 8px, 16px, .3);
@include absolute(auto, 6rem, 1rem, auto);
}
&-icon {
font-weight: bold;
}
&-text {
font-size: .9rem;
}
}

60
frontend/app/features/schemas/pages/schema/schema-fields.component.ts

@ -0,0 +1,60 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable:no-shadowed-variable
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, Input, OnInit } from '@angular/core';
import {
DialogModel,
fadeAnimation,
FieldDto,
fieldTypes,
PatternsState,
SchemaDetailsDto,
SchemasState,
sorted
} from '@app/shared';
@Component({
selector: 'sqx-schema-fields',
styleUrls: ['./schema-fields.component.scss'],
templateUrl: './schema-fields.component.html',
animations: [
fadeAnimation
]
})
export class SchemaFieldsComponent implements OnInit {
public fieldTypes = fieldTypes;
@Input()
public schema: SchemaDetailsDto;
public addFieldDialog = new DialogModel();
public trackByFieldFn: (index: number, field: FieldDto) => any;
constructor(
public readonly schemasState: SchemasState,
public readonly patternsState: PatternsState
) {
this.trackByFieldFn = this.trackByField.bind(this);
}
public ngOnInit() {
this.patternsState.load();
}
public sortFields(event: CdkDragDrop<ReadonlyArray<FieldDto>>) {
this.schemasState.orderFields(this.schema, sorted(event)).subscribe();
}
public trackByField(index: number, field: FieldDto) {
return field.fieldId + this.schema.id;
}
}

98
frontend/app/features/schemas/pages/schema/schema-page.component.html

@ -1,15 +1,15 @@
<sqx-title [message]="schemasState.schemaName"></sqx-title> <sqx-title [message]="schemasState.schemaName"></sqx-title>
<sqx-panel desiredWidth="60rem" [showSidebar]="true"> <sqx-panel desiredWidth="50rem" contentClass="grid" [showSidebar]="true">
<ng-container title> <ng-container header>
<i class="schema-edit icon-pencil" (click)="editSchemaDialog.show()"></i> <span (dblclick)="editSchemaDialog.show()">{{schema.displayName}}</span> <ul class="nav nav-tabs2">
<li class="nav-item" *ngFor="let tab of selectableTabs">
<a class="nav-link" [routerLink]="['./', tab.toLowerCase()]" routerLinkActive="active">{{tab}}</a>
</li>
</ul>
</ng-container> </ng-container>
<ng-container menu> <ng-container menu>
<button type="button" class="btn btn-text mr-1" (click)="exportDialog.show()">
JSON View
</button>
<div class="btn-group mr-1" #buttonPublish> <div class="btn-group mr-1" #buttonPublish>
<button type="button" class="btn btn-publishing btn-toggle" [class.btn-success]="schema.isPublished" [disabled]="!schema.canPublish" (click)="publish()"> <button type="button" class="btn btn-publishing btn-toggle" [class.btn-success]="schema.isPublished" [disabled]="!schema.canPublish" (click)="publish()">
Published Published
@ -25,14 +25,7 @@
</button> </button>
<ng-container *sqxModal="editOptionsDropdown;closeAlways:true"> <ng-container *sqxModal="editOptionsDropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade> <div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<a class="dropdown-item" (click)="configureScriptsDialog.show()">
Scripts
</a>
<a class="dropdown-item" (click)="configurePreviewUrlsDialog.show()">
Preview Urls
</a>
<ng-container *ngIf="schemasState.canCreate"> <ng-container *ngIf="schemasState.canCreate">
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
@ -66,73 +59,32 @@
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<div class="table-items-row table-items-row-empty" *ngIf="schema && schema.fields.length === 0"> <ng-container [ngSwitch]="selectedTab">
No field created yet. <ng-container *ngSwitchCase="'fields'">
<sqx-schema-fields [schema]="schema"></sqx-schema-fields>
<button type="button" class="btn btn-success btn-sm ml-2" (click)="addFieldDialog.show()" *ngIf="schema.canAddField"> </ng-container>
<i class="icon icon-plus"></i> Add Field <ng-container *ngSwitchCase="'scripts'">
</button> <sqx-schema-scripts-form [schema]="schema"></sqx-schema-scripts-form>
</div> </ng-container>
<ng-container *ngSwitchCase="'json'">
<ng-container *ngIf="patternsState.patterns | async; let patterns"> <sqx-schema-export-form [schema]="schema"></sqx-schema-export-form>
<div class="schemas" </ng-container>
cdkDropList <ng-container *ngSwitchCase="'more'">
[cdkDropListDisabled]="!schema.canOrderFields" <div class="cards">
[cdkDropListData]="schema.fields" <sqx-schema-preview-urls-form [schema]="schema"></sqx-schema-preview-urls-form>
(cdkDropListDropped)="sortFields($event)"> <sqx-schema-edit-form [schema]="schema"></sqx-schema-edit-form>
<div *ngFor="let field of schema.fields; trackBy: trackByFieldFn"
class="table-drag"
cdkDrag
cdkDragLockAxis="y">
<sqx-field [field]="field" [schema]="schema" [patterns]="patterns">
<i cdkDragHandle class="icon-drag2 drag-handle"></i>
</sqx-field>
</div> </div>
</div> </ng-container>
<button type="button" class="btn btn-success field-button" (click)="addFieldDialog.show()" *ngIf="schema.canAddField">
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">Add Field</div>
</button>
</ng-container> </ng-container>
</ng-container> </ng-container>
<ng-container sidebar> <ng-container sidebar>
<div class="panel-nav"> <div class="panel-nav">
<a class="panel-link" routerLink="help" routerLinkActive="active" title="Help" titlePosition="left"> <a class="panel-link" [routerLink]="[selectedTab, 'help']" routerLinkActive="active" title="Help" titlePosition="left">
<i class="icon-help"></i> <i class="icon-help"></i>
</a> </a>
</div> </div>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<ng-container *sqxModal="editSchemaDialog">
<sqx-schema-edit-form [schema]="schema"
(complete)="editSchemaDialog.hide()">
</sqx-schema-edit-form>
</ng-container>
<ng-container *sqxModal="addFieldDialog">
<sqx-field-wizard [schema]="schema"
(complete)="addFieldDialog.hide()">
</sqx-field-wizard>
</ng-container>
<ng-container *sqxModal="configureScriptsDialog">
<sqx-schema-scripts-form [schema]="schema"
(complete)="configureScriptsDialog.hide()">
</sqx-schema-scripts-form>
</ng-container>
<ng-container *sqxModal="configurePreviewUrlsDialog">
<sqx-schema-preview-urls-form [schema]="schema"
(complete)="configurePreviewUrlsDialog.hide()">
</sqx-schema-preview-urls-form>
</ng-container>
<ng-container *sqxModal="exportDialog">
<sqx-schema-export-form [schema]="schema"
(complete)="exportDialog.hide()">
</sqx-schema-export-form>
</ng-container>

49
frontend/app/features/schemas/pages/schema/schema-page.component.scss

@ -8,50 +8,15 @@
} }
} }
.btn-group-sm { .cards {
margin-top: .25rem; padding: $panel-padding;
overflow-y: auto;
} }
.btn-sm { .nav-tabs2 {
margin-top: .25rem; @include absolute(auto, auto, 0, auto);
}
.btn-success {
margin: 0;
}
.schemas {
padding-bottom: 7rem;
}
.schema {
&-edit {
color: $color-border-dark;
font-size: .9rem;
font-weight: normal;
padding: .6rem .25rem;
border: 0;
background: transparent;
vertical-align: baseline;
}
}
.field-icon {
color: $color-border-dark;
}
.field-button {
& {
@include circle(5.25rem);
@include box-shadow(0, 8px, 16px, .3);
@include absolute(auto, 6rem, 1rem, auto);
}
&-icon {
font-weight: bold;
}
&-text { a {
font-size: .9rem; padding-bottom: 1.5rem;
} }
} }

42
frontend/app/features/schemas/pages/schema/schema-page.component.ts

@ -7,22 +7,17 @@
// tslint:disable:no-shadowed-variable // tslint:disable:no-shadowed-variable
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { import {
DialogModel,
fadeAnimation, fadeAnimation,
FieldDto,
fieldTypes,
MessageBus, MessageBus,
ModalModel, ModalModel,
PatternsState,
ResourceOwner, ResourceOwner,
SchemaDetailsDto, SchemaDetailsDto,
SchemasState, SchemasState,
sorted Types
} from '@app/shared'; } from '@app/shared';
import { import {
@ -38,33 +33,30 @@ import {
] ]
}) })
export class SchemaPageComponent extends ResourceOwner implements OnInit { export class SchemaPageComponent extends ResourceOwner implements OnInit {
public fieldTypes = fieldTypes;
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
public addFieldDialog = new DialogModel();
public configurePreviewUrlsDialog = new DialogModel();
public configureScriptsDialog = new DialogModel();
public editOptionsDropdown = new ModalModel(); public editOptionsDropdown = new ModalModel();
public editSchemaDialog = new DialogModel();
public exportDialog = new DialogModel();
public trackByFieldFn: (index: number, field: FieldDto) => any; public selectedTab = 'fields';
public selectableTabs: ReadonlyArray<string> = ['Fields', 'Scripts', 'Json', 'More'];
constructor( constructor(
public readonly schemasState: SchemasState, public readonly schemasState: SchemasState,
public readonly patternsState: PatternsState,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router, private readonly router: Router,
private readonly messageBus: MessageBus private readonly messageBus: MessageBus
) { ) {
super(); super();
this.trackByFieldFn = this.trackByField.bind(this);
} }
public ngOnInit() { public ngOnInit() {
this.patternsState.load(); this.own(
this.router.events
.subscribe(event => {
if (Types.is(event, NavigationEnd)) {
this.selectedTab = this.route.firstChild!.snapshot.routeConfig!.path!;
}
}));
this.own( this.own(
this.schemasState.selectedSchema this.schemasState.selectedSchema
@ -81,14 +73,6 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
this.schemasState.unpublish(this.schema).subscribe(); this.schemasState.unpublish(this.schema).subscribe();
} }
public sortFields(event: CdkDragDrop<ReadonlyArray<FieldDto>>) {
this.schemasState.orderFields(this.schema, sorted(event)).subscribe();
}
public trackByField(index: number, field: FieldDto) {
return field.fieldId + this.schema.id;
}
public deleteSchema() { public deleteSchema() {
this.schemasState.delete(this.schema) this.schemasState.delete(this.schema)
.subscribe(() => { .subscribe(() => {
@ -100,6 +84,10 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
this.messageBus.emit(new SchemaCloning(this.schema.export())); this.messageBus.emit(new SchemaCloning(this.schema.export()));
} }
public selectTab(tab: string) {
this.selectedTab = tab;
}
private back() { private back() {
this.router.navigate(['../'], { relativeTo: this.route }); this.router.navigate(['../'], { relativeTo: this.route });
} }

34
frontend/app/features/schemas/pages/schema/schema-preview-urls-form.component.html

@ -1,17 +1,11 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()"> <form [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<sqx-modal-dialog (close)="emitComplete()" large="true"> <div class="card mb-2">
<ng-container title> <div class="card-header">Preview URLs</div>
Preview Urls
</ng-container>
<ng-container content> <div class="card-body">
<sqx-form-hint> <sqx-form-alert>
Adding a preview url generates a link in the content editor, referring to your custom preview or production environment where the content item is used. Checkout the integrated help page to learn more about preview URL's.
</sqx-form-hint> </sqx-form-alert>
<sqx-form-hint>
Read the <a href="https://https://docs.squidex.io/guides/09-preview" sqxExternalLink>documentation</a> before setting up custom content preview urls.
</sqx-form-hint>
<div class="content"> <div class="content">
<div class="mt-4" *ngIf="!isEditable && editForm.form.controls.length === 0"> <div class="mt-4" *ngIf="!isEditable && editForm.form.controls.length === 0">
@ -28,7 +22,7 @@
<div class="col pr-1"> <div class="col pr-1">
<sqx-control-errors [for]="form.get('url')" fieldName="Url" [submitted]="editForm.submitted | async"></sqx-control-errors> <sqx-control-errors [for]="form.get('url')" fieldName="Url" [submitted]="editForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" maxlength="1000" [formControl]="form.get('url')" placeholder="Url with placeholders like ${id} or ${data.slug}" /> <input type="text" class="form-control" maxlength="1000" [formControl]="form.get('url')" placeholder="Url with variables" />
</div> </div>
<div class="col-auto col-options"> <div class="col-auto col-options">
@ -52,7 +46,7 @@
<div class="col pr-1"> <div class="col pr-1">
<sqx-control-errors for="url" [submitted]="addForm.submitted | async"></sqx-control-errors> <sqx-control-errors for="url" [submitted]="addForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" maxlength="1000" formControlName="url" placeholder="Url with placeholders like ${id} or ${data.slug}" /> <input type="text" class="form-control" maxlength="1000" formControlName="url" placeholder="Url with variables" />
</div> </div>
<div class="col-auto col-options"> <div class="col-auto col-options">
@ -66,11 +60,9 @@
</div> </div>
</div> </div>
</div> </div>
</ng-container> </div>
<div class="card-footer" *ngIf="isEditable">
<ng-container footer> <button type="submit" class="float-right btn btn-primary" [class.invisible]="!isEditable">Save</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()" [disabled]="editForm.submitted | async">Cancel</button> </div>
<button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">Save</button> </div>
</ng-container>
</sqx-modal-dialog>
</form> </form>

6
frontend/app/features/schemas/pages/schema/schema-preview-urls-form.component.scss

@ -2,12 +2,12 @@
@import '_mixins'; @import '_mixins';
.content { .content {
min-height: 200px;
margin-top: 1rem; margin-top: 1rem;
} }
.btn-delete { .card-header,
margin-left: 42px; .card-footer {
padding: 1.25rem;
} }
.col-options { .col-options {

19
frontend/app/features/schemas/pages/schema/schema-preview-urls-form.component.ts

@ -5,12 +5,13 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { import {
AddPreviewUrlForm, AddPreviewUrlForm,
ConfigurePreviewUrlsForm, ConfigurePreviewUrlsForm,
DialogService,
SchemaDetailsDto, SchemaDetailsDto,
SchemasState SchemasState
} from '@app/shared'; } from '@app/shared';
@ -20,10 +21,7 @@ import {
styleUrls: ['./schema-preview-urls-form.component.scss'], styleUrls: ['./schema-preview-urls-form.component.scss'],
templateUrl: './schema-preview-urls-form.component.html' templateUrl: './schema-preview-urls-form.component.html'
}) })
export class SchemaPreviewUrlsFormComponent implements OnInit { export class SchemaPreviewUrlsFormComponent implements OnChanges {
@Output()
public complete = new EventEmitter();
@Input() @Input()
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -34,22 +32,19 @@ export class SchemaPreviewUrlsFormComponent implements OnInit {
public isEditable = false; public isEditable = false;
constructor( constructor(
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly schemasState: SchemasState private readonly schemasState: SchemasState
) { ) {
} }
public ngOnInit() { public ngOnChanges() {
this.isEditable = this.schema.canUpdateUrls; this.isEditable = this.schema.canUpdateUrls;
this.editForm.load(this.schema.previewUrls); this.editForm.load(this.schema.previewUrls);
this.editForm.setEnabled(this.isEditable); this.editForm.setEnabled(this.isEditable);
} }
public emitComplete() {
this.complete.emit();
}
public cancelAdd() { public cancelAdd() {
this.addForm.submitCompleted(); this.addForm.submitCompleted();
} }
@ -78,7 +73,9 @@ export class SchemaPreviewUrlsFormComponent implements OnInit {
if (value) { if (value) {
this.schemasState.configurePreviewUrls(this.schema, value) this.schemasState.configurePreviewUrls(this.schema, value)
.subscribe(() => { .subscribe(() => {
this.emitComplete(); this.dialogs.notifyInfo('Preview URLs successfully.');
this.editForm.submitCompleted({ noReset: true });
}, error => { }, error => {
this.editForm.submitFailed(error); this.editForm.submitFailed(error);
}); });

43
frontend/app/features/schemas/pages/schema/schema-scripts-form.component.html

@ -1,30 +1,19 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()"> <form class="inner-form" [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<sqx-modal-dialog (close)="emitComplete()" large="true"> <div class="inner-header">
<ng-container title> <ul class="nav nav-tabs2">
Scripts <li class="nav-item" *ngFor="let script of editForm.form.controls | sqxKeys">
</ng-container> <a class="nav-link" [class.active]="selectedField === script" (click)="selectField(script)">{{script | titlecase}}</a>
</li>
<ng-container tabs> </ul>
<ul class="nav nav-tabs2">
<li class="nav-item" *ngFor="let script of editForm.form.controls | sqxKeys"> <button type="submit" class="float-right btn btn-primary" [class.invisible]="!isEditable">Save</button>
<a class="nav-link" [class.active]="selectedField === script" (click)="selectField(script)">{{script | titlecase}}</a> </div>
</li>
</ul>
</ng-container>
<ng-container content>
<div class="form-group">
<ng-container *ngFor="let script of editForm.form.controls | sqxKeys">
<ng-container *ngIf="selectedField === script">
<sqx-code-editor [noBorder]="true" [formControlName]="script"></sqx-code-editor>
</ng-container>
</ng-container>
</div>
</ng-container>
<ng-container footer> <div class="inner-main">
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()" [disabled]="editForm.submitted | async">Cancel</button> <ng-container *ngFor="let script of editForm.form.controls | sqxKeys">
<button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">Save</button> <ng-container *ngIf="selectedField === script">
<sqx-code-editor [noBorder]="true" [formControlName]="script"></sqx-code-editor>
</ng-container>
</ng-container> </ng-container>
</sqx-modal-dialog> </div>
</form> </form>

23
frontend/app/features/schemas/pages/schema/schema-scripts-form.component.scss

@ -1,19 +1,24 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
.nav-link { :host,
cursor: default; .inner-form,
.inner-main {
@include flex-box;
@include flex-grow(1);
@include flex-direction(column);
} }
:host ::ng-deep { .inner-header,
.modal-body { .inner-main {
padding: 0; position: relative;
} }
.modal-content { .inner-header {
height: 100%; padding: 1rem $panel-padding;
} }
:host ::ng-deep {
.editor { .editor {
@include absolute(0, 0, 0, 0); @include absolute(0, 0, 0, 0);
} }

19
frontend/app/features/schemas/pages/schema/schema-scripts-form.component.ts

@ -5,10 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { import {
DialogService,
EditScriptsForm, EditScriptsForm,
SchemaDetailsDto, SchemaDetailsDto,
SchemasState SchemasState
@ -19,10 +20,7 @@ import {
styleUrls: ['./schema-scripts-form.component.scss'], styleUrls: ['./schema-scripts-form.component.scss'],
templateUrl: './schema-scripts-form.component.html' templateUrl: './schema-scripts-form.component.html'
}) })
export class SchemaScriptsFormComponent implements OnInit { export class SchemaScriptsFormComponent implements OnChanges {
@Output()
public complete = new EventEmitter();
@Input() @Input()
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -33,22 +31,19 @@ export class SchemaScriptsFormComponent implements OnInit {
public isEditable = false; public isEditable = false;
constructor( constructor(
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly schemasState: SchemasState private readonly schemasState: SchemasState
) { ) {
} }
public ngOnInit() { public ngOnChanges() {
this.isEditable = this.schema.canUpdateScripts; this.isEditable = this.schema.canUpdateScripts;
this.editForm.load(this.schema.scripts); this.editForm.load(this.schema.scripts);
this.editForm.setEnabled(this.isEditable); this.editForm.setEnabled(this.isEditable);
} }
public emitComplete() {
this.complete.emit();
}
public selectField(field: string) { public selectField(field: string) {
this.selectedField = field; this.selectedField = field;
} }
@ -63,7 +58,9 @@ export class SchemaScriptsFormComponent implements OnInit {
if (value) { if (value) {
this.schemasState.configureScripts(this.schema, value) this.schemasState.configureScripts(this.schema, value)
.subscribe(() => { .subscribe(() => {
this.emitComplete(); this.dialogs.notifyInfo('Scripts saved successfully.');
this.editForm.submitCompleted({ noReset: true });
}, error => { }, error => {
this.editForm.submitFailed(error); this.editForm.submitFailed(error);
}); });

21
frontend/app/features/settings/pages/more/more-page.component.html

@ -4,11 +4,11 @@
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<div class="card mb-4"> <form [formGroup]="updateForm.form" (ngSubmit)="save()">
<h3 class="card-header">General</h3> <div class="card mb-2">
<h3 class="card-header">General</h3>
<div class="card-body"> <div class="card-body">
<form [formGroup]="updateForm.form" (ngSubmit)="save()">
<sqx-form-error [error]="updateForm.error | async"></sqx-form-error> <sqx-form-error [error]="updateForm.error | async"></sqx-form-error>
<div class="form-group"> <div class="form-group">
@ -32,14 +32,15 @@
<input type="text" class="form-control" id="description" maxlength="100" formControlName="description" /> <input type="text" class="form-control" id="description" maxlength="100" formControlName="description" />
</div> </div>
</div>
<div class="form-group"> <div class="card-footer">
<button type="submit" class="btn btn-primary">Save</button> <button type="submit" class="float-right btn btn-primary">Save</button>
</div> </div>
</form>
</div> </div>
</div> </form>
<div class="card mb-4">
<div class="card mb-2">
<h3 class="card-header">Image</h3> <h3 class="card-header">Image</h3>
<div class="card-body"> <div class="card-body">

5
frontend/app/features/settings/pages/more/more-page.component.scss

@ -17,6 +17,11 @@
} }
} }
.card-header,
.card-footer {
padding: 1.25rem;
}
@mixin overlay { @mixin overlay {
& { & {
@include transition(opacity .4s ease); @include transition(opacity .4s ease);

2
frontend/app/framework/angular/panel.component.html

@ -29,6 +29,8 @@
</ng-template> </ng-template>
</ng-container> </ng-container>
<ng-content select="[header]"></ng-content>
<div class="panel-header-row" *ngIf="showSecondHeader"> <div class="panel-header-row" *ngIf="showSecondHeader">
<ng-content select="[secondHeader]"></ng-content> <ng-content select="[secondHeader]"></ng-content>
</div> </div>

4
frontend/app/shared/services/help.service.spec.ts

@ -35,7 +35,7 @@ describe('ClientsService', () => {
helpSections = result; helpSections = result;
}); });
const req = httpMock.expectOne('https://raw.githubusercontent.com/Squidex/squidex-docs/master/01-chapter/02-article.md'); const req = httpMock.expectOne('https://raw.githubusercontent.com/squidex/squidex-docs2/master/01-chapter/02-article.md');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
@ -54,7 +54,7 @@ describe('ClientsService', () => {
helpSections = result; helpSections = result;
}); });
const req = httpMock.expectOne('https://raw.githubusercontent.com/Squidex/squidex-docs/master/01-chapter/02-article.md'); const req = httpMock.expectOne('https://raw.githubusercontent.com/squidex/squidex-docs2/master/01-chapter/02-article.md');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();

2
frontend/app/shared/services/help.service.ts

@ -18,7 +18,7 @@ export class HelpService {
} }
public getHelp(helpPage: string): Observable<string> { public getHelp(helpPage: string): Observable<string> {
const url = `https://raw.githubusercontent.com/Squidex/squidex-docs/master/${helpPage}.md`; const url = `https://raw.githubusercontent.com/squidex/squidex-docs2/master/${helpPage}.md`;
return this.http.get(url, { responseType: 'text' }).pipe( return this.http.get(url, { responseType: 'text' }).pipe(
catchError(() => of(''))); catchError(() => of('')));

Loading…
Cancel
Save