Browse Source

Take the guards back.

pull/271/head
Sebastian Stehle 9 years ago
parent
commit
f2e8c010bc
  1. 3
      src/Squidex/app/features/administration/declarations.ts
  2. 24
      src/Squidex/app/features/administration/guards/unset-user.guard.ts
  3. 38
      src/Squidex/app/features/administration/guards/user-must-exist.guard.ts
  4. 16
      src/Squidex/app/features/administration/module.ts
  5. 65
      src/Squidex/app/features/administration/pages/users/user-page.component.html
  6. 24
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  7. 2
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  8. 5
      src/Squidex/app/features/administration/pages/users/users-page.component.ts
  9. 15
      src/Squidex/app/features/administration/state/users.state.ts
  10. 4
      src/Squidex/app/features/api/api-area.component.html
  11. 9
      src/Squidex/app/features/api/api-area.component.ts
  12. 2
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.html
  13. 10
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts
  14. 10
      src/Squidex/app/features/apps/pages/apps-page.component.html
  15. 13
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  16. 8
      src/Squidex/app/features/schemas/guards/schema-must-exist.guard.ts
  17. 23
      src/Squidex/app/features/schemas/module.ts
  18. 2
      src/Squidex/app/features/schemas/pages/messages.ts
  19. 2
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html
  20. 6
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts
  21. 8
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  22. 22
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  23. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss
  24. 19
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  25. 2
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  26. 7
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  27. 28
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  28. 69
      src/Squidex/app/features/schemas/state/schemas.state.ts
  29. 2
      src/Squidex/app/framework/angular/root-view.component.ts
  30. 2
      src/Squidex/app/shared/services/schemas.service.ts
  31. 4
      src/Squidex/app/shared/state/apps.state.ts

3
src/Squidex/app/features/administration/declarations.ts

@ -7,6 +7,9 @@
export * from './administration-area.component'; export * from './administration-area.component';
export * from './guards/user-must-exist.guard';
export * from './guards/unset-user.guard';
export * from './pages/event-consumers/event-consumers-page.component'; export * from './pages/event-consumers/event-consumers-page.component';
export * from './pages/users/user-page.component'; export * from './pages/users/user-page.component';
export * from './pages/users/users-page.component'; export * from './pages/users/users-page.component';

24
src/Squidex/app/features/administration/guards/unset-user.guard.ts

@ -0,0 +1,24 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UsersState } from './../state/users.state';
@Injectable()
export class UnsetUserGuard implements CanActivate {
constructor(
private readonly usersState: UsersState
) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.usersState.selectUser(null).map(u => u === null);
}
}

38
src/Squidex/app/features/administration/guards/user-must-exist.guard.ts

@ -0,0 +1,38 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { allParams } from 'framework';
import { UsersState } from './../state/users.state';
@Injectable()
export class UserMustExistGuard implements CanActivate {
constructor(
private readonly usersState: UsersState,
private readonly router: Router
) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const userId = allParams(route)['userId'];
const result =
this.usersState.selectUser(userId)
.do(dto => {
if (!dto) {
this.router.navigate(['/404']);
}
})
.map(u => u !== null);
return result;
}
}

16
src/Squidex/app/features/administration/module.ts

@ -17,9 +17,12 @@ import {
AdministrationAreaComponent, AdministrationAreaComponent,
EventConsumersPageComponent, EventConsumersPageComponent,
EventConsumersService, EventConsumersService,
UnsetUserGuard,
UserMustExistGuard,
UserPageComponent, UserPageComponent,
UsersPageComponent, UsersPageComponent,
UsersService UsersService,
UsersState
} from './declarations'; } from './declarations';
const routes: Routes = [ const routes: Routes = [
@ -40,11 +43,13 @@ const routes: Routes = [
children: [ children: [
{ {
path: 'new', path: 'new',
component: UserPageComponent component: UserPageComponent,
canActivate: [UnsetUserGuard]
}, },
{ {
path: ':userId', path: ':userId',
component: UserPageComponent component: UserPageComponent,
canActivate: [UserMustExistGuard]
} }
] ]
} }
@ -68,7 +73,10 @@ const routes: Routes = [
], ],
providers: [ providers: [
EventConsumersService, EventConsumersService,
UsersService UnsetUserGuard,
UserMustExistGuard,
UsersService,
UsersState
] ]
}) })
export class SqxFeatureAdministrationModule { } export class SqxFeatureAdministrationModule { }

65
src/Squidex/app/features/administration/pages/users/user-page.component.html

@ -1,24 +1,27 @@
<sqx-title message="User Management"></sqx-title> <sqx-title message="User Management"></sqx-title>
<form [formGroup]="userForm" (ngSubmit)="save()"> <form [formGroup]="userForm" (ngSubmit)="save()">
<input style="display:none" type="password" name="foilautofill" /> <input style="display:none" type="password" name="foilautofill"/>
<sqx-panel desiredWidth="26rem"> <sqx-panel desiredWidth="26rem">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title-row"> <div class="panel-title-row">
<div class="float-right" *ngIf="!isNotFound"> <div class="float-right">
<button type="submit" class="btn btn-primary" title="CTRL + S"> <button type="submit" class="btn btn-primary" title="CTRL + S">
Save Save
</button> </button>
</div> </div>
<sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut> <sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut>
<h3 class="panel-title"> <h3 class="panel-title" *ngIf="!user">
{{isNotFound || user ? 'Edit User' : 'New User'}} New User
</h3>
<h3 class="panel-title" *ngIf="user">
Edit User
</h3> </h3>
</div> </div>
<sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut> <sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut>
<a class="panel-close" sqxParentLink> <a class="panel-close" sqxParentLink>
@ -28,46 +31,40 @@
<div class="panel-main"> <div class="panel-main">
<div class="panel-content panel-content-blank"> <div class="panel-content panel-content-blank">
<div *ngIf="isNotFound"> <div *ngIf="userFormError">
The user does not exist. <div class="form-alert form-alert-error" [innerHTML]="userFormError"></div>
</div> </div>
<div *ngIf="!isNotFound"> <div class="form-group">
<div *ngIf="userFormError"> <label for="email">Email</label>
<div class="form-alert form-alert-error" [innerHTML]="userFormError"></div>
</div>
<div class="form-group"> <sqx-control-errors for="email" [submitted]="userFormSubmitted"></sqx-control-errors>
<label for="email">Email</label>
<sqx-control-errors for="email" [submitted]="userFormSubmitted"></sqx-control-errors>
<input type="email" class="form-control" id="email" maxlength="100" formControlName="email" autocomplete="false" /> <input type="email" class="form-control" id="email" maxlength="100" formControlName="email" autocomplete="false" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="displayName">Display Name</label> <label for="displayName">Display Name</label>
<sqx-control-errors for="displayName" [submitted]="userFormSubmitted"></sqx-control-errors> <sqx-control-errors for="displayName" [submitted]="userFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="false" /> <input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="false" />
</div> </div>
<div class="form-group form-group-password" [class.hidden]="isCurrentUser"> <div class="form-group form-group-password" [class.hidden]="isCurrentUser">
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> <label for="password">Password</label>
<sqx-control-errors for="password" [submitted]="userFormSubmitted"></sqx-control-errors> <sqx-control-errors for="password" [submitted]="userFormSubmitted"></sqx-control-errors>
<input type="password" class="form-control" id="password" maxlength="100" formControlName="password" autocomplete="false" /> <input type="password" class="form-control" id="password" maxlength="100" formControlName="password" autocomplete="false" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Confirm Password</label> <label for="password">Confirm Password</label>
<sqx-control-errors for="passwordConfirm" [submitted]="userFormSubmitted"></sqx-control-errors> <sqx-control-errors for="passwordConfirm" [submitted]="userFormSubmitted"></sqx-control-errors>
<input type="password" class="form-control" id="passwordConfirm" maxlength="100" formControlName="passwordConfirm" autocomplete="false" /> <input type="password" class="form-control" id="passwordConfirm" maxlength="100" formControlName="passwordConfirm" autocomplete="false" />
</div>
</div> </div>
</div> </div>
</div> </div>

24
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -5,9 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { AuthService, ValidatorsEx } from 'shared'; import { AuthService, ValidatorsEx } from 'shared';
@ -19,7 +20,9 @@ import { UsersState } from './../../state/users.state';
styleUrls: ['./user-page.component.scss'], styleUrls: ['./user-page.component.scss'],
templateUrl: './user-page.component.html' templateUrl: './user-page.component.html'
}) })
export class UserPageComponent implements OnInit { export class UserPageComponent implements OnDestroy, OnInit {
private selectedUserSubscription: Subscription;
public userFormSubmitted = false; public userFormSubmitted = false;
public userFormError = ''; public userFormError = '';
public userForm = public userForm =
@ -48,7 +51,6 @@ export class UserPageComponent implements OnInit {
public user: UserDto | null; public user: UserDto | null;
public isCurrentUser = false; public isCurrentUser = false;
public isNotFound = false;
constructor( constructor(
private readonly authService: AuthService, private readonly authService: AuthService,
@ -59,14 +61,13 @@ export class UserPageComponent implements OnInit {
) { ) {
} }
public ngOnInit() { public ngOnDestroy() {
this.route.params.map(p => p['userId']) this.selectedUserSubscription.unsubscribe();
.switchMap(id => this.usersState.selectUser(id).map(u => { return { user: u, expected: !!id }; })) }
.subscribe(result => {
this.isNotFound = !result.user && result.expected;
this.setupAndPopulateForm(result.user); public ngOnInit() {
}); this.selectedUserSubscription =
this.usersState.selectedUser.subscribe(user => this.setupAndPopulateForm(user!));
} }
public save() { public save() {
@ -118,9 +119,8 @@ export class UserPageComponent implements OnInit {
private resetFormState(message: string = '') { private resetFormState(message: string = '') {
this.userFormSubmitted = false; this.userFormSubmitted = false;
this.userFormError = message; this.userFormError = message;
this.userForm.enable();
this.userForm.controls['password'].reset(); this.userForm.controls['password'].reset();
this.userForm.controls['passwordConfirm'].reset(); this.userForm.controls['passwordConfirm'].reset();
this.userForm.enable();
} }
} }

2
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -76,7 +76,7 @@
<i class="icon icon-lock"></i> <i class="icon icon-lock"></i>
</button> </button>
</span> </span>
<button *ngIf="user.id === authState.user.id" class="btn btn-link invisible"> <button *ngIf="user.id === authState.user?.id" class="btn btn-link invisible">
&nbsp; &nbsp;
</button> </button>
</td> </td>

5
src/Squidex/app/features/administration/pages/users/users-page.component.ts

@ -16,10 +16,7 @@ import { UsersState } from './../../state/users.state';
@Component({ @Component({
selector: 'sqx-users-page', selector: 'sqx-users-page',
styleUrls: ['./users-page.component.scss'], styleUrls: ['./users-page.component.scss'],
templateUrl: './users-page.component.html', templateUrl: './users-page.component.html'
providers: [
UsersState
]
}) })
export class UsersPageComponent implements OnInit { export class UsersPageComponent implements OnInit {
public usersFilter = new FormControl(); public usersFilter = new FormControl();

15
src/Squidex/app/features/administration/state/users.state.ts

@ -37,8 +37,14 @@ export class UsersState {
} }
public selectUser(id: string | null): Observable<UserDto | null> { public selectUser(id: string | null): Observable<UserDto | null> {
const observable = return this.loadUser(id)
!id ? .do(user => {
this.selectedUser.next(user);
});
}
private loadUser(id: string | null) {
return !id ?
Observable.of(null) : Observable.of(null) :
Observable.of(this.users.value.find(x => x.id === id)) Observable.of(this.users.value.find(x => x.id === id))
.switchMap(user => { .switchMap(user => {
@ -48,11 +54,6 @@ export class UsersState {
return Observable.of(user); return Observable.of(user);
} }
}); });
return observable
.do(user => {
this.selectedUser.next(user);
});
} }
public loadUsers(): Observable<any> { public loadUsers(): Observable<any> {

4
src/Squidex/app/features/api/api-area.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title> <sqx-title message="{app} | Settings" parameter1="app" [value1]="appsState.appName"></sqx-title>
<sqx-panel theme="dark" desiredWidth="12rem"> <sqx-panel theme="dark" desiredWidth="12rem">
<div class="panel-header"> <div class="panel-header">
@ -21,7 +21,7 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/api/content/{{ctx.appName}}/docs" target="_blank"> <a class="nav-link" href="/api/content/{{appsState.appName}}/docs" target="_blank">
Swagger Swagger
</a> </a>
</li> </li>

9
src/Squidex/app/features/api/api-area.component.ts

@ -7,19 +7,16 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AppContext } from 'shared'; import { AppsState } from 'shared';
@Component({ @Component({
selector: 'sqx-api-area', selector: 'sqx-api-area',
styleUrls: ['./api-area.component.scss'], styleUrls: ['./api-area.component.scss'],
templateUrl: './api-area.component.html', templateUrl: './api-area.component.html'
providers: [
AppContext
]
}) })
export class ApiAreaComponent { export class ApiAreaComponent {
constructor( constructor(
public readonly ctx: AppContext public readonly appsState: AppsState
) { ) {
} }
} }

2
src/Squidex/app/features/api/pages/graphql/graphql-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="ctx.appName"></sqx-title> <sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="appsState.appName"></sqx-title>
<sqx-panel desiredWidth="*"> <sqx-panel desiredWidth="*">
<div #graphiQLContainer></div> <div #graphiQLContainer></div>

10
src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts

@ -16,7 +16,7 @@ const GraphiQL = require('graphiql');
/* tslint:disable:use-view-encapsulation */ /* tslint:disable:use-view-encapsulation */
import { import {
AppContext, AppsState,
GraphQlService, GraphQlService,
LocalStoreService LocalStoreService
} from 'shared'; } from 'shared';
@ -25,16 +25,14 @@ import {
selector: 'sqx-graphql-page', selector: 'sqx-graphql-page',
styleUrls: ['./graphql-page.component.scss'], styleUrls: ['./graphql-page.component.scss'],
templateUrl: './graphql-page.component.html', templateUrl: './graphql-page.component.html',
providers: [
AppContext
],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class GraphQLPageComponent implements OnInit { export class GraphQLPageComponent implements OnInit {
@ViewChild('graphiQLContainer') @ViewChild('graphiQLContainer')
public graphiQLContainer: ElementRef; public graphiQLContainer: ElementRef;
constructor(public readonly ctx: AppContext, constructor(
public readonly appsState: AppsState,
private readonly graphQlService: GraphQlService, private readonly graphQlService: GraphQlService,
private readonly localStoreService: LocalStoreService private readonly localStoreService: LocalStoreService
) { ) {
@ -56,7 +54,7 @@ export class GraphQLPageComponent implements OnInit {
} }
private request(params: any) { private request(params: any) {
return this.graphQlService.query(this.ctx.appName, params).catch(response => Observable.of(response.error)).toPromise(); return this.graphQlService.query(this.appsState.appName, params).catch(response => Observable.of(response.error)).toPromise();
} }
} }

10
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -1,7 +1,7 @@
<sqx-title message="Apps"></sqx-title> <sqx-title message="Apps"></sqx-title>
<div class="apps-section"> <div class="apps-section">
<h1 class="apps-title">Hi {{ctx.user.displayName}}</h1> <h1 class="apps-title">Hi {{authState.user?.displayName}}</h1>
<div class="subtext"> <div class="subtext">
Welcome to Squidex. Welcome to Squidex.
@ -49,7 +49,9 @@
<div class="card-text"> <div class="card-text">
<div>Start with our ready to use blog.</div> <div>Start with our ready to use blog.</div>
<div>Sample Code: <a href="https://github.com/Squidex/squidex-samples/tree/master/csharp/Sample.Blog" (click)="$event.stopPropagation()" target="_blank">ASP.NET Core</a></div> <div>
Sample Code: <a href="https://github.com/Squidex/squidex-samples/tree/master/csharp/Sample.Blog" (click)="$event.stopPropagation()" target="_blank">C#</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -64,7 +66,9 @@
<div class="card-text"> <div class="card-text">
<div>Create your profile page.</div> <div>Create your profile page.</div>
<div>Sample Code: <a href="https://github.com/Squidex/squidex-samples/tree/master/csharp/Sample.Profile" (click)="$event.stopPropagation()" target="_blank">ASP.NET Core</a></div> <div>
Sample Code: <a href="https://github.com/Squidex/squidex-samples/tree/master/csharp/Sample.Profile" (click)="$event.stopPropagation()" target="_blank">C#</a>
</div>
</div> </div>
</div> </div>
</div> </div>

13
src/Squidex/app/features/apps/pages/apps-page.component.ts

@ -9,9 +9,9 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
AppContext,
AppDto, AppDto,
AppsState, AppsState,
AuthService,
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
ModalView, ModalView,
@ -22,9 +22,6 @@ import {
selector: 'sqx-apps-page', selector: 'sqx-apps-page',
styleUrls: ['./apps-page.component.scss'], styleUrls: ['./apps-page.component.scss'],
templateUrl: './apps-page.component.html', templateUrl: './apps-page.component.html',
providers: [
AppContext
],
animations: [ animations: [
fadeAnimation fadeAnimation
] ]
@ -35,13 +32,13 @@ export class AppsPageComponent implements OnDestroy, OnInit {
public addAppDialog = new ModalView(); public addAppDialog = new ModalView();
public apps: ImmutableArray<AppDto>; public apps: ImmutableArray<AppDto>;
public apptemplate = ''; public appTemplate = '';
public onboardingModal = new ModalView(); public onboardingModal = new ModalView();
constructor( constructor(
public readonly ctx: AppContext, public readonly appsState: AppsState,
private readonly appsState: AppsState, public readonly authState: AuthService,
private readonly onboardingService: OnboardingService private readonly onboardingService: OnboardingService
) { ) {
} }
@ -64,7 +61,7 @@ export class AppsPageComponent implements OnDestroy, OnInit {
} }
public createNewApp(template: string) { public createNewApp(template: string) {
this.apptemplate = template; this.appTemplate = template;
this.addAppDialog.show(); this.addAppDialog.show();
} }

8
src/Squidex/app/features/schemas/guards/schema-must-exist.guard.ts

@ -22,13 +22,7 @@ export class SchemaMustExistGuard implements CanActivate {
} }
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const params = allParams(route); const schemaName = allParams(route)['schemaName'];
const schemaName = params['schemaName'];
if (!schemaName) {
throw 'Route must contain schema name.';
}
const result = const result =
this.schemasState.selectSchema(schemaName) this.schemasState.selectSchema(schemaName)

23
src/Squidex/app/features/schemas/module.ts

@ -10,8 +10,6 @@ import { RouterModule, Routes } from '@angular/router';
import { DndModule } from 'ng2-dnd'; import { DndModule } from 'ng2-dnd';
import { import {
HelpComponent,
HistoryComponent,
SqxFrameworkModule, SqxFrameworkModule,
SqxSharedModule SqxSharedModule
} from 'shared'; } from 'shared';
@ -54,29 +52,10 @@ const routes: Routes = [
path: '', path: '',
component: SchemasPageComponent, component: SchemasPageComponent,
children: [ children: [
{
path: ''
},
{ {
path: ':schemaName', path: ':schemaName',
component: SchemaPageComponent, component: SchemaPageComponent,
canActivate: [SchemaMustExistGuard], canActivate: [SchemaMustExistGuard]
children: [
{
path: 'history',
component: HistoryComponent,
data: {
channel: 'schemas.{schemaName}'
}
},
{
path: 'help',
component: HelpComponent,
data: {
helpPage: '05-integrated/schemas'
}
}
]
}] }]
} }
]; ];

2
src/Squidex/app/features/schemas/pages/messages.ts

@ -7,7 +7,7 @@
export class SchemaCloning { export class SchemaCloning {
constructor( constructor(
public readonly importing: any public readonly schema: any
) { ) {
} }
} }

2
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html

@ -42,7 +42,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<sqx-control-errors for="name" [submitted]="addFieldFormSubmitted"></sqx-control-errors> <sqx-control-errors for="name" submitOnly="true" [submitted]="addFieldFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" #nameInput placeholder="Enter field name" sqxFocusOnInit /> <input type="text" class="form-control" formControlName="name" maxlength="40" #nameInput placeholder="Enter field name" sqxFocusOnInit />
</div> </div>

6
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts

@ -10,7 +10,6 @@ import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { import {
AddFieldDto, AddFieldDto,
AppContext,
createProperties, createProperties,
fadeAnimation, fadeAnimation,
FieldDto, FieldDto,
@ -26,9 +25,6 @@ import { SchemasState } from './../../state/schemas.state';
selector: 'sqx-field-wizard', selector: 'sqx-field-wizard',
styleUrls: ['./field-wizard.component.scss'], styleUrls: ['./field-wizard.component.scss'],
templateUrl: './field-wizard.component.html', templateUrl: './field-wizard.component.html',
providers: [
AppContext
],
animations: [ animations: [
fadeAnimation fadeAnimation
] ]
@ -146,9 +142,9 @@ export class FieldWizardComponent {
private resetFieldForm(error = '') { private resetFieldForm(error = '') {
this.addFieldFormSubmitted = false; this.addFieldFormSubmitted = false;
this.addFieldError = error;
this.addFieldForm.enable(); this.addFieldForm.enable();
this.addFieldForm.reset({ type: 'String' }, { emitEvent: false }); this.addFieldForm.reset({ type: 'String' }, { emitEvent: false });
this.addFieldError = error;
} }
} }

8
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -93,19 +93,21 @@ export class FieldComponent implements OnInit {
if (this.editForm.valid) { if (this.editForm.valid) {
const properties = createProperties(this.field.properties['fieldType'], this.editForm.value); const properties = createProperties(this.field.properties['fieldType'], this.editForm.value);
this.schemasState.updateField(this.schema, this.field, new UpdateFieldDto(properties)).subscribe(); this.schemasState.updateField(this.schema, this.field, new UpdateFieldDto(properties))
.subscribe(() => {
this.resetEditForm();
});
} }
} }
private resetEditForm() { private resetEditForm() {
this.isEditing = false;
this.editFormSubmitted = false; this.editFormSubmitted = false;
this.editForm.reset(this.field.properties); this.editForm.reset(this.field.properties);
if (this.field.isLocked) { if (this.field.isLocked) {
this.editForm.disable(); this.editForm.disable();
} }
this.isEditing = false;
} }
} }

22
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | {schema}" parameter1="app" [value1]="ctx.appName" parameter2="schema" [value2]="schema?.displayName"></sqx-title> <sqx-title message="{app} | {schema}" parameter1="app" [value1]="appsState.appName" parameter2="schema" [value2]="schema?.displayName"></sqx-title>
<sqx-panel desiredWidth="56rem"> <sqx-panel desiredWidth="56rem">
<div class="panel-header"> <div class="panel-header">
@ -68,22 +68,6 @@
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">Add Field</div> <i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">Add Field</div>
</button> </button>
</div> </div>
<div class="panel-sidebar">
<a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i>
</a>
<a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i>
</a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div>
</div> </div>
</sqx-panel> </sqx-panel>
@ -128,6 +112,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<router-outlet></router-outlet>

2
src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss

@ -40,7 +40,7 @@
& { & {
@include circle(5.25rem); @include circle(5.25rem);
@include box-shadow(0, 8px, 16px, .3); @include box-shadow(0, 8px, 16px, .3);
@include absolute(auto, 6.5rem, 1rem, auto); @include absolute(auto, 2rem, 1rem, auto);
} }
&-icon { &-icon {

19
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -6,14 +6,15 @@
*/ */
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
AppContext, AppsState,
fadeAnimation, fadeAnimation,
FieldDto, FieldDto,
fieldTypes, fieldTypes,
MessageBus,
ModalView, ModalView,
SchemaDetailsDto SchemaDetailsDto
} from 'shared'; } from 'shared';
@ -28,9 +29,6 @@ import { SchemasState } from './../../state/schemas.state';
selector: 'sqx-schema-page', selector: 'sqx-schema-page',
styleUrls: ['./schema-page.component.scss'], styleUrls: ['./schema-page.component.scss'],
templateUrl: './schema-page.component.html', templateUrl: './schema-page.component.html',
providers: [
AppContext
],
animations: [ animations: [
fadeAnimation fadeAnimation
] ]
@ -52,9 +50,12 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
public addFieldDialog = new ModalView(); public addFieldDialog = new ModalView();
constructor(public readonly ctx: AppContext, constructor(
public readonly appsState: AppsState,
public readonly schemasState: SchemasState,
private readonly route: ActivatedRoute,
private readonly router: Router, private readonly router: Router,
private readonly schemasState: SchemasState private readonly messageBus: MessageBus
) { ) {
} }
@ -92,7 +93,7 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
} }
public cloneSchema() { public cloneSchema() {
this.ctx.bus.emit(new SchemaCloning(this.schemaExport)); this.messageBus.emit(new SchemaCloning(this.schemaExport));
} }
private export() { private export() {
@ -127,7 +128,7 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
} }
private back() { private back() {
this.router.navigate(['../'], { relativeTo: this.ctx.route }); this.router.navigate(['../'], { relativeTo: this.route });
} }
} }

2
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -23,7 +23,7 @@
<input type="text" class="form-control" id="schemaName" formControlName="name" autocomplete="off" sqxLowerCaseInput sqxFocusOnInit /> <input type="text" class="form-control" id="schemaName" formControlName="name" autocomplete="off" sqxLowerCaseInput sqxFocusOnInit />
<small class="form-text text-muted"> <small class="form-text text-muted">
The schema name becomes part of the api url,<br /> e.g {{apiUrl.buildUrl("api/content/")}}{{ctx.appName}}/<b>{{schemaName | async}}</b>/. The schema name becomes part of the api url,<br /> e.g {{apiUrl.buildUrl("api/content/")}}{{appsState.appName}}/<b>{{schemaName | async}}</b>/.
</small> </small>
<small class="form-text text-muted"> <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. 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.

7
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -8,7 +8,11 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { ApiUrlConfig, ValidatorsEx } from 'shared'; import {
ApiUrlConfig,
AppsState,
ValidatorsEx
} from 'shared';
import { SchemasState } from './../../state/schemas.state'; import { SchemasState } from './../../state/schemas.state';
@ -47,6 +51,7 @@ export class SchemaFormComponent implements OnInit {
constructor( constructor(
public readonly apiUrl: ApiUrlConfig, public readonly apiUrl: ApiUrlConfig,
public readonly appsState: AppsState,
private readonly schemasState: SchemasState, private readonly schemasState: SchemasState,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {

28
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -5,16 +5,20 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { import {
AppsState, AppsState,
fadeAnimation, fadeAnimation,
MessageBus,
ModalView ModalView
} from 'shared'; } from 'shared';
import { SchemaCloning } from './../messages';
import { SchemasState } from './../../state/schemas.state'; import { SchemasState } from './../../state/schemas.state';
@Component({ @Component({
@ -25,7 +29,9 @@ import { SchemasState } from './../../state/schemas.state';
fadeAnimation fadeAnimation
] ]
}) })
export class SchemasPageComponent implements OnInit { export class SchemasPageComponent implements OnDestroy, OnInit {
private schemaCloningSubscription: Subscription;
public addSchemaDialog = new ModalView(); public addSchemaDialog = new ModalView();
public schemasFilter = new FormControl(); public schemasFilter = new FormControl();
@ -42,13 +48,27 @@ export class SchemasPageComponent implements OnInit {
public import: any; public import: any;
constructor(public readonly appsState: AppsState, constructor(
public readonly appsState: AppsState,
private readonly schemasState: SchemasState,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly schemasState: SchemasState private readonly messageBus: MessageBus
) { ) {
} }
public ngOnDestroy() {
this.schemaCloningSubscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.schemaCloningSubscription =
this.messageBus.of(SchemaCloning)
.subscribe(m => {
this.import = m.schema;
this.addSchemaDialog.show();
});
this.route.params.map(q => q['showDialog']) this.route.params.map(q => q['showDialog'])
.subscribe(showDialog => { .subscribe(showDialog => {
if (showDialog) { if (showDialog) {

69
src/Squidex/app/features/schemas/state/schemas.state.ts

@ -32,9 +32,10 @@ export class SchemasState {
public schemasItems = new BehaviorSubject<ImmutableArray<SchemaDto>>(ImmutableArray.empty()); public schemasItems = new BehaviorSubject<ImmutableArray<SchemaDto>>(ImmutableArray.empty());
public selectedSchema = new BehaviorSubject<SchemaDetailsDto | null>(null); public selectedSchema = new BehaviorSubject<SchemaDetailsDto | null>(null);
public selectedReferencedSchema = new BehaviorSubject<SchemaDetailsDto | null>(null);
private get app() { private get appName() {
return this.appsState.selectedApp.value!.name; return this.appsState.appName;
} }
private get user() { private get user() {
@ -49,28 +50,35 @@ export class SchemasState {
) { ) {
} }
public selectSchema(id: string | null): Observable<boolean> { public selectSchema(id: string | null): Observable<SchemaDetailsDto | null> {
const observable = return this.loadSchema(id)
!id ? .do(schema => {
this.selectedSchema.next(schema);
});
}
public selectReferencedSchema(id: string | null): Observable<SchemaDetailsDto | null> {
return this.loadSchema(id)
.do(schema => {
this.selectedReferencedSchema.next(schema);
});
}
private loadSchema(id: string | null) {
return !id ?
Observable.of(null) : Observable.of(null) :
Observable.of(<SchemaDetailsDto>this.schemasItems.value.find(x => x.id === id && x instanceof SchemaDetailsDto)) Observable.of(<SchemaDetailsDto>this.schemasItems.value.find(x => x.id === id && x instanceof SchemaDetailsDto))
.switchMap(schema => { .switchMap(schema => {
if (!schema) { if (!schema) {
return this.schemasService.getSchema(this.app, id).catch(() => Observable.of(null)); return this.schemasService.getSchema(this.appName, id).catch(() => Observable.of(null));
} else { } else {
return Observable.of(schema); return Observable.of(schema);
} }
}); });
return observable
.do(schema => {
this.selectedSchema.next(schema);
})
.map(s => s !== null);
} }
public load(): Observable<any> { public load(): Observable<any> {
return this.schemasService.getSchemas(this.app) return this.schemasService.getSchemas(this.appName)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dtos => { .do(dtos => {
this.schemasItems.nextBy(v => ImmutableArray.of(dtos)); this.schemasItems.nextBy(v => ImmutableArray.of(dtos));
@ -78,21 +86,21 @@ export class SchemasState {
} }
public create(request: CreateSchemaDto) { public create(request: CreateSchemaDto) {
return this.schemasService.postSchema(this.app, request, this.user, DateTime.now()) return this.schemasService.postSchema(this.appName, request, this.user, DateTime.now())
.do(dto => { .do(dto => {
this.schemasItems.nextBy(v => v.push(dto)); this.schemasItems.nextBy(v => v.push(dto));
}); });
} }
public addField(schema: SchemaDetailsDto, request: AddFieldDto): Observable<FieldDto> { public addField(schema: SchemaDetailsDto, request: AddFieldDto): Observable<FieldDto> {
return this.schemasService.postField(this.app, schema.name, request, schema.version) return this.schemasService.postField(this.appName, schema.name, request, schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(schema.addField(dto.payload, this.user, dto.version)); this.replaceSchema(schema.addField(dto.payload, this.user, dto.version));
}).map(d => d.payload); }).map(d => d.payload);
} }
public publish(schema: SchemaDto): Observable<any> { public publish(schema: SchemaDto): Observable<any> {
return this.schemasService.publishSchema(this.app, schema.name, schema.version) return this.schemasService.publishSchema(this.appName, schema.name, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.publish(this.user, dto.version)); this.replaceSchema(schema.publish(this.user, dto.version));
@ -100,7 +108,7 @@ export class SchemasState {
} }
public unpublish(schema: SchemaDto): Observable<any> { public unpublish(schema: SchemaDto): Observable<any> {
return this.schemasService.unpublishSchema(this.app, schema.name, schema.version) return this.schemasService.unpublishSchema(this.appName, schema.name, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.unpublish(this.user, dto.version)); this.replaceSchema(schema.unpublish(this.user, dto.version));
@ -108,7 +116,7 @@ export class SchemasState {
} }
public enableField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { public enableField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> {
return this.schemasService.enableField(this.app, schema.name, field.fieldId, schema.version) return this.schemasService.enableField(this.appName, schema.name, field.fieldId, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.updateField(field.enable(), this.user, dto.version)); this.replaceSchema(schema.updateField(field.enable(), this.user, dto.version));
@ -116,7 +124,7 @@ export class SchemasState {
} }
public disableField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { public disableField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> {
return this.schemasService.disableField(this.app, schema.name, field.fieldId, schema.version) return this.schemasService.disableField(this.appName, schema.name, field.fieldId, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.updateField(field.disable(), this.user, dto.version)); this.replaceSchema(schema.updateField(field.disable(), this.user, dto.version));
@ -124,7 +132,7 @@ export class SchemasState {
} }
public lockField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { public lockField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> {
return this.schemasService.lockField(this.app, schema.name, field.fieldId, schema.version) return this.schemasService.lockField(this.appName, schema.name, field.fieldId, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.updateField(field.lock(), this.user, dto.version)); this.replaceSchema(schema.updateField(field.lock(), this.user, dto.version));
@ -132,7 +140,7 @@ export class SchemasState {
} }
public showField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { public showField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> {
return this.schemasService.showField(this.app, schema.name, field.fieldId, schema.version) return this.schemasService.showField(this.appName, schema.name, field.fieldId, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.updateField(field.show(), this.user, dto.version)); this.replaceSchema(schema.updateField(field.show(), this.user, dto.version));
@ -140,7 +148,7 @@ export class SchemasState {
} }
public hideField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { public hideField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> {
return this.schemasService.hideField(this.app, schema.name, field.fieldId, schema.version) return this.schemasService.hideField(this.appName, schema.name, field.fieldId, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.updateField(field.hide(), this.user, dto.version)); this.replaceSchema(schema.updateField(field.hide(), this.user, dto.version));
@ -148,7 +156,7 @@ export class SchemasState {
} }
public deleteField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { public deleteField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> {
return this.schemasService.deleteField(this.app, schema.name, field.fieldId, schema.version) return this.schemasService.deleteField(this.appName, schema.name, field.fieldId, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.removeField(field, this.user, dto.version)); this.replaceSchema(schema.removeField(field, this.user, dto.version));
@ -156,7 +164,7 @@ export class SchemasState {
} }
public sortFields(schema: SchemaDetailsDto, fields: FieldDto[]): Observable<any> { public sortFields(schema: SchemaDetailsDto, fields: FieldDto[]): Observable<any> {
return this.schemasService.putFieldOrdering(this.app, schema.name, fields.map(t => t.fieldId), schema.version) return this.schemasService.putFieldOrdering(this.appName, schema.name, fields.map(t => t.fieldId), schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.replaceFields(fields, this.user, dto.version)); this.replaceSchema(schema.replaceFields(fields, this.user, dto.version));
@ -164,7 +172,7 @@ export class SchemasState {
} }
public updateField(schema: SchemaDetailsDto, field: FieldDto, request: UpdateFieldDto): Observable<any> { public updateField(schema: SchemaDetailsDto, field: FieldDto, request: UpdateFieldDto): Observable<any> {
return this.schemasService.putField(this.app, schema.name, field.fieldId, request, schema.version) return this.schemasService.putField(this.appName, schema.name, field.fieldId, request, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.updateField(field.update(request.properties), this.user, dto.version)); this.replaceSchema(schema.updateField(field.update(request.properties), this.user, dto.version));
@ -172,7 +180,7 @@ export class SchemasState {
} }
public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto): Observable<any> { public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto): Observable<any> {
return this.schemasService.putSchemaScripts(this.app, schema.name, request, schema.version) return this.schemasService.putSchemaScripts(this.appName, schema.name, request, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.configureScripts(request, this.user, dto.version)); this.replaceSchema(schema.configureScripts(request, this.user, dto.version));
@ -180,7 +188,7 @@ export class SchemasState {
} }
public update(schema: SchemaDetailsDto, request: UpdateSchemaDto): Observable<any> { public update(schema: SchemaDetailsDto, request: UpdateSchemaDto): Observable<any> {
return this.schemasService.putSchema(this.app, schema.name, request, schema.version) return this.schemasService.putSchema(this.appName, schema.name, request, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.replaceSchema(schema.update(request, this.user, dto.version)); this.replaceSchema(schema.update(request, this.user, dto.version));
@ -188,7 +196,7 @@ export class SchemasState {
} }
public delete(schema: SchemaDto): Observable<any> { public delete(schema: SchemaDto): Observable<any> {
return this.schemasService.deleteSchema(this.app, schema.name, schema.version) return this.schemasService.deleteSchema(this.appName, schema.name, schema.version)
.catch(error => this.dialogs.notifyError(error)) .catch(error => this.dialogs.notifyError(error))
.do(dto => { .do(dto => {
this.schemasItems.nextBy(v => v.filter(s => s.id !== schema.id)); this.schemasItems.nextBy(v => v.filter(s => s.id !== schema.id));
@ -198,7 +206,10 @@ export class SchemasState {
private replaceSchema(schema: SchemaDto) { private replaceSchema(schema: SchemaDto) {
this.schemasItems.nextBy(v => v.replaceBy('id', schema)); this.schemasItems.nextBy(v => v.replaceBy('id', schema));
this.selectedSchema.nextBy(v => v !== null && v.id === schema.id ? <SchemaDetailsDto>schema : v); if (schema instanceof SchemaDetailsDto) {
this.selectedSchema.nextBy(v => v !== null && v.id === schema.id ? schema : v);
this.selectedReferencedSchema.nextBy(v => v !== null && v.id === schema.id ? schema : v);
}
} }
public trackBySchema(index: number, schema: SchemaDto): any { public trackBySchema(index: number, schema: SchemaDto): any {

2
src/Squidex/app/framework/angular/root-view.component.ts

@ -16,6 +16,6 @@ import { Component, ViewChild, ViewContainerRef } from '@angular/core';
` `
}) })
export class RootViewComponent { export class RootViewComponent {
@ViewChild('element') @ViewChild('element', { read: ViewContainerRef })
public viewContainer: ViewContainerRef; public viewContainer: ViewContainerRef;
} }

2
src/Squidex/app/shared/services/schemas.service.ts

@ -32,7 +32,7 @@ export const fieldTypes = [
description: 'Images, videos, documents.' description: 'Images, videos, documents.'
}, { }, {
type: 'Boolean', type: 'Boolean',
description: 'Yes or no, true or false, 1 or 0.' description: 'Yes or no, true or false.'
}, { }, {
type: 'DateTime', type: 'DateTime',
description: 'Events date, opening hours.' description: 'Events date, opening hours.'

4
src/Squidex/app/shared/state/apps.state.ts

@ -25,6 +25,10 @@ export class AppsState {
public selectedApp = new BehaviorSubject<AppDto | null>(null); public selectedApp = new BehaviorSubject<AppDto | null>(null);
public get appName() {
return this.selectedApp.value!.name;
}
constructor( constructor(
private readonly appsService: AppsService private readonly appsService: AppsService
) { ) {

Loading…
Cancel
Save