mirror of https://github.com/Squidex/squidex.git
48 changed files with 717 additions and 220 deletions
@ -0,0 +1,16 @@ |
|||
<sqx-title message="{app} | Schema" parameter1="app" value1="{{appName() | async}}"></sqx-title> |
|||
|
|||
<div class="panel panel-light"> |
|||
<div class="panel-header"> |
|||
<div> |
|||
<h3 class="panel-title">Schema</h3> |
|||
|
|||
<a class="panel-close" routerLink="../"> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="panel-content"> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,7 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.panel { |
|||
min-width: 700px; |
|||
max-width: 700px; |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
NotificationService, |
|||
SchemasService, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
const FALLBACK_NAME = 'my-schema'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-schema-page', |
|||
styleUrls: ['./schema-page.component.scss'], |
|||
templateUrl: './schema-page.component.html' |
|||
}) |
|||
export class SchemaPageComponent extends AppComponentBase { |
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, |
|||
private readonly schemasService: SchemasService |
|||
) { |
|||
super(apps, notifications, users); |
|||
} |
|||
} |
|||
|
|||
@ -1,46 +0,0 @@ |
|||
<sqx-title message="{app} | Schemas" parameter="app" value="{{appName() | async}}"></sqx-title> |
|||
|
|||
<div class="panel panel-dark"> |
|||
<div class="panel-header"> |
|||
<div> |
|||
<h3 class="panel-title">Schemas</h3> |
|||
|
|||
<a class="panel-close" dashboardLink> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
<div class="subheader"> |
|||
<div class="clearfix"> |
|||
<button class="btn btn-new" (click)="createSchema()"> |
|||
<i class="icon-plus"></i> New Schema |
|||
</button> |
|||
</div> |
|||
<div class="search-form"> |
|||
<input class="form-control form-control-dark" placeholder="Search for schemas..." /> |
|||
|
|||
<i class="icon-search"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="panel-content"> |
|||
asdasd |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="modal" *sqxModalView="modalDialog" [@fade]> |
|||
<div class="modal-dialog" role="document"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header"> |
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
|
|||
<h4 class="modal-title">Create Schema</h4> |
|||
</div> |
|||
|
|||
<div class="modal-body"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -1,76 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.panel { |
|||
min-width: 450px; |
|||
max-width: 450px; |
|||
} |
|||
|
|||
.panel-header { |
|||
min-height: 150px; |
|||
max-height: 150px; |
|||
} |
|||
|
|||
.subheader { |
|||
margin-top: 1rem; |
|||
margin-right: -40px; |
|||
} |
|||
|
|||
.search-form { |
|||
& { |
|||
position: relative; |
|||
margin-top: .5rem; |
|||
} |
|||
|
|||
.form-control { |
|||
padding-left: 50px; |
|||
} |
|||
|
|||
.icon-search { |
|||
@include absolute(8px, auto, auto, 12px); |
|||
color: $color-accent-dark; |
|||
font-size: 1.3rem; |
|||
font-weight: lighter; |
|||
} |
|||
} |
|||
|
|||
.btn-new { |
|||
& { |
|||
padding-left: 0; |
|||
background: transparent; |
|||
border: 0; |
|||
color: darken($color-accent-dark, 15%); |
|||
font-size: 1.05rem; |
|||
} |
|||
|
|||
&:hover { |
|||
color: darken($color-accent-dark, 5%); |
|||
outline: 0; |
|||
} |
|||
|
|||
&:focus { |
|||
color: $color-accent-dark; |
|||
outline: 0; |
|||
} |
|||
|
|||
.icon-plus { |
|||
& { |
|||
@include circle(24px); |
|||
background: transparent; |
|||
border: 2px solid darken($color-accent-dark, 15%); |
|||
font-size: .8rem; |
|||
font-weight: bold; |
|||
vertical-align: baseline; |
|||
line-height: 20px; |
|||
display: inline-block; |
|||
} |
|||
|
|||
&:hover { |
|||
border-color: darken($color-accent-dark, 5%); |
|||
} |
|||
|
|||
&:focus { |
|||
border-color: $color-accent-dark; |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component } from '@angular/core'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
fadeAnimation, |
|||
ModalView, |
|||
NotificationService, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-schemas-page', |
|||
styleUrls: ['./schemas-page.component.scss'], |
|||
templateUrl: './schemas-page.component.html', |
|||
animations: [ |
|||
fadeAnimation |
|||
] |
|||
}) |
|||
export class SchemasPageComponent extends AppComponentBase { |
|||
public modalDialog = new ModalView(); |
|||
|
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) { |
|||
super(apps, notifications, users); |
|||
} |
|||
|
|||
public createSchema() { |
|||
this.modalDialog.show(); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,41 @@ |
|||
<form [formGroup]="createForm" (ngSubmit)="createSchema()"> |
|||
<div *ngIf="creationError"> |
|||
<div class="form-error"> |
|||
{{creationError}} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="app-name">Name</label> |
|||
|
|||
<div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').touched" [@fade]> |
|||
<div class="errors"> |
|||
<span *ngIf="createForm.get('name').hasError('required')"> |
|||
Name is required. |
|||
</span> |
|||
<span *ngIf="createForm.get('name').hasError('maxlength')"> |
|||
Name can not have more than 40 characters. |
|||
</span> |
|||
<span *ngIf="createForm.get('name').hasError('pattern')"> |
|||
Name can contain lower case letters (a-z), numbers and dashes only (not at the end). |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<input type="text" class="form-control" id="schema-name" formControlName="name" /> |
|||
|
|||
<span class="form-hint"> |
|||
<p> |
|||
The schema name becomes part of the api url,<br /> e.g https://{{appName}}.squidex.io/<b>{{schemaName}}</b>/. |
|||
</p> |
|||
<p> |
|||
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. |
|||
</p> |
|||
</span> |
|||
</div> |
|||
|
|||
<div class="form-group clearfix"> |
|||
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button> |
|||
<button type="submit" class="float-xs-right btn btn-primary" >Create</button> |
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,2 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -0,0 +1,101 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
|
|||
import { |
|||
AuthService, |
|||
CreateSchemaDto, |
|||
DateTime, |
|||
fadeAnimation, |
|||
SchemaDto, |
|||
SchemasService |
|||
} from 'shared'; |
|||
|
|||
const FALLBACK_NAME = 'my-schema'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-schema-form', |
|||
styleUrls: ['./schema-form.component.scss'], |
|||
templateUrl: './schema-form.component.html', |
|||
animations: [ |
|||
fadeAnimation |
|||
] |
|||
}) |
|||
export class SchemaFormComponent implements OnInit { |
|||
@Input() |
|||
public showClose = false; |
|||
|
|||
@Input() |
|||
public appName: string; |
|||
|
|||
@Output() |
|||
public created = new EventEmitter<SchemaDto>(); |
|||
|
|||
@Output() |
|||
public cancelled = new EventEmitter(); |
|||
|
|||
public creationError = ''; |
|||
public createForm: FormGroup = |
|||
this.formBuilder.group({ |
|||
name: ['', |
|||
[ |
|||
Validators.required, |
|||
Validators.maxLength(40), |
|||
Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*') |
|||
]] |
|||
}); |
|||
|
|||
public schemaName = FALLBACK_NAME; |
|||
|
|||
constructor( |
|||
private readonly schemas: SchemasService, |
|||
private readonly formBuilder: FormBuilder, |
|||
private readonly authService: AuthService |
|||
) { |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.createForm.controls['name'].valueChanges.subscribe(value => { |
|||
this.schemaName = value || FALLBACK_NAME; |
|||
}); |
|||
} |
|||
|
|||
public createSchema() { |
|||
this.createForm.markAsTouched(); |
|||
|
|||
if (this.createForm.valid) { |
|||
this.createForm.disable(); |
|||
|
|||
const name = this.createForm.controls['name'].value; |
|||
const dto = new CreateSchemaDto(name); |
|||
const now = DateTime.now(); |
|||
|
|||
const me = `subject:${this.authService.user.id}`; |
|||
|
|||
this.schemas.postSchema(this.appName, dto) |
|||
.subscribe(dto => { |
|||
this.createForm.reset(); |
|||
this.created.emit(new SchemaDto(dto.id, name, now, now, me, me)); |
|||
}, error => { |
|||
this.reset(); |
|||
this.creationError = error.displayMessage; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private reset() { |
|||
this.createForm.enable(); |
|||
this.creationError = ''; |
|||
} |
|||
|
|||
public cancel() { |
|||
this.reset(); |
|||
this.cancelled.emit(); |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
<sqx-title message="{app} | Schemas" parameter1="app" value1="{{appName() | async}}"></sqx-title> |
|||
|
|||
<div class="panel panel-dark"> |
|||
<div class="panel-header"> |
|||
<div> |
|||
<h3 class="panel-title">Schemas</h3> |
|||
|
|||
<a class="panel-close" dashboardLink> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
<div class="subheader"> |
|||
<div class="clearfix"> |
|||
<button class="btn btn-new" (click)="modalDialog.show()"> |
|||
<i class="icon-plus"></i> New Schema |
|||
</button> |
|||
</div> |
|||
<div class="search-form"> |
|||
<input class="form-control form-control-dark" [(ngModel)]="schemasFilter" [ngModelOptions]="{standalone: true}" placeholder="Search for schemas..." /> |
|||
|
|||
<i class="icon-search"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="panel-content"> |
|||
<div class="schemas"> |
|||
<div class="schema" *ngFor="let schema of filteredSchemas" [routerLink]="[schema.name]" routerLinkActive="active"> |
|||
<div class="schema-inner"> |
|||
<div class="row"> |
|||
<div class="col-xs-4"> |
|||
<span class="schema-name">{{schema.name}}</span> |
|||
</div> |
|||
<div class="col-xs-4"> |
|||
<span class="schema-user"> |
|||
<i class="icon-person"></i> <span class="schema-user-text">{{userName(schema.lastModifiedBy, true) | async}}</span> |
|||
</span> |
|||
</div> |
|||
<div class="col-xs-4 schema-col-right"> |
|||
<span class="schema-modified">{{schema.lastModified.toLocal() | fromNow}}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="modal" *sqxModalView="modalDialog" [@fade]> |
|||
<div class="modal-dialog" role="document"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header"> |
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
|
|||
<h4 class="modal-title">Create Schema</h4> |
|||
</div> |
|||
|
|||
<div class="modal-body"> |
|||
<sqx-schema-form |
|||
[appName]="appName() | async" |
|||
(created)="onSchemaCreationCompleted($event)" |
|||
(cancelled)="modalDialog.hide()"></sqx-schema-form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<router-outlet></router-outlet> |
|||
@ -0,0 +1,155 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.panel { |
|||
min-width: 450px; |
|||
max-width: 450px; |
|||
} |
|||
|
|||
.panel-header { |
|||
min-height: 170px; |
|||
max-height: 170px; |
|||
} |
|||
|
|||
.subheader { |
|||
margin-top: 1rem; |
|||
margin-right: -40px; |
|||
} |
|||
|
|||
.search-form { |
|||
& { |
|||
position: relative; |
|||
margin-top: .5rem; |
|||
} |
|||
|
|||
.form-control { |
|||
padding-left: 50px; |
|||
} |
|||
|
|||
.icon-search { |
|||
@include absolute(8px, auto, auto, 12px); |
|||
color: $color-dark-foreground-selected; |
|||
font-size: 1.3rem; |
|||
font-weight: lighter; |
|||
} |
|||
} |
|||
|
|||
.btn-new { |
|||
& { |
|||
padding-left: 0; |
|||
background: transparent; |
|||
border: 0; |
|||
color: $color-dark-foreground-selected; |
|||
font-size: 1.05rem; |
|||
} |
|||
|
|||
&:hover { |
|||
color: lighten($color-dark-foreground-selected, 5%); |
|||
outline: 0; |
|||
} |
|||
|
|||
&:focus { |
|||
color: lighten($color-dark-foreground-selected, 10%); |
|||
outline: 0; |
|||
} |
|||
|
|||
.icon-plus { |
|||
& { |
|||
@include circle(24px); |
|||
display: inline-block; |
|||
background: transparent; |
|||
border: 2px solid $color-dark-foreground-selected; |
|||
margin-right: .5rem; |
|||
font-size: .8rem; |
|||
font-weight: bold; |
|||
vertical-align: baseline; |
|||
line-height: 20px; |
|||
} |
|||
|
|||
&:hover { |
|||
border-color: lighten($color-dark-foreground-selected, 5%); |
|||
} |
|||
|
|||
&:focus { |
|||
border-color: lighten($color-dark-foreground-selected, 10%); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.schemas { |
|||
margin-left: -$panel-padding; |
|||
margin-right: -$panel-padding; |
|||
} |
|||
|
|||
.schema { |
|||
& { |
|||
padding-left: $panel-padding; |
|||
padding-right: $panel-padding; |
|||
} |
|||
|
|||
&:hover, |
|||
&.active { |
|||
& { |
|||
background: $color-dark-background-selected; |
|||
} |
|||
|
|||
& + .schema > .schema-inner { |
|||
border-color: transparent; |
|||
} |
|||
|
|||
.schema-inner { |
|||
border-color: transparent; |
|||
} |
|||
} |
|||
|
|||
&-inner { |
|||
padding-top: 1rem; |
|||
padding-bottom: 1rem; |
|||
border-top: 1px solid darken($color-dark-foreground, 10%); |
|||
} |
|||
|
|||
&-col-left { |
|||
@include truncate; |
|||
} |
|||
|
|||
&-col-right { |
|||
text-align: right; |
|||
} |
|||
|
|||
&-modified { |
|||
font-size: .8rem; |
|||
} |
|||
|
|||
&-name { |
|||
@include truncate; |
|||
color: $color-dark-foreground-selected; |
|||
font-size: 1rem; |
|||
font-weight: normal; |
|||
} |
|||
|
|||
&-user { |
|||
& { |
|||
@include border-radius(1px); |
|||
display: inline-block; |
|||
background: $color-dark-background-accent; |
|||
padding: .1rem .3rem; |
|||
font-size: .8rem; |
|||
font-weight: normal; |
|||
margin-left: 10px; |
|||
max-width: 100%; |
|||
vertical-align: baseline; |
|||
} |
|||
|
|||
&-text { |
|||
@include truncate; |
|||
display: inline-block; |
|||
vertical-align: bottom; |
|||
} |
|||
} |
|||
|
|||
&:first-child { |
|||
.schema-inner { |
|||
border: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
fadeAnimation, |
|||
ImmutableArray, |
|||
ModalView, |
|||
NotificationService, |
|||
SchemaDto, |
|||
SchemasService, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
const FALLBACK_NAME = 'my-schema'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-schemas-page', |
|||
styleUrls: ['./schemas-page.component.scss'], |
|||
templateUrl: './schemas-page.component.html', |
|||
animations: [ |
|||
fadeAnimation |
|||
] |
|||
}) |
|||
export class SchemasPageComponent extends AppComponentBase { |
|||
public modalDialog = new ModalView(); |
|||
|
|||
public schemasFilter: string; |
|||
public schemas = ImmutableArray.empty<SchemaDto>(); |
|||
|
|||
public get filteredSchemas() { |
|||
let result = this.schemas; |
|||
|
|||
if (this.schemasFilter && this.schemasFilter.length > 0) { |
|||
result = result.filter(t => t.name.indexOf(this.schemasFilter) >= 0); |
|||
} |
|||
|
|||
result = |
|||
result.sort((a, b) => { |
|||
if (a.name < b.name) { |
|||
return -1; |
|||
} |
|||
if (a.name > b.name) { |
|||
return 1; |
|||
} |
|||
return 0; |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, |
|||
private readonly formBuilder: FormBuilder, |
|||
private readonly schemasService: SchemasService |
|||
) { |
|||
super(apps, notifications, users); |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.load(); |
|||
} |
|||
|
|||
public load() { |
|||
this.appName() |
|||
.switchMap(app => this.schemasService.getSchemas(app).retry(2)) |
|||
.subscribe(dtos => { |
|||
this.schemas = ImmutableArray.of(dtos); |
|||
}, error => { |
|||
this.notifyError(error); |
|||
}); |
|||
} |
|||
|
|||
public onSchemaCreationCompleted(dto: SchemaDto) { |
|||
this.schemas = this.schemas.push(dto); |
|||
|
|||
this.modalDialog.hide(); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,2 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -0,0 +1,2 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue