mirror of https://github.com/Squidex/squidex.git
32 changed files with 697 additions and 509 deletions
@ -1,44 +1,36 @@ |
|||
<div class="modal-dialog"> |
|||
<div class="modal-content"> |
|||
<form [formGroup]="editForm" (ngSubmit)="saveSchema()"> |
|||
<div class="modal-header"> |
|||
<h4 class="modal-title">Edit Schema</h4> |
|||
<form [formGroup]="editForm" (ngSubmit)="saveSchema()"> |
|||
<sqx-modal-dialog (close)="complete()"> |
|||
<ng-container title> |
|||
Edit Schema |
|||
</ng-container> |
|||
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="complete()"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
</div> |
|||
|
|||
<div class="modal-body"> |
|||
<div class="form-group"> |
|||
<label for="schemaName">Name</label> |
|||
<ng-container content> |
|||
<div class="form-group"> |
|||
<label for="schemaName">Name</label> |
|||
|
|||
<input type="text" class="form-control" id="schemaName" readonly [ngModel]="schema.name" [ngModelOptions]="{standalone: true}" /> |
|||
</div> |
|||
<input type="text" class="form-control" id="schemaName" readonly [ngModel]="schema.name" [ngModelOptions]="{standalone: true}" /> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="schemaLabel">Label</label> |
|||
|
|||
<sqx-control-errors for="label" [submitted]="editFormSubmitted"></sqx-control-errors> |
|||
<div class="form-group"> |
|||
<label for="schemaLabel">Label</label> |
|||
|
|||
<sqx-control-errors for="label" [submitted]="editFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="schemaLabel" formControlName="label" /> |
|||
</div> |
|||
<input type="text" class="form-control" id="schemaLabel" formControlName="label" /> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="schemaHints">Hints</label> |
|||
|
|||
<sqx-control-errors for="hints" [submitted]="editFormSubmitted"></sqx-control-errors> |
|||
<div class="form-group"> |
|||
<label for="schemaHints">Hints</label> |
|||
|
|||
<sqx-control-errors for="hints" [submitted]="editFormSubmitted"></sqx-control-errors> |
|||
|
|||
<textarea type="text" class="form-control" id="schemaHints" formControlName="hints" rows="4"></textarea> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="modal-footer"> |
|||
<div class="clearfix"> |
|||
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="editFormSubmitted">Cancel</button> |
|||
<button type="submit" class="float-right btn btn-primary">Save</button> |
|||
</div> |
|||
<textarea type="text" class="form-control" id="schemaHints" formControlName="hints" rows="4"></textarea> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
<ng-container footer> |
|||
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="editFormSubmitted">Cancel</button> |
|||
<button type="submit" class="float-right btn btn-primary">Save</button> |
|||
</ng-container> |
|||
</sqx-modal-dialog> |
|||
</form> |
|||
@ -1,38 +1,30 @@ |
|||
<div class="modal-dialog modal-lg"> |
|||
<div class="modal-content"> |
|||
<form [formGroup]="editForm" (ngSubmit)="saveSchema()"> |
|||
<div class="modal-header"> |
|||
<h4 class="modal-title">Scripts</h4> |
|||
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()"> |
|||
<sqx-modal-dialog (close)="complete()"> |
|||
<ng-container title> |
|||
Scripts |
|||
</ng-container> |
|||
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="complete()"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
</div> |
|||
|
|||
<div class="modal-tabs clearfix"> |
|||
<ul class="nav nav-tabs2"> |
|||
<li class="nav-item" *ngFor="let script of scripts"> |
|||
<a class="nav-link" [class.active]="selectedField === 'script' + script" (click)="selectField('script' + script)">{{script}}</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
<ng-container tabs> |
|||
<ul class="nav nav-tabs2"> |
|||
<li class="nav-item" *ngFor="let script of editForm.form.controls | sqxKeys"> |
|||
<a class="nav-link" [class.active]="selectedField === script" (click)="selectField(script)">{{script.substr(6)}}</a> |
|||
</li> |
|||
</ul> |
|||
</ng-container> |
|||
|
|||
<div class="modal-body"> |
|||
<div class="form-group"> |
|||
<div *ngFor="let script of scripts"> |
|||
<div *ngIf="selectedField === 'script' + script"> |
|||
<sqx-jscript-editor name="script" [formControlName]="'script' + script"></sqx-jscript-editor> |
|||
</div> |
|||
<ng-container content> |
|||
<div class="form-group"> |
|||
<div *ngFor="let script of editForm.form.controls | sqxKeys"> |
|||
<div *ngIf="selectedField === script"> |
|||
<sqx-jscript-editor name="script" [formControlName]="script"></sqx-jscript-editor> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
<div class="modal-footer"> |
|||
<div class="clearfix"> |
|||
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="editFormSubmitted">Cancel</button> |
|||
<button type="submit" class="float-right btn btn-primary">Save</button> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
<ng-container footer> |
|||
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="editFormSubmitted">Cancel</button> |
|||
<button type="submit" class="float-right btn btn-primary">Save</button> |
|||
</ng-container> |
|||
</sqx-modal-dialog> |
|||
</form> |
|||
|
|||
@ -1,51 +1,49 @@ |
|||
<div class="modal-dialog"> |
|||
<div class="modal-content"> |
|||
<form [formGroup]="createForm" (ngSubmit)="createSchema()"> |
|||
<div class="modal-header"> |
|||
<h4 class="modal-title" *ngIf="import">Clone Schema</h4> |
|||
<h4 class="modal-title" *ngIf="!import">Create Schema</h4> |
|||
<form [formGroup]="createForm.form" (ngSubmit)="createSchema()"> |
|||
<sqx-modal-dialog (close)="complete()" [large]="false"> |
|||
<ng-container title> |
|||
<ng-container *ngIf="import; else noImport"> |
|||
Clone Schema |
|||
</ng-container> |
|||
<ng-template #noImport> |
|||
Create Schema |
|||
</ng-template> |
|||
</ng-container> |
|||
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="complete()"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
<ng-container content> |
|||
<ng-container *ngIf="createForm.error | async; let error"> |
|||
<div class="form-alert form-alert-error" [innerHTML]="error"></div> |
|||
</ng-container> |
|||
|
|||
<div class="form-group"> |
|||
<label for="schemaName">Name</label> |
|||
|
|||
<sqx-control-errors for="name" submitOnly="true" [submitted]="createForm.submitted | async"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="schemaName" formControlName="name" autocomplete="off" sqxLowerCaseInput sqxFocusOnInit /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The schema name becomes part of the api url,<br /> e.g {{apiUrl.buildUrl("api/content/")}}{{appsState.appName}}/<b>{{createForm.schemaName | async}}</b>/. |
|||
</small> |
|||
<small class="form-text text-muted"> |
|||
It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later. |
|||
</small> |
|||
</div> |
|||
|
|||
<div class="modal-body"> |
|||
<div *ngIf="createFormError"> |
|||
<div class="form-alert form-alert-error" [innerHTML]="createFormError"></div> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="schemaName">Name</label> |
|||
|
|||
<sqx-control-errors for="name" submitOnly="true" [submitted]="createFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="schemaName" formControlName="name" autocomplete="off" sqxLowerCaseInput sqxFocusOnInit /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The schema name becomes part of the api url,<br /> e.g {{apiUrl.buildUrl("api/content/")}}{{appsState.appName}}/<b>{{schemaName | async}}</b>/. |
|||
</small> |
|||
<small class="form-text text-muted"> |
|||
It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later. |
|||
</small> |
|||
</div> |
|||
|
|||
<div class="form-group clearfix"> |
|||
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button> |
|||
<button type="submit" class="float-right btn btn-success">Create</button> |
|||
</div> |
|||
|
|||
<div> |
|||
<button class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="showImport"> |
|||
Import schema |
|||
</button> |
|||
<button class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="!showImport"> |
|||
Hide |
|||
</button> |
|||
|
|||
<sqx-json-editor *ngIf="showImport" formControlName="import"></sqx-json-editor> |
|||
</div> |
|||
<div class="form-group clearfix"> |
|||
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button> |
|||
<button type="submit" class="float-right btn btn-success">Create</button> |
|||
</div> |
|||
|
|||
<div> |
|||
<button class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="showImport"> |
|||
Import schema |
|||
</button> |
|||
<button class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="!showImport"> |
|||
Hide |
|||
</button> |
|||
|
|||
<sqx-json-editor *ngIf="showImport" formControlName="import"></sqx-json-editor> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
</sqx-modal-dialog> |
|||
</form> |
|||
@ -1,64 +1,54 @@ |
|||
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="appsState.appName"></sqx-title> |
|||
|
|||
<sqx-panel theme="dark" desiredWidth="30rem"> |
|||
<div class="panel-header"> |
|||
<div class="panel-title-row"> |
|||
<h3 class="panel-title"><i class="icon-schemas"></i> Schemas</h3> |
|||
</div> |
|||
|
|||
<a class="panel-close" sqxParentLink isLazyLoaded="true"> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
|
|||
<div class="panel-header-row"> |
|||
<sqx-shortcut keys="ctrl+shift+g" (trigger)="addSchemaDialog.show()"></sqx-shortcut> |
|||
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut> |
|||
|
|||
<button class="btn btn-success subheader-button" (click)="createSchema(null)" title="New Schema (CTRL + SHIFT + G)"> |
|||
<i class="icon-plus"></i> |
|||
</button> |
|||
|
|||
<div class="search-form"> |
|||
<input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="Search for schemas..." /> |
|||
|
|||
<i class="icon-search"></i> |
|||
</div> |
|||
<sqx-panel theme="dark" desiredWidth="30rem" showSecondHeader="true"> |
|||
<ng-container title> |
|||
Schemas |
|||
</ng-container> |
|||
|
|||
<ng-container secondHeader> |
|||
<sqx-shortcut keys="ctrl+shift+g" (trigger)="addSchemaDialog.show()"></sqx-shortcut> |
|||
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut> |
|||
|
|||
<button class="btn btn-success subheader-button" (click)="createSchema(null)" title="New Schema (CTRL + SHIFT + G)"> |
|||
<i class="icon-plus"></i> |
|||
</button> |
|||
|
|||
<div class="search-form"> |
|||
<input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="Search for schemas..." /> |
|||
|
|||
<i class="icon-search"></i> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="panel-main"> |
|||
<div class="panel-content"> |
|||
<ul class="nav nav-panel nav-dark nav-dark-bordered flex-column"> |
|||
<li class="nav-item" *ngFor="let schema of schemasFiltered | async; trackBy: schemasState.trackBySchema"> |
|||
<a class="nav-link" [routerLink]="[schema.name]" routerLinkActive="active"> |
|||
<div class="row"> |
|||
<div class="col col-4"> |
|||
<span class="schema-name">{{schema.displayName}}</span> |
|||
</div> |
|||
<div class="col col-4"> |
|||
<span class="schema-user"> |
|||
<i class="icon-user"></i> {{schema.lastModifiedBy | sqxUserNameRef}} |
|||
</span> |
|||
</div> |
|||
<div class="col col-4 schema-modified"> |
|||
<small class="item-modified">{{schema.lastModified | sqxFromNow}}</small> |
|||
|
|||
<span class="item-published" [class.unpublished]="!schema.isPublished"></span> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
<ng-container content> |
|||
<ul class="nav nav-panel nav-dark nav-dark-bordered flex-column"> |
|||
<li class="nav-item" *ngFor="let schema of schemasFiltered | async; trackBy: schemasState.trackBySchema"> |
|||
<a class="nav-link" [routerLink]="[schema.name]" routerLinkActive="active"> |
|||
<div class="row"> |
|||
<div class="col col-4"> |
|||
<span class="schema-name">{{schema.displayName}}</span> |
|||
</div> |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
<div class="col col-4"> |
|||
<span class="schema-user"> |
|||
<i class="icon-user"></i> {{schema.lastModifiedBy | sqxUserNameRef}} |
|||
</span> |
|||
</div> |
|||
<div class="col col-4 schema-modified"> |
|||
<small class="item-modified">{{schema.lastModified | sqxFromNow}}</small> |
|||
|
|||
<span class="item-published" [class.unpublished]="!schema.isPublished"></span> |
|||
</div> |
|||
</div> |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
</ng-container> |
|||
</sqx-panel> |
|||
|
|||
<div class="modal" *sqxModalView="addSchemaDialog;onRoot:true" @fade> |
|||
<div class="modal-backdrop"></div> |
|||
|
|||
<ng-container *sqxModalView="addSchemaDialog;onRoot:true"> |
|||
<sqx-schema-form [import]="import" |
|||
(completed)="addSchemaDialog.hide()"> |
|||
</sqx-schema-form> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
<router-outlet></router-outlet> |
|||
@ -0,0 +1,31 @@ |
|||
<div class="modal" @fade> |
|||
<div class="modal-backdrop"></div> |
|||
|
|||
<div class="modal-dialog {{large ? 'modal-lg' : ''}}"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header"> |
|||
<h4 class="modal-title"> |
|||
<ng-content select=[title]></ng-content> |
|||
</h4> |
|||
|
|||
<button type="button" class="close" (click)="close.emit()"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
</div> |
|||
|
|||
<div class="modal-tabs clearfix" #tabsElement [hidden]="!showTabs"> |
|||
<ng-content select=[tabs]></ng-content> |
|||
</div> |
|||
|
|||
<div class="modal-body"> |
|||
<ng-content select=[content]></ng-content> |
|||
</div> |
|||
|
|||
<div class="modal-footer" [hidden]="!showFooter"> |
|||
<div class="clearfix" #footerElement> |
|||
<ng-content select=[footer]></ng-content> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,6 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
|
|||
.modal { |
|||
display: block; |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; |
|||
|
|||
import { fadeAnimation } from './../animations'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-modal-dialog', |
|||
styleUrls: ['./modal-dialog.component.scss'], |
|||
templateUrl: './modal-dialog.component.html', |
|||
animations: [ |
|||
fadeAnimation |
|||
], |
|||
changeDetection: ChangeDetectionStrategy.Default |
|||
}) |
|||
export class ModalDialogComponent implements AfterViewInit { |
|||
@Input() |
|||
public showClose = true; |
|||
|
|||
@Input() |
|||
public large = true; |
|||
|
|||
@Output() |
|||
public close = new EventEmitter(); |
|||
|
|||
@ViewChild('tabsElement') |
|||
public tabsElement: ElementRef; |
|||
|
|||
@ViewChild('footerElement') |
|||
public footerElement: ElementRef; |
|||
|
|||
public showTabs = false; |
|||
public showFooter = false; |
|||
|
|||
constructor( |
|||
private readonly changeDetector: ChangeDetectorRef |
|||
) { |
|||
} |
|||
|
|||
public ngAfterViewInit() { |
|||
this.showTabs = this.tabsElement.nativeElement.children.length > 0; |
|||
this.showFooter = this.footerElement.nativeElement.children.length > 0; |
|||
|
|||
this.changeDetector.detectChanges(); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
<div class="grid-footer clearfix" *ngIf="pager && pager.numberOfItems > 0"> |
|||
<div class="float-right pagination"> |
|||
<span class="pagination-text">{{pager.itemFirst}}-{{pager.itemLast}} of {{pager.numberOfItems}}</span> |
|||
|
|||
<button class="btn btn-link btn-secondary pagination-button" [disabled]="!pager.canGoPrev" (click)="prev.emit()"> |
|||
<i class="icon-angle-left"></i> |
|||
</button> |
|||
<button class="btn btn-link btn-secondary pagination-button" [disabled]="!pager.canGoNext" (click)="next.emit()"> |
|||
<i class="icon-angle-right"></i> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,2 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
@ -0,0 +1,26 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core'; |
|||
|
|||
import { Pager } from './../internal'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-pager', |
|||
styleUrls: ['./pager.component.scss'], |
|||
templateUrl: './pager.component.html' |
|||
}) |
|||
export class PagerComponent { |
|||
@Input() |
|||
public pager: Pager; |
|||
|
|||
@Output() |
|||
public next = new EventEmitter(); |
|||
|
|||
@Output() |
|||
public prev = new EventEmitter(); |
|||
} |
|||
@ -1,2 +1,9 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
@import '_vars'; |
|||
|
|||
.panel-header { |
|||
&.large { |
|||
min-height: 8rem; |
|||
max-height: 8rem; |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { AbstractControl } from '@angular/forms'; |
|||
import { BehaviorSubject, Observable } from 'rxjs'; |
|||
|
|||
import { ErrorDto } from './utils/error'; |
|||
|
|||
export interface FormState { |
|||
submitted: boolean; |
|||
|
|||
error?: string; |
|||
} |
|||
|
|||
export class Form<T extends AbstractControl> { |
|||
private readonly state = new State<FormState>({ submitted: false }); |
|||
|
|||
public submitted = |
|||
this.state.changes.map(s => s.submitted); |
|||
|
|||
public error = |
|||
this.state.changes.map(s => s.error); |
|||
|
|||
constructor( |
|||
public readonly form: T |
|||
) { |
|||
} |
|||
|
|||
public load(value: any) { |
|||
this.state.next({ submitted: false, error: null }); |
|||
|
|||
this.form.reset(value); |
|||
} |
|||
|
|||
public submit(): any | null { |
|||
this.state.next({ submitted: true }); |
|||
|
|||
if (this.form.valid) { |
|||
this.form.disable(); |
|||
|
|||
return this.form.value; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public submitCompleted() { |
|||
this.state.next({ submitted: false, error: null }); |
|||
|
|||
this.form.enable(); |
|||
} |
|||
|
|||
public submitFailed(error?: string | ErrorDto) { |
|||
this.state.next({ submitted: false, error: this.getError(error) }); |
|||
|
|||
this.form.enable(); |
|||
} |
|||
|
|||
private getError(error?: string | ErrorDto) { |
|||
if (error instanceof ErrorDto) { |
|||
return error.displayMessage; |
|||
} else { |
|||
return error; |
|||
} |
|||
} |
|||
} |
|||
|
|||
export class State<T extends {}> { |
|||
private readonly state: BehaviorSubject<T>; |
|||
|
|||
public get changes(): Observable<T> { |
|||
return this.state; |
|||
} |
|||
|
|||
public get snapshot() { |
|||
return this.state.value; |
|||
} |
|||
|
|||
constructor(state: T) { |
|||
this.state = new BehaviorSubject(state); |
|||
} |
|||
|
|||
public next(update: ((v: T) => T) | object) { |
|||
if (update instanceof Function) { |
|||
this.state.next(update(this.state.value)); |
|||
} else { |
|||
this.state.next(Object.assign({}, this.snapshot, update)); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue