mirror of https://github.com/Squidex/squidex.git
29 changed files with 728 additions and 140 deletions
@ -1,16 +0,0 @@ |
|||
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title> |
|||
|
|||
<div class="panel panel-light"> |
|||
<div class="panel-header"> |
|||
<h3 class="panel-title">Content</h3> |
|||
|
|||
<a class="panel-close" dashboardLink> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
|
|||
<div class="panel-main"> |
|||
<div class="panel-content"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -1,27 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component } from '@angular/core'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
NotificationService, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-content-page', |
|||
styleUrls: ['./content-page.component.scss'], |
|||
templateUrl: './content-page.component.html' |
|||
}) |
|||
export class ContentPageComponent extends AppComponentBase { |
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) { |
|||
super(apps, notifications, users); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,95 @@ |
|||
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title> |
|||
|
|||
<form [formGroup]="contentForm" (ngSubmit)="saveSchema()"> |
|||
<div class="panel panel-light" > |
|||
<div class="panel-header"> |
|||
<div class="float-xs-right"> |
|||
<button type="submit" class="btn btn-primary"> |
|||
Save |
|||
</button> |
|||
</div> |
|||
|
|||
<h3 class="panel-title" *ngIf="isNewMode">New {{schema|displayName}}</h3> |
|||
|
|||
<a class="panel-close" routerLink="../"> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
|
|||
<div class="panel-main"> |
|||
<div class="panel-content"> |
|||
<div *ngFor="let field of schema.fields"> |
|||
<div class="table-items-row"> |
|||
<label>{{field|displayName:'properties.label':'name'}}</label> |
|||
|
|||
<div> |
|||
<div [ngSwitch]="field.properties.fieldType"> |
|||
<div *ngSwitchCase="'number'"> |
|||
<div [ngSwitch]="field.properties.editor"> |
|||
<div *ngSwitchCase="'Input'"> |
|||
<input class="form-control" type="number" [formControlName]="field.name"> |
|||
</div> |
|||
<div *ngSwitchCase="'Dropdown'"> |
|||
<select class="form-control" [formControlName]="field.name"> |
|||
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option> |
|||
</select> |
|||
</div> |
|||
<div *ngSwitchCase="'Radio'"> |
|||
<label *ngFor="let value of field.properties.allowedValues"> |
|||
<input type="radio" value="{{value}}" [formControlName]="field.name"> {{value}} |
|||
</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div *ngSwitchCase="'string'"> |
|||
<div [ngSwitch]="field.properties.editor"> |
|||
<div *ngSwitchCase="'Input'"> |
|||
<input class="form-control" type="number" [formControlName]="field.name"> |
|||
</div> |
|||
<div *ngSwitchCase="'Dropdown'"> |
|||
<select class="form-control" [formControlName]="field.name"> |
|||
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option> |
|||
</select> |
|||
</div> |
|||
<div *ngSwitchCase="'TextArea'"> |
|||
<textarea class="form-control" [formControlName]="field.name"></textarea> |
|||
</div> |
|||
<div *ngSwitchCase="'Radio'"> |
|||
<label *ngFor="let value of field.properties.allowedValues"> |
|||
<input type="radio" value="{{value}}" [formControlName]="field.name"> {{value}} |
|||
</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div *ngSwitchCase="'boolean'"> |
|||
<div [ngSwitch]="field.properties.editor"> |
|||
<div *ngSwitchCase="'Checkbox'"> |
|||
<div class="form-check"> |
|||
<input type="checkbox" [formControlName]="field.name"> |
|||
</div> |
|||
</div> |
|||
<div *ngSwitchCase="'Toggle'"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-hint" *ngIf="field.properties.hints && field.properties.hints.length > 0"> |
|||
{{field.properties.hints}} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="panel-sidebar" *ngIf="!isNewMode"> |
|||
<div class="nav nav-pills nav-stacked nav-light"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" routerLink="history" routerLinkActive="active"> |
|||
<i class="icon-time"></i> |
|||
</a> |
|||
</li> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,62 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; |
|||
import { ActivatedRoute } from '@angular/router'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
NotificationService, |
|||
SchemaDetailsDto, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-content-page', |
|||
styleUrls: ['./content-page.component.scss'], |
|||
templateUrl: './content-page.component.html' |
|||
}) |
|||
export class ContentPageComponent extends AppComponentBase { |
|||
public schema: SchemaDetailsDto; |
|||
|
|||
public contentForm: FormGroup; |
|||
|
|||
public isNewMode = false; |
|||
|
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, |
|||
private readonly route: ActivatedRoute |
|||
) { |
|||
super(apps, notifications, users); |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.route.params.map(p => p['contentId']).subscribe(contentId => { |
|||
this.isNewMode = !contentId; |
|||
}); |
|||
|
|||
this.route.data.map(p => p['schema']).subscribe((schema: SchemaDetailsDto) => { |
|||
this.schema = schema; |
|||
|
|||
this.setupForm(schema); |
|||
}); |
|||
} |
|||
|
|||
private setupForm(schema: SchemaDetailsDto) { |
|||
const controls: { [key: string]: AbstractControl } = {}; |
|||
|
|||
for (const field of schema.fields) { |
|||
const formControl = new FormControl(); |
|||
|
|||
controls[field.name] = formControl; |
|||
} |
|||
|
|||
this.contentForm = new FormGroup(controls); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,33 @@ |
|||
<sqx-title message="{app} | Contents" parameter1="app" value1="{{appName() | async}}"></sqx-title> |
|||
|
|||
<div class="panel panel-light"> |
|||
<div class="panel-header"> |
|||
<div class="float-xs-right"> |
|||
<a class="btn btn-success" [routerLink]="['new']"> |
|||
<i class="icon-plus"></i> New |
|||
</a> |
|||
</div> |
|||
|
|||
<h3 class="panel-title">{{schema|displayName}} Contents</h3> |
|||
|
|||
<a class="panel-close" routerLink="../"> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
|
|||
<div class="panel-main"> |
|||
<div class="panel-content"> |
|||
</div> |
|||
<div class="panel-sidebar"> |
|||
<div class="nav nav-pills nav-stacked nav-light"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" routerLink="history" routerLinkActive="active"> |
|||
<i class="icon-time"></i> |
|||
</a> |
|||
</li> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<router-outlet></router-outlet> |
|||
@ -0,0 +1,7 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.panel { |
|||
min-width: 600px; |
|||
max-width: 600px; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { ActivatedRoute } from '@angular/router'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
NotificationService, |
|||
SchemaDetailsDto, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-contents-page', |
|||
styleUrls: ['./contents-page.component.scss'], |
|||
templateUrl: './contents-page.component.html' |
|||
}) |
|||
export class ContentsPageComponent extends AppComponentBase implements OnInit { |
|||
public schema: SchemaDetailsDto; |
|||
|
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, |
|||
private readonly route: ActivatedRoute |
|||
) { |
|||
super(apps, notifications, users); |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.route.data.map(p => p['schema']).subscribe(schema => { |
|||
this.schema = schema; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,35 @@ |
|||
<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="search-form"> |
|||
<input class="form-control form-control-dark" [formControl]="schemasFilter" placeholder="Search for schemas..." /> |
|||
|
|||
<i class="icon-search"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="panel-main"> |
|||
<div class="panel-content"> |
|||
<div class="schemas"> |
|||
<div class="schema" *ngFor="let schema of schemasFiltered | async" [routerLink]="[schema.name]" routerLinkActive="active"> |
|||
<div class="schema-inner"> |
|||
<span class="schema-name">{{schema|displayName}}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<router-outlet></router-outlet> |
|||
@ -0,0 +1,83 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.panel { |
|||
min-width: 280px; |
|||
max-width: 280px; |
|||
} |
|||
|
|||
.panel-header { |
|||
min-height: 120px; |
|||
max-height: 120px; |
|||
} |
|||
|
|||
.subheader { |
|||
@include flex-box; |
|||
@include flex-flow(row); |
|||
margin-top: 2rem; |
|||
margin-right: -40px; |
|||
} |
|||
|
|||
.search-form { |
|||
& { |
|||
@include flex-grow(1); |
|||
position: relative; |
|||
} |
|||
|
|||
.form-control { |
|||
padding-left: 50px; |
|||
} |
|||
|
|||
.icon-search { |
|||
@include absolute(10px, auto, auto, 12px); |
|||
color: $color-dark-foreground-selected; |
|||
font-size: 1.3rem; |
|||
font-weight: lighter; |
|||
} |
|||
} |
|||
|
|||
.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%); |
|||
} |
|||
|
|||
&-name { |
|||
@include truncate; |
|||
color: $color-dark-foreground-selected; |
|||
font-size: 1rem; |
|||
font-weight: normal; |
|||
} |
|||
|
|||
&:first-child { |
|||
.schema-inner { |
|||
border: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { FormControl } from '@angular/forms'; |
|||
import { BehaviorSubject, Observable } from 'rxjs'; |
|||
|
|||
import { |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
ImmutableArray, |
|||
NotificationService, |
|||
SchemaDto, |
|||
SchemasService, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-schemas-page', |
|||
styleUrls: ['./schemas-page.component.scss'], |
|||
templateUrl: './schemas-page.component.html' |
|||
}) |
|||
export class SchemasPageComponent extends AppComponentBase implements OnInit { |
|||
public schemas = new BehaviorSubject(ImmutableArray.empty<SchemaDto>()); |
|||
public schemasFilter = new FormControl(); |
|||
public schemasFiltered = |
|||
Observable.of(null) |
|||
.merge(this.schemasFilter.valueChanges.debounceTime(100)) |
|||
.combineLatest(this.schemas, |
|||
(query, schemas) => { |
|||
|
|||
schemas = schemas.filter(t => t.isPublished); |
|||
|
|||
if (query && query.length > 0) { |
|||
schemas = schemas.filter(t => t.name.indexOf(query) >= 0); |
|||
} |
|||
|
|||
schemas = |
|||
schemas.sort((a, b) => { |
|||
if (a.name < b.name) { |
|||
return -1; |
|||
} |
|||
if (a.name > b.name) { |
|||
return 1; |
|||
} |
|||
return 0; |
|||
}); |
|||
|
|||
return schemas; |
|||
}); |
|||
|
|||
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, |
|||
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.next(ImmutableArray.of(dtos)); |
|||
}, error => { |
|||
this.notifyError(error); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,55 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; |
|||
|
|||
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; |
|||
|
|||
@Injectable() |
|||
export class ResolvePublishedSchemaGuard implements Resolve<SchemaDetailsDto> { |
|||
constructor( |
|||
private readonly schemasService: SchemasService, |
|||
private readonly router: Router |
|||
) { |
|||
} |
|||
|
|||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<SchemaDetailsDto> { |
|||
const appName = this.findParameter(route, 'appName'); |
|||
const schemaName = this.findParameter(route, 'schemaName'); |
|||
|
|||
const result = |
|||
this.schemasService.getSchema(appName, schemaName).toPromise() |
|||
.then(dto => { |
|||
if (!dto || !dto.isPublished) { |
|||
this.router.navigate(['/404']); |
|||
} |
|||
|
|||
return dto; |
|||
}).catch(() => { |
|||
this.router.navigate(['/404']); |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private findParameter(route: ActivatedRouteSnapshot, name: string) { |
|||
let result: string; |
|||
|
|||
while (route) { |
|||
result = route.params[name]; |
|||
|
|||
if (result) { |
|||
break; |
|||
} |
|||
|
|||
route = route.parent; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; |
|||
|
|||
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; |
|||
|
|||
@Injectable() |
|||
export class ResolveSchemaGuard implements Resolve<SchemaDetailsDto> { |
|||
constructor( |
|||
private readonly schemasService: SchemasService, |
|||
private readonly router: Router |
|||
) { |
|||
} |
|||
|
|||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<SchemaDetailsDto> { |
|||
const appName = this.findParameter(route, 'appName'); |
|||
const schemaName = this.findParameter(route, 'schemaName'); |
|||
|
|||
const result = |
|||
this.schemasService.getSchema(appName, schemaName).toPromise() |
|||
.then(dto => { |
|||
if (!dto) { |
|||
this.router.navigate(['/404']); |
|||
} |
|||
|
|||
return dto; |
|||
}).catch(() => { |
|||
this.router.navigate(['/404']); |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private findParameter(route: ActivatedRouteSnapshot, name: string) { |
|||
let result: string; |
|||
|
|||
while (route) { |
|||
result = route.params[name]; |
|||
|
|||
if (result) { |
|||
break; |
|||
} |
|||
|
|||
route = route.parent; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue