Browse Source

Refactoring/small components (#416)

* Better title service
* Smaller components
pull/419/head
Sebastian Stehle 7 years ago
committed by GitHub
parent
commit
6ba2045df3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Squidex/app-config/webpack.config.js
  2. 2
      src/Squidex/app/app.module.ts
  3. 2
      src/Squidex/app/features/administration/declarations.ts
  4. 4
      src/Squidex/app/features/administration/module.ts
  5. 66
      src/Squidex/app/features/administration/pages/event-consumers/event-consumer.component.ts
  6. 27
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  7. 12
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  8. 5
      src/Squidex/app/features/administration/pages/users/user-page.component.html
  9. 55
      src/Squidex/app/features/administration/pages/users/user.component.ts
  10. 23
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  11. 11
      src/Squidex/app/features/administration/pages/users/users-page.component.ts
  12. 4
      src/Squidex/app/features/administration/services/event-consumers.service.ts
  13. 2
      src/Squidex/app/features/api/api-area.component.html
  14. 2
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.html
  15. 5
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts
  16. 6
      src/Squidex/app/features/apps/pages/news-dialog.component.ts
  17. 2
      src/Squidex/app/features/assets/pages/assets-page.component.html
  18. 5
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  19. 2
      src/Squidex/app/features/content/declarations.ts
  20. 4
      src/Squidex/app/features/content/module.ts
  21. 3
      src/Squidex/app/features/content/pages/comments/comments-page.component.ts
  22. 9
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  23. 14
      src/Squidex/app/features/content/pages/content/content-page.component.html
  24. 8
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  25. 12
      src/Squidex/app/features/content/pages/content/field-languages.component.ts
  26. 4
      src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts
  27. 29
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  28. 7
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  29. 2
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  30. 10
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  31. 8
      src/Squidex/app/features/content/shared/assets-editor.component.html
  32. 98
      src/Squidex/app/features/content/shared/content-item.component.html
  33. 3
      src/Squidex/app/features/content/shared/content-status.component.ts
  34. 3
      src/Squidex/app/features/content/shared/content-value.component.ts
  35. 101
      src/Squidex/app/features/content/shared/content.component.html
  36. 0
      src/Squidex/app/features/content/shared/content.component.scss
  37. 12
      src/Squidex/app/features/content/shared/content.component.ts
  38. 19
      src/Squidex/app/features/content/shared/contents-selector.component.html
  39. 9
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  40. 3
      src/Squidex/app/features/content/shared/due-time-selector.component.ts
  41. 19
      src/Squidex/app/features/content/shared/references-editor.component.html
  42. 4
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  43. 15
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  44. 1
      src/Squidex/app/features/rules/declarations.ts
  45. 2
      src/Squidex/app/features/rules/module.ts
  46. 2
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.html
  47. 5
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts
  48. 24
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  49. 87
      src/Squidex/app/features/rules/pages/rules/rule.component.ts
  50. 42
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  51. 2
      src/Squidex/app/features/rules/pages/rules/rules-page.component.ts
  52. 3
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  53. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  54. 9
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  55. 2
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  56. 5
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  57. 9
      src/Squidex/app/features/settings/declarations.ts
  58. 22
      src/Squidex/app/features/settings/module.ts
  59. 87
      src/Squidex/app/features/settings/pages/backups/backup.component.ts
  60. 54
      src/Squidex/app/features/settings/pages/backups/backups-page.component.html
  61. 9
      src/Squidex/app/features/settings/pages/backups/backups-page.component.ts
  62. 20
      src/Squidex/app/features/settings/pages/backups/pipes.ts
  63. 60
      src/Squidex/app/features/settings/pages/clients/client-add-form.component.ts
  64. 9
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  65. 20
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  66. 28
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  67. 24
      src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.html
  68. 16
      src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.scss
  69. 84
      src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.ts
  70. 67
      src/Squidex/app/features/settings/pages/contributors/contributor.component.ts
  71. 59
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  72. 14
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss
  73. 77
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  74. 69
      src/Squidex/app/features/settings/pages/languages/language-add-form.component.ts
  75. 3
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  76. 21
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  77. 42
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  78. 2
      src/Squidex/app/features/settings/pages/more/more-page.component.html
  79. 19
      src/Squidex/app/features/settings/pages/more/more-page.component.ts
  80. 12
      src/Squidex/app/features/settings/pages/patterns/pattern.component.ts
  81. 2
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html
  82. 2
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts
  83. 44
      src/Squidex/app/features/settings/pages/plans/plan.component.html
  84. 34
      src/Squidex/app/features/settings/pages/plans/plan.component.scss
  85. 41
      src/Squidex/app/features/settings/pages/plans/plan.component.ts
  86. 51
      src/Squidex/app/features/settings/pages/plans/plans-page.component.html
  87. 16
      src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
  88. 60
      src/Squidex/app/features/settings/pages/roles/role-add-form.component.ts
  89. 2
      src/Squidex/app/features/settings/pages/roles/role.component.html
  90. 16
      src/Squidex/app/features/settings/pages/roles/role.component.ts
  91. 20
      src/Squidex/app/features/settings/pages/roles/roles-page.component.html
  92. 31
      src/Squidex/app/features/settings/pages/roles/roles-page.component.ts
  93. 29
      src/Squidex/app/features/settings/pages/workflows/schema-tag-converter.ts
  94. 60
      src/Squidex/app/features/settings/pages/workflows/workflow-add-form.component.ts
  95. 27
      src/Squidex/app/features/settings/pages/workflows/workflow-step.component.ts
  96. 2
      src/Squidex/app/features/settings/pages/workflows/workflow-transition.component.html
  97. 19
      src/Squidex/app/features/settings/pages/workflows/workflow-transition.component.ts
  98. 3
      src/Squidex/app/features/settings/pages/workflows/workflow.component.ts
  99. 20
      src/Squidex/app/features/settings/pages/workflows/workflows-page.component.html
  100. 50
      src/Squidex/app/features/settings/pages/workflows/workflows-page.component.ts

6
src/Squidex/app-config/webpack.config.js

@ -125,7 +125,11 @@ module.exports = function (env) {
use: [{
loader: 'raw-loader'
}, {
loader: 'sass-loader', options: { includePaths: [root('app', 'theme')] }
loader: 'sass-loader', options: {
sassOptions: {
includePaths: [root('app', 'theme')]
}
}
}],
exclude: root('app', 'theme')
}]

2
src/Squidex/app/app.module.ts

@ -55,7 +55,7 @@ export function configUIOptions() {
}
export function configTitles() {
return new TitlesConfig({}, undefined, 'Squidex Headless CMS');
return new TitlesConfig(undefined, 'Squidex Headless CMS');
}
export function configAnalyticsId() {

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

@ -10,8 +10,10 @@ export * from './administration-area.component';
export * from './guards/user-must-exist.guard';
export * from './guards/unset-user.guard';
export * from './pages/event-consumers/event-consumer.component';
export * from './pages/event-consumers/event-consumers-page.component';
export * from './pages/restore/restore-page.component';
export * from './pages/users/user.component';
export * from './pages/users/user-page.component';
export * from './pages/users/users-page.component';

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

@ -15,11 +15,13 @@ import {
import {
AdministrationAreaComponent,
EventConsumerComponent,
EventConsumersPageComponent,
EventConsumersService,
EventConsumersState,
RestorePageComponent,
UnsetUserGuard,
UserComponent,
UserMustExistGuard,
UserPageComponent,
UsersPageComponent,
@ -73,8 +75,10 @@ const routes: Routes = [
],
declarations: [
AdministrationAreaComponent,
EventConsumerComponent,
EventConsumersPageComponent,
RestorePageComponent,
UserComponent,
UserPageComponent,
UsersPageComponent
],

66
src/Squidex/app/features/administration/pages/event-consumers/event-consumer.component.ts

@ -0,0 +1,66 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: component-selector
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal';
@Component({
selector: '[sqxEventConsumer]',
template: `
<tr [class.faulted]="eventConsumer.error && eventConsumer.error?.length > 0">
<td class="auto-auto">
<span class="truncate">
<i class="faulted-icon icon icon-bug" (click)="error.emit()" [class.hidden]="!eventConsumer.error || eventConsumer.error?.length === 0"></i>
{{eventConsumer.name}}
</span>
</td>
<td class="cell-auto-right">
<span>{{eventConsumer.position}}</span>
</td>
<td class="cell-actions-lg">
<button type="button" class="btn btn-text" (click)="reset()" *ngIf="eventConsumer.canReset" title="Reset Event Consumer">
<i class="icon icon-reset"></i>
</button>
<button type="button" class="btn btn-text" (click)="start()" *ngIf="eventConsumer.canStart" title="Start Event Consumer">
<i class="icon icon-play"></i>
</button>
<button type="button" class="btn btn-text" (click)="stop()" *ngIf="eventConsumer.canStop" title="Stop Event Consumer">
<i class="icon icon-pause"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EventConsumerComponent {
@Output()
public error = new EventEmitter();
@Input('sqxEventConsumer')
public eventConsumer: EventConsumerDto;
constructor(
public readonly eventConsumersState: EventConsumersState
) {
}
public start() {
this.eventConsumersState.start(this.eventConsumer);
}
public stop() {
this.eventConsumersState.stop(this.eventConsumer);
}
public reset() {
this.eventConsumersState.reset(this.eventConsumer);
}
}

27
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -29,31 +29,8 @@
</tr>
</thead>
<tbody *ngFor="let eventConsumer of eventConsumersState.eventConsumers | async; trackBy: trackByEventConsumer">
<tr [class.faulted]="eventConsumer.error && eventConsumer.error.length > 0">
<td class="auto-auto">
<span class="truncate">
<i class="faulted-icon icon icon-bug" (click)="showError(eventConsumer)" [class.hidden]="!eventConsumer.error || eventConsumer.error.length === 0"></i>
{{eventConsumer.name}}
</span>
</td>
<td class="cell-auto-right">
<span>{{eventConsumer.position}}</span>
</td>
<td class="cell-actions-lg">
<button type="button" class="btn btn-text" (click)="reset(eventConsumer)" *ngIf="eventConsumer.canReset" title="Reset Event Consumer">
<i class="icon icon-reset"></i>
</button>
<button type="button" class="btn btn-text" (click)="start(eventConsumer)" *ngIf="eventConsumer.canStart" title="Start Event Consumer">
<i class="icon icon-play"></i>
</button>
<button type="button" class="btn btn-text" (click)="stop(eventConsumer)" *ngIf="eventConsumer.canStop" title="Stop Event Consumer">
<i class="icon icon-pause"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
<tbody *ngFor="let eventConsumer of eventConsumersState.eventConsumers | async; trackBy: trackByEventConsumer"
[sqxEventConsumer]="eventConsumer" (error)="showError(eventConsumer)">
</tbody>
</table>
</ng-container>

12
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -38,18 +38,6 @@ export class EventConsumersPageComponent extends ResourceOwner implements OnInit
this.eventConsumersState.load(true, false);
}
public start(eventConsumer: EventConsumerDto) {
this.eventConsumersState.start(eventConsumer);
}
public stop(eventConsumer: EventConsumerDto) {
this.eventConsumersState.stop(eventConsumer);
}
public reset(eventConsumer: EventConsumerDto) {
this.eventConsumersState.reset(eventConsumer);
}
public trackByEventConsumer(index: number, es: EventConsumerDto) {
return es.name;
}

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

@ -1,4 +1,3 @@
<sqx-title message="User Management"></sqx-title>
<form [formGroup]="userForm.form" (ngSubmit)="save()">
<input style="display:none" type="password" name="foilautofill"/>
@ -6,10 +5,14 @@
<sqx-panel desiredWidth="26rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
<ng-container *ngIf="usersState.selectedUser | async; else noUserTitle">
<sqx-title message="Edit User"></sqx-title>
Edit User
</ng-container>
<ng-template #noUserTitle>
<sqx-title message="New User"></sqx-title>
New User
</ng-template>
</ng-container>

55
src/Squidex/app/features/administration/pages/users/user.component.ts

@ -0,0 +1,55 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: component-selector
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { UserDto, UsersState } from '@app/features/administration/internal';
@Component({
selector: '[sqxUser]',
template: `
<tr [routerLink]="user.id" routerLinkActive="active">
<td class="cell-user">
<img class="user-picture" title="{{user.displayName}}" [attr.src]="user | sqxUserDtoPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell">{{user.displayName}}</span>
</td>
<td class="cell-auto">
<span class="user-email table-cell">{{user.email}}</span>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text" (click)="lock()" sqxStopClick *ngIf="user.canLock" title="Lock User">
<i class="icon icon-unlocked"></i>
</button>
<button type="button" class="btn btn-text" (click)="unlock()" sqxStopClick *ngIf="user.canUnlock" title="Unlock User">
<i class="icon icon-lock"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
@Input('sqxUser')
public user: UserDto;
constructor(
private readonly usersState: UsersState
) {
}
public lock() {
this.usersState.lock(this.user);
}
public unlock() {
this.usersState.unlock(this.user);
}
}

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

@ -51,27 +51,8 @@
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed" *ngIf="usersState.users | async; let users">
<tbody *ngFor="let user of users; trackBy: trackByUser">
<tr [routerLink]="user.id" routerLinkActive="active">
<td class="cell-user">
<img class="user-picture" title="{{user.name}}" [attr.src]="user | sqxUserDtoPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell">{{user.displayName}}</span>
</td>
<td class="cell-auto">
<span class="user-email table-cell">{{user.email}}</span>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text" (click)="lock(user)" sqxStopClick *ngIf="user.canLock" title="Lock User">
<i class="icon icon-unlocked"></i>
</button>
<button type="button" class="btn btn-text" (click)="unlock(user)" sqxStopClick *ngIf="user.canUnlock" title="Unlock User">
<i class="icon icon-lock"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
<tbody *ngFor="let user of users; trackBy: trackByUser"
[sqxUser]="user">
</tbody>
</table>
</div>

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

@ -43,16 +43,7 @@ export class UsersPageComponent implements OnInit {
this.usersState.goNext();
}
public lock(user: UserDto) {
this.usersState.lock(user);
}
public unlock(user: UserDto) {
this.usersState.unlock(user);
}
public trackByUser(index: number, user: UserDto) {
return user.id;
}
}
}

4
src/Squidex/app/features/administration/services/event-consumers.service.ts

@ -33,7 +33,7 @@ export class EventConsumerDto {
public readonly canStop: boolean;
public readonly canStart: boolean;
public readonly canRestart: boolean;
public readonly canReset: boolean;
constructor(links: ResourceLinks,
public readonly name: string,
@ -46,7 +46,7 @@ export class EventConsumerDto {
this.canStop = hasAnyLink(links, 'stop');
this.canStart = hasAnyLink(links, 'start');
this.canRestart = hasAnyLink(links, 'canReset');
this.canReset = hasAnyLink(links, 'reset');
}
}

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="API"></sqx-title>
<sqx-panel theme="dark" desiredWidth="12rem">
<ng-container title>

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]="appsState.appDisplayName"></sqx-title>
<sqx-title message="GraphQL"></sqx-title>
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true">
<div inner #graphiQLContainer></div>

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

@ -26,7 +26,7 @@ export class GraphQLPageComponent implements AfterViewInit {
public graphiQLContainer: ElementRef;
constructor(
public readonly appsState: AppsState,
private readonly appsState: AppsState,
private readonly graphQlService: GraphQlService
) {
}
@ -45,5 +45,4 @@ export class GraphQLPageComponent implements AfterViewInit {
private request(params: any) {
return this.graphQlService.query(this.appsState.appName, params).pipe(catchError(response => of(response.error))).toPromise();
}
}
}

6
src/Squidex/app/features/apps/pages/news-dialog.component.ts

@ -15,12 +15,12 @@ import { FeatureDto } from '@app/shared';
templateUrl: './news-dialog.component.html'
})
export class NewsDialogComponent {
@Input()
public features: FeatureDto[];
@Output()
public close = new EventEmitter();
@Input()
public features: FeatureDto[];
public emitClose() {
this.close.emit();
}

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Assets" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Assets"></sqx-title>
<sqx-panel desiredWidth="*" minWidth="50rem" showSidebar="true">
<ng-container title>

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

@ -9,7 +9,6 @@ import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import {
AppsState,
AssetsState,
LocalStoreService,
Queries,
@ -31,7 +30,6 @@ export class AssetsPageComponent extends ResourceOwner implements OnInit {
public isListView: boolean;
constructor(
public readonly appsState: AppsState,
public readonly assetsState: AssetsState,
private readonly localStore: LocalStoreService,
private readonly uiState: UIState
@ -74,5 +72,4 @@ export class AssetsPageComponent extends ResourceOwner implements OnInit {
this.localStore.setBoolean('squidex.assets.list-view', isListView);
}
}
}

2
src/Squidex/app/features/content/declarations.ts

@ -17,7 +17,7 @@ export * from './pages/schemas/schemas-page.component';
export * from './shared/array-editor.component';
export * from './shared/array-item.component';
export * from './shared/assets-editor.component';
export * from './shared/content-item.component';
export * from './shared/content.component';
export * from './shared/content-status.component';
export * from './shared/content-value.component';
export * from './shared/content-value-editor.component';

4
src/Squidex/app/features/content/module.ts

@ -25,9 +25,9 @@ import {
ArrayItemComponent,
AssetsEditorComponent,
CommentsPageComponent,
ContentComponent,
ContentFieldComponent,
ContentHistoryPageComponent,
ContentItemComponent,
ContentPageComponent,
ContentsFiltersPageComponent,
ContentsPageComponent,
@ -112,7 +112,7 @@ const routes: Routes = [
CommentsPageComponent,
ContentFieldComponent,
ContentHistoryPageComponent,
ContentItemComponent,
ContentComponent,
ContentPageComponent,
ContentsFiltersPageComponent,
ContentsPageComponent,

3
src/Squidex/app/features/content/pages/comments/comments-page.component.ts

@ -26,5 +26,4 @@ export class CommentsPageComponent implements OnInit {
public ngOnInit() {
this.commentsId = allParams(this.route)['contentId'];
}
}
}

9
src/Squidex/app/features/content/pages/content/content-field.component.ts

@ -30,6 +30,9 @@ import {
templateUrl: './content-field.component.html'
})
export class ContentFieldComponent implements DoCheck, OnChanges {
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@Input()
public form: EditContentForm;
@ -54,9 +57,6 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
@Input()
public languages: AppLanguageDto[];
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
public selectedFormControl: AbstractControl;
public selectedFormControlCompare?: AbstractControl;
@ -195,5 +195,4 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
private configKey() {
return `squidex.schemas.${this.schema.id}.fields.${this.field.fieldId}.show-all`;
}
}
}

14
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | {schema} | Content" parameter1="app" parameter2="schema" [value1]="appsState.appDisplayName" [value2]="schema.displayName"></sqx-title>
<sqx-title [message]="schema.displayName"></sqx-title>
<form [formGroup]="contentForm.form" (ngSubmit)="saveAndPublish()">
<sqx-panel desiredWidth="*" minWidth="60rem" [showSidebar]="content">
@ -7,11 +7,15 @@
<i class="icon-angle-left"></i>
</a>
<ng-container *ngIf="!content else notNewTitle">
New Content
<ng-container *ngIf="content else noContentTitle">
<sqx-title message="Edit Content"></sqx-title>
Edit Content
</ng-container>
<ng-template #notNewTitle>
Content
<ng-template #noContentTitle>
<sqx-title message="New Content"></sqx-title>
New Content
</ng-template>
</ng-container>

8
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -15,7 +15,6 @@ import { ContentVersionSelected } from './../messages';
import {
ApiUrlConfig,
AppLanguageDto,
AppsState,
AuthService,
AutoSaveKey,
AutoSaveService,
@ -70,7 +69,6 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
public dueTimeSelector: DueTimeSelectorComponent;
constructor(apiUrl: ApiUrlConfig, authService: AuthService,
public readonly appsState: AppsState,
public readonly contentsState: ContentsState,
private readonly autoSaveService: AutoSaveService,
private readonly dialogs: DialogService,
@ -100,11 +98,9 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
this.own(
this.schemasState.selectedSchema
.subscribe(schema => {
if (schema) {
this.schema = schema!;
this.schema = schema;
this.contentForm = new EditContentForm(this.languages, this.schema);
}
this.contentForm = new EditContentForm(this.languages, this.schema);
}));
this.own(

12
src/Squidex/app/features/content/pages/content/field-languages.component.ts

@ -32,6 +32,12 @@ import { AppLanguageDto, RootFieldDto } from '@app/shared';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FieldLanguagesComponent {
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@Output()
public showAllControlsChange = new EventEmitter<boolean>();
@Input()
public field: RootFieldDto;
@ -43,10 +49,4 @@ export class FieldLanguagesComponent {
@Input()
public languages: AppLanguageDto[];
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@Output()
public showAllControlsChange = new EventEmitter<boolean>();
}

4
src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts

@ -37,9 +37,7 @@ export class ContentsFiltersPageComponent extends ResourceOwner implements OnIni
this.own(
this.schemasState.selectedSchema
.subscribe(schema => {
if (schema) {
this.schemaQueries = new Queries(this.uiState, `schemas.${schema.name}`);
}
this.schemaQueries = new Queries(this.uiState, `schemas.${schema.name}`);
}));
}

29
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | {schema} | Contents" parameter1="app" parameter2="schema" [value1]="appsState.appDisplayName" [value2]="schema.displayName"></sqx-title>
<sqx-title [message]="schema.displayName"></sqx-title>
<sqx-panel desiredWidth="*" minWidth="50rem" contentClass="grid" showSidebar="true">
<ng-container title>
@ -91,21 +91,18 @@
<div class="grid-content" [sqxSyncScrolling]="header">
<div class="table-container" sqxIgnoreScrollbar>
<table class="table table-items table-fixed" [style.minWidth]="minWidth">
<tbody *ngFor="let content of contentsState.contents | async; trackBy: trackByContent">
<tr [sqxContent]="content"
(delete)="delete(content)"
[selected]="isItemSelected(content)"
(selectedChange)="selectItem(content, $event)"
(statusChange)="changeStatus(content, $event)"
(clone)="clone(content)"
[language]="language"
[canClone]="contentsState.snapshot.canCreate"
[routerLink]="[content.id]"
[routerLinkActive]="'active'"
[schema]="schema"
[schemaFields]="schema.listFields">
</tr>
<tr class="spacer"></tr>
<tbody *ngFor="let content of contentsState.contents | async; trackBy: trackByContent"
[sqxContent]="content"
(delete)="delete(content)"
[selected]="isItemSelected(content)"
(selectedChange)="selectItem(content, $event)"
(statusChange)="changeStatus(content, $event)"
(clone)="clone(content)"
[link]="[content.id]"
[language]="language"
[canClone]="contentsState.snapshot.canCreate"
[schema]="schema"
[schemaFields]="schema.listFields">
</tbody>
</table>
</div>

7
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -10,7 +10,6 @@ import { onErrorResumeNext, switchMap, tap } from 'rxjs/operators';
import {
AppLanguageDto,
AppsState,
ContentDto,
ContentsState,
ImmutableArray,
@ -57,7 +56,6 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
public dueTimeSelector: DueTimeSelectorComponent;
constructor(
public readonly appsState: AppsState,
public readonly contentsState: ContentsState,
private readonly languagesState: LanguagesState,
private readonly schemasState: SchemasState,
@ -72,7 +70,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
.subscribe(schema => {
this.resetSelection();
this.schema = schema!;
this.schema = schema;
this.minWidth = `${300 + (200 * this.schema.listFields.length)}px`;
@ -239,5 +237,4 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
this.queryModel = queryModelFromSchema(this.schema, this.languages.values, this.contentsState.snapshot.statuses);
}
}
}
}

2
src/Squidex/app/features/content/pages/schemas/schemas-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Contents"></sqx-title>
<sqx-panel theme="dark" desiredWidth="16rem" showSecondHeader="true">
<ng-container title>

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

@ -8,11 +8,7 @@
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import {
AppsState,
SchemaCategory,
SchemasState
} from '@app/shared';
import { SchemaCategory, SchemasState } from '@app/shared';
@Component({
selector: 'sqx-schemas-page',
@ -23,7 +19,6 @@ export class SchemasPageComponent implements OnInit {
public schemasFilter = new FormControl();
constructor(
public readonly appsState: AppsState,
public readonly schemasState: SchemasState
) {
}
@ -35,5 +30,4 @@ export class SchemasPageComponent implements OnInit {
public trackByCategory(index: number, category: SchemaCategory) {
return category.name;
}
}
}

8
src/Squidex/app/features/content/shared/assets-editor.component.html

@ -27,8 +27,8 @@
(load)="addAsset(file, $event)">
</sqx-asset>
<sqx-asset *ngFor="let asset of snapshot.assets; trackBy: trackByAsset" [asset]="asset" [isCompact]="isCompact" removeMode="true"
(update)="notifyOthers($event)"
(remove)="removeLoadedAsset($event)">
(update)="notifyOthers(asset)"
(remove)="removeLoadedAsset(asset)">
</sqx-asset>
</div>
</ng-container>
@ -48,8 +48,8 @@
<sqx-asset [asset]="asset" removeMode="true"
[isListView]="true"
[isCompact]="isCompact"
(update)="notifyOthers($event)"
(remove)="removeLoadedAsset($event)">
(update)="notifyOthers(asset)"
(remove)="removeLoadedAsset(asset)">
</sqx-asset>
</div>
</div>

98
src/Squidex/app/features/content/shared/content-item.component.html

@ -1,98 +0,0 @@
<td class="cell-select" sqxStopClick>
<ng-container *ngIf="!isReference; else referenceTemplate">
<input type="checkbox" class="form-check"
[disabled]="!selectable"
[ngModel]="selected || !selectable"
(ngModelChange)="selectedChange.emit($event)" />
</ng-container>
<ng-template #referenceTemplate>
<i class="icon-drag2 drag-handle"></i>
</ng-template>
</td>
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick>
<div class="dropdown dropdown-options" *ngIf="content">
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade>
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)">
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}}
</a>
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone">
Clone
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="emitDelete()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
</a>
</div>
</ng-container>
</div>
</td>
<ng-container *ngIf="isDirty">
<td class="cell-actions" >
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick>
<i class="icon-close"></i>
</button>
</td>
<td class="cell-user" >
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick>
<i class="icon-checkmark"></i>
</button>
</td>
</ng-container>
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)">
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor>
</ng-container>
<ng-template #displayTemplate>
<sqx-content-value [value]="values[i]"></sqx-content-value>
</ng-template>
</td>
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td>
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty">
<div class="reference-edit">
<button type="button" class="btn btn-text-secondary">
<i class="icon-dots"></i>
</button>
<div class="reference-menu">
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]">
<i class="icon-pencil"></i>
</a>
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()">
<i class="icon-close"></i>
</button>
</div>
</div>
</td>

3
src/Squidex/app/features/content/shared/content-status.component.ts

@ -49,5 +49,4 @@ export class ContentStatusComponent {
return this.status;
}
}
}
}

3
src/Squidex/app/features/content/shared/content-value.component.ts

@ -17,8 +17,7 @@ import { HtmlValue, Types } from '@app/shared';
</ng-container>
<ng-template #html>
<span class="truncate" [innerHTML]="value.html"></span>
</ng-template>
`,
</ng-template>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentValueComponent {

101
src/Squidex/app/features/content/shared/content.component.html

@ -0,0 +1,101 @@
<tr [routerLink]="link" routerLinkActive="active">
<td class="cell-select" sqxStopClick>
<ng-container *ngIf="!isReference; else referenceTemplate">
<input type="checkbox" class="form-check"
[disabled]="!selectable"
[ngModel]="selected || !selectable"
(ngModelChange)="selectedChange.emit($event)" />
</ng-container>
<ng-template #referenceTemplate>
<i class="icon-drag2 drag-handle"></i>
</ng-template>
</td>
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick>
<div class="dropdown dropdown-options" *ngIf="content">
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade>
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)">
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}}
</a>
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone">
Clone
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="emitDelete()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
</a>
</div>
</ng-container>
</div>
</td>
<ng-container *ngIf="isDirty">
<td class="cell-actions" >
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick>
<i class="icon-close"></i>
</button>
</td>
<td class="cell-user" >
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick>
<i class="icon-checkmark"></i>
</button>
</td>
</ng-container>
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)">
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor>
</ng-container>
<ng-template #displayTemplate>
<sqx-content-value [value]="values[i]"></sqx-content-value>
</ng-template>
</td>
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td>
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty">
<div class="reference-edit">
<button type="button" class="btn btn-text-secondary">
<i class="icon-dots"></i>
</button>
<div class="reference-menu">
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]">
<i class="icon-pencil"></i>
</a>
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()">
<i class="icon-close"></i>
</button>
</div>
</div>
</td>
</tr>
<tr class="spacer"></tr>

0
src/Squidex/app/features/content/shared/content-item.component.scss → src/Squidex/app/features/content/shared/content.component.scss

12
src/Squidex/app/features/content/shared/content-item.component.ts → src/Squidex/app/features/content/shared/content.component.ts

@ -24,14 +24,14 @@ import {
@Component({
selector: '[sqxContent]',
styleUrls: ['./content-item.component.scss'],
templateUrl: './content-item.component.html',
styleUrls: ['./content.component.scss'],
templateUrl: './content.component.html',
animations: [
fadeAnimation
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentItemComponent implements OnChanges {
export class ContentComponent implements OnChanges {
@Output()
public clone = new EventEmitter();
@ -71,6 +71,9 @@ export class ContentItemComponent implements OnChanges {
@Input()
public isCompact = false;
@Input()
public link: any = null;
@Input('sqxContent')
public content: ContentDto;
@ -170,5 +173,4 @@ export class ContentItemComponent implements OnChanges {
public trackByField(field: FieldDto) {
return field.fieldId + this.schema.id;
}
}
}

19
src/Squidex/app/features/content/shared/contents-selector.component.html

@ -62,16 +62,15 @@
<div class="grid-content" [sqxSyncScrolling]="header">
<div class="table-container" sqxIgnoreScrollbar>
<table class="table table-items table-fixed" [style.minWidth]="minWidth" *ngIf="contentsState.contents | async; let contents">
<tbody *ngFor="let content of contents; trackBy: trackByContent">
<tr [sqxContent]="content"
[selectable]="!isItemAlreadySelected(content)"
[selected]="isItemSelected(content)"
(selectedChange)="selectContent(content)"
[language]="language"
[schema]="schema"
[schemaFields]="schema.referenceFields"
isReadOnly="true"></tr>
<tr class="spacer"></tr>
<tbody *ngFor="let content of contents; trackBy: trackByContent"
[sqxContent]="content"
[selectable]="!isItemAlreadySelected(content)"
[selected]="isItemSelected(content)"
(selectedChange)="selectContent(content)"
[language]="language"
[schema]="schema"
[schemaFields]="schema.referenceFields"
isReadOnly="true">
</tbody>
</table>
</div>

9
src/Squidex/app/features/content/shared/contents-selector.component.ts

@ -27,6 +27,9 @@ import {
]
})
export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
@Output()
public select = new EventEmitter<ContentDto[]>();
@Input()
public language: LanguageDto;
@ -42,9 +45,6 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
@Input()
public schema: SchemaDetailsDto;
@Output()
public select = new EventEmitter<ContentDto[]>();
public queryModel: QueryModel;
public selectedItems: { [id: string]: ContentDto; } = {};
@ -142,5 +142,4 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
public trackByContent(content: ContentDto): string {
return content.id;
}
}
}

3
src/Squidex/app/features/content/shared/due-time-selector.component.ts

@ -48,5 +48,4 @@ export class DueTimeSelectorComponent {
this.dueTimeFunction = null!;
this.dueTime = null;
}
}
}

19
src/Squidex/app/features/content/shared/references-editor.component.html

@ -9,16 +9,15 @@
<table class="table table-items table-fixed" [class.disabled]="snapshot.isDisabled" *ngIf="schema && snapshot.contentItems && snapshot.contentItems.length > 0"
[sqxSortModel]="snapshot.contentItems.mutableValues"
(sqxSort)="sort($event)">
<tbody *ngFor="let content of snapshot.contentItems; trackBy: trackByContent">
<tr [sqxContent]="content"
[language]="language"
[isReadOnly]="true"
[isReference]="true"
[isCompact]="isCompact"
[schema]="schema"
[schemaFields]="schema.referenceFields"
(delete)="remove(content)"></tr>
<tr class="spacer"></tr>
<tbody *ngFor="let content of snapshot.contentItems; trackBy: trackByContent"
[sqxContent]="content"
[language]="language"
[isReadOnly]="true"
[isReference]="true"
[isCompact]="isCompact"
[schema]="schema"
[schemaFields]="schema.referenceFields"
(delete)="remove(content)">
</tbody>
</table>
</ng-container>

4
src/Squidex/app/features/dashboard/pages/dashboard-page.component.html

@ -1,6 +1,6 @@
<ng-container *ngIf="app | async; let app">
<sqx-title message="{app} | Dashboard" parameter1="app" [value1]="app.displayName"></sqx-title>
<sqx-title message="Dashboard"></sqx-title>
<ng-container *ngIf="appsState.selectedApp | async; let app">
<div class="dashboard" @fade>
<div class="dashboard-inner">
<div>

15
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -51,8 +51,6 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
public isPerformanceStacked = false;
public app = this.appsState.selectedValidApp;
public chartOptions = {
responsive: true,
scales: {
@ -106,7 +104,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
public ngOnInit() {
this.own(
this.app.pipe(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getTodayStorage(app.name)))
.subscribe(dto => {
this.assetsCurrent = dto.size;
@ -114,7 +112,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
}));
this.own(
this.app.pipe(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getMonthCalls(app.name)))
.subscribe(dto => {
this.callsCurrent = dto.count;
@ -122,14 +120,14 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
}));
this.own(
this.app.pipe(
this.appsState.selectedApp.pipe(
switchMap(app => this.historyService.getHistory(app.name, '')))
.subscribe(dto => {
this.history = dto;
}));
this.own(
this.app.pipe(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getStorageUsages(app.name, DateTime.today().addDays(-20), DateTime.today())))
.subscribe(dtos => {
const labels = createLabels(dtos);
@ -166,7 +164,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
}));
this.own(
this.app.pipe(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getCallsUsages(app.name, DateTime.today().addDays(-20), DateTime.today())))
.subscribe(dtos => {
const labels = createLabelsFromSet(dtos);
@ -215,5 +213,4 @@ function createLabels(dtos: { date: DateTime }[]): string[] {
function createLabelsFromSet(dtos: { [category: string]: { date: DateTime }[] }): string[] {
return createLabels(dtos[Object.keys(dtos)[0]]);
}
}

1
src/Squidex/app/features/rules/declarations.ts

@ -12,6 +12,7 @@ export * from './pages/rules/triggers/content-changed-trigger.component';
export * from './pages/rules/triggers/schema-changed-trigger.component';
export * from './pages/rules/triggers/usage-trigger.component';
export * from './pages/rules/rule.component';
export * from './pages/rules/rule-element.component';
export * from './pages/rules/rule-wizard.component';
export * from './pages/rules/rules-page.component';

2
src/Squidex/app/features/rules/module.ts

@ -18,6 +18,7 @@ import {
AssetChangedTriggerComponent,
ContentChangedTriggerComponent,
GenericActionComponent,
RuleComponent,
RuleElementComponent,
RuleEventBadgeClassPipe,
RuleEventsPageComponent,
@ -57,6 +58,7 @@ const routes: Routes = [
AssetChangedTriggerComponent,
ContentChangedTriggerComponent,
GenericActionComponent,
RuleComponent,
RuleElementComponent,
RuleEventBadgeClassPipe,
RuleEventsPageComponent,

2
src/Squidex/app/features/rules/pages/events/rule-events-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Rules Events" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Events"></sqx-title>
<sqx-panel desiredWidth="63rem">
<ng-container title>

5
src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts

@ -8,7 +8,6 @@
import { Component, OnInit } from '@angular/core';
import {
AppsState,
RuleEventDto,
RuleEventsState
} from '@app/shared';
@ -22,7 +21,6 @@ export class RuleEventsPageComponent implements OnInit {
public selectedEventId: string | null = null;
constructor(
public readonly appsState: AppsState,
public readonly ruleEventsState: RuleEventsState
) {
}
@ -58,5 +56,4 @@ export class RuleEventsPageComponent implements OnInit {
public trackByRuleEvent(index: number, ruleEvent: RuleEventDto) {
return ruleEvent.id;
}
}
}

24
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts

@ -27,18 +27,6 @@ export const MODE_EDIT_ACTION = 'EditAction';
templateUrl: './rule-wizard.component.html'
})
export class RuleWizardComponent implements AfterViewInit, OnInit {
public actionForm = new Form<FormGroup, any>(new FormGroup({}));
public actionType: string;
public action: any = {};
public triggerForm = new Form<FormGroup, any>(new FormGroup({}));
public triggerType: string;
public trigger: any = {};
public isEditable: boolean;
public step = 1;
@Output()
public complete = new EventEmitter();
@ -57,6 +45,18 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
@Input()
public mode = MODE_WIZARD;
public actionForm = new Form<FormGroup, any>(new FormGroup({}));
public actionType: string;
public action: any = {};
public triggerForm = new Form<FormGroup, any>(new FormGroup({}));
public triggerType: string;
public trigger: any = {};
public isEditable: boolean;
public step = 1;
constructor(
private readonly rulesState: RulesState
) {

87
src/Squidex/app/features/rules/pages/rules/rule.component.ts

@ -0,0 +1,87 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: component-selector
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import {
ActionsDto,
RuleDto,
RulesState,
TriggersDto
} from '@app/shared';
@Component({
selector: '[sqxRule]',
template: `
<tr>
<td class="cell-separator">
<h3>If</h3>
</td>
<td class="cell-auto">
<span (click)="editTrigger.emit()">
<sqx-rule-element [type]="rule.triggerType" [element]="ruleTriggers[rule.triggerType]"></sqx-rule-element>
</span>
</td>
<td class="cell-separator">
<h3>then</h3>
</td>
<td class="cell-auto">
<span (click)="editAction.emit()">
<sqx-rule-element [type]="rule.actionType" [element]="ruleActions[rule.actionType]"></sqx-rule-element>
</span>
</td>
<td class="cell-actions">
<sqx-toggle [disabled]="!rule.canDisable && !rule.canEnable" [ngModel]="rule.isEnabled" (ngModelChange)="toggle()"></sqx-toggle>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text-danger"
[disabled]="!rule.canDelete"
(sqxConfirmClick)="delete()"
confirmTitle="Delete rule"
confirmText="Do you really want to delete the rule?">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RuleComponent {
@Output()
public editTrigger = new EventEmitter();
@Output()
public editAction = new EventEmitter();
@Input()
public ruleTriggers: TriggersDto;
@Input()
public ruleActions: ActionsDto;
@Input('sqxRule')
public rule: RuleDto;
constructor(
private readonly rulesState: RulesState
) {
}
public delete() {
this.rulesState.delete(this.rule);
}
public toggle() {
if (this.rule.isEnabled) {
this.rulesState.disable(this.rule);
} else {
this.rulesState.enable(this.rule);
}
}
}

42
src/Squidex/app/features/rules/pages/rules/rules-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Rules" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Rules"></sqx-title>
<sqx-panel desiredWidth="54rem" showSidebar="true">
<ng-container title>
@ -32,38 +32,14 @@
</div>
<table class="table table-items table-fixed">
<tbody *ngFor="let rule of rules; trackBy: trackByRule">
<tr>
<td class="cell-separator">
<h3>If</h3>
</td>
<td class="cell-auto">
<span (click)="editTrigger(rule)">
<sqx-rule-element [type]="rule.triggerType" [element]="ruleTriggers[rule.triggerType]"></sqx-rule-element>
</span>
</td>
<td class="cell-separator">
<h3>then</h3>
</td>
<td class="cell-auto">
<span (click)="editAction(rule)">
<sqx-rule-element [type]="rule.actionType" [element]="ruleActions[rule.actionType]"></sqx-rule-element>
</span>
</td>
<td class="cell-actions">
<sqx-toggle [disabled]="!rule.canDisable && !rule.canEnable" [ngModel]="rule.isEnabled" (ngModelChange)="toggle(rule)"></sqx-toggle>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text-danger"
[disabled]="!rule.canDelete"
(sqxConfirmClick)="delete(rule)"
confirmTitle="Delete rule"
confirmText="Do you really want to delete the rule?">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
<tbody *ngFor="let rule of rules; trackBy: trackByRule"
[sqxRule]="rule"
[ruleActions]="ruleActions"
[ruleTriggers]="ruleTriggers"
(delete)="delete(rule)"
(editAction)="editAction(rule)"
(editTrigger)="editTrigger(rule)"
(toggle)="toggle(rule)">
</tbody>
</table>

2
src/Squidex/app/features/rules/pages/rules/rules-page.component.ts

@ -9,7 +9,6 @@ import { Component, OnInit } from '@angular/core';
import {
ALL_TRIGGERS,
AppsState,
DialogModel,
RuleDto,
RuleElementDto,
@ -33,7 +32,6 @@ export class RulesPageComponent implements OnInit {
public ruleTriggers = ALL_TRIGGERS;
constructor(
public readonly appsState: AppsState,
public readonly rulesState: RulesState,
public readonly rulesService: RulesService,
public readonly schemasState: SchemasState

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

@ -131,5 +131,4 @@ export class FieldComponent implements OnChanges {
public trackByField(index: number, field: NestedFieldDto) {
return field.fieldId + this.schema.id;
}
}
}

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | {schema}" parameter1="app" [value1]="appsState.appDisplayName" parameter2="schema" [value2]="schemasState.schemaName"></sqx-title>
<sqx-title [message]="schemasState.schemaName"></sqx-title>
<sqx-panel desiredWidth="60rem" [showSidebar]="true">
<ng-container title>

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

@ -11,7 +11,6 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
AppsState,
DialogModel,
fadeAnimation,
FieldDto,
@ -51,7 +50,6 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
public trackByFieldFn: Function;
constructor(
public readonly appsState: AppsState,
public readonly schemasState: SchemasState,
public readonly patternsState: PatternsState,
private readonly route: ActivatedRoute,
@ -69,9 +67,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
this.own(
this.schemasState.selectedSchema
.subscribe(schema => {
if (schema) {
this.schema = schema;
}
this.schema = schema;
}));
}
@ -105,5 +101,4 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
private back() {
this.router.navigate(['../'], { relativeTo: this.route });
}
}
}

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Schemas"></sqx-title>
<sqx-panel theme="dark" desiredWidth="30rem" showSecondHeader="true">
<ng-container title>

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

@ -11,7 +11,6 @@ import { ActivatedRoute, Router } from '@angular/router';
import { map } from 'rxjs/operators';
import {
AppsState,
CreateCategoryForm,
DialogModel,
MessageBus,
@ -37,7 +36,6 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit {
public import: any;
constructor(
public readonly appsState: AppsState,
public readonly schemasState: SchemasState,
private readonly formBuilder: FormBuilder,
private readonly messageBus: MessageBus,
@ -98,5 +96,4 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit {
public trackByCategory(index: number, category: SchemaCategory) {
return category.name;
}
}
}

9
src/Squidex/app/features/settings/declarations.ts

@ -5,20 +5,27 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/backups/backup.component';
export * from './pages/backups/backups-page.component';
export * from './pages/backups/pipes';
export * from './pages/clients/client-add-form.component';
export * from './pages/clients/client.component';
export * from './pages/clients/clients-page.component';
export * from './pages/contributors/contributor-add-form.component';
export * from './pages/contributors/contributor.component';
export * from './pages/contributors/contributors-page.component';
export * from './pages/contributors/import-contributors-dialog.component';
export * from './pages/languages/language-add-form.component';
export * from './pages/languages/language.component';
export * from './pages/languages/languages-page.component';
export * from './pages/more/more-page.component';
export * from './pages/patterns/pattern.component';
export * from './pages/patterns/patterns-page.component';
export * from './pages/plans/plan.component';
export * from './pages/plans/plans-page.component';
export * from './pages/roles/role-add-form.component';
export * from './pages/roles/role.component';
export * from './pages/roles/roles-page.component';
export * from './pages/workflows/workflow-add-form.component';
export * from './pages/workflows/workflow-step.component';
export * from './pages/workflows/workflow-transition.component';
export * from './pages/workflows/workflow.component';

22
src/Squidex/app/features/settings/module.ts

@ -16,21 +16,28 @@ import {
} from '@app/shared';
import {
BackupDurationPipe,
BackupComponent,
BackupsPageComponent,
ClientAddFormComponent,
ClientComponent,
ClientsPageComponent,
ContributorAddFormComponent,
ContributorComponent,
ContributorsPageComponent,
ImportContributorsDialogComponent,
LanguageAddFormComponent,
LanguageComponent,
LanguagesPageComponent,
MorePageComponent,
PatternComponent,
PatternsPageComponent,
PlanComponent,
PlansPageComponent,
RoleAddFormComponent,
RoleComponent,
RolesPageComponent,
SettingsAreaComponent,
WorkflowAddFormComponent,
WorkflowComponent,
WorkflowsPageComponent,
WorkflowStepComponent,
@ -196,25 +203,32 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
BackupDurationPipe,
BackupComponent,
BackupsPageComponent,
ClientAddFormComponent,
ClientComponent,
ClientsPageComponent,
ContributorAddFormComponent,
ContributorComponent,
ContributorsPageComponent,
ImportContributorsDialogComponent,
LanguageAddFormComponent,
LanguageComponent,
LanguagesPageComponent,
MorePageComponent,
PatternComponent,
PatternsPageComponent,
PlanComponent,
PlansPageComponent,
RoleAddFormComponent,
RoleComponent,
RolesPageComponent,
SettingsAreaComponent,
WorkflowAddFormComponent,
WorkflowComponent,
WorkflowsPageComponent,
WorkflowTransitionComponent,
WorkflowStepComponent
WorkflowStepComponent,
WorkflowTransitionComponent
]
})
export class SqxFeatureSettingsModule {}

87
src/Squidex/app/features/settings/pages/backups/backup.component.ts

@ -0,0 +1,87 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import {
ApiUrlConfig,
BackupDto,
BackupsState,
Duration
} from '@app/shared';
@Component({
selector: 'sqx-backup',
template: `
<div class="table-items-row">
<div class="row">
<div class="col-auto" [ngSwitch]="backup.status">
<sqx-status-icon size="lg" [status]="backup.status"></sqx-status-icon>
</div>
<div class="col-auto">
<div>
Started:
</div>
<div>
Duration:
</div>
</div>
<div class="col-3">
<div>
{{backup.started | sqxFromNow}}
</div>
<div *ngIf="backup.stopped">
{{duration}}
</div>
</div>
<div class="col">
<div>
<span title="Archived events">
Events: <strong class="backup-progress">{{backup.handledEvents | sqxKNumber}}</strong>
</span>,
<span title="Archived assets">
Assets: <strong class="backup-progress">{{backup.handledAssets | sqxKNumber}}</strong>
</span>
</div>
<div *ngIf="backup.stopped && !backup.isFailed">
Download:
<a href="{{apiUrl.buildUrl(backup.downloadUrl)}}" sqxExternalLink="noicon">
Ready <i class="icon-external-link"></i>
</a>
</div>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-danger mt-1"
[disabled]="!backup.canDelete"
(sqxConfirmClick)="delete()"
confirmTitle="Delete backup"
confirmText="Do you really want to delete the backup?">
<i class="icon-bin2"></i>
</button>
</div>
</div>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BackupComponent {
@Input()
public backup: BackupDto;
public get duration() {
return Duration.create(this.backup.started, this.backup.stopped!).toString();
}
constructor(
public readonly apiUrl: ApiUrlConfig, private readonly backupsState: BackupsState
) {
}
public delete() {
this.backupsState.delete(this.backup);
}
}

54
src/Squidex/app/features/settings/pages/backups/backups-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Backups | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Backups"></sqx-title>
<sqx-panel desiredWidth="50rem" [showSidebar]="true">
<ng-container title>
@ -30,56 +30,10 @@
Start Backup
</button>
</div>
<div class="table-items-row" *ngFor="let backup of backups; trackBy: trackByBackup">
<div class="row">
<div class="col-auto" [ngSwitch]="backup.status">
<sqx-status-icon size="lg" [status]="backup.status"></sqx-status-icon>
</div>
<div class="col-auto">
<div>
Started:
</div>
<div>
Duration:
</div>
</div>
<div class="col-3">
<div>
{{backup.started | sqxFromNow}}
</div>
<div *ngIf="backup.stopped">
{{backup | sqxBackupDuration}}
</div>
</div>
<div class="col">
<div>
<span title="Archived events">
Events: <strong class="backup-progress">{{backup.handledEvents | sqxKNumber}}</strong>
</span>,
<span title="Archived assets">
Assets: <strong class="backup-progress">{{backup.handledAssets | sqxKNumber}}</strong>
</span>
</div>
<div *ngIf="backup.stopped && !backup.isFailed">
Download:
<a href="{{apiUrl.buildUrl(backup.downloadUrl)}}" sqxExternalLink="noicon">
Ready <i class="icon-external-link"></i>
</a>
</div>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-danger mt-1"
[disabled]="!backup.canDelete"
(sqxConfirmClick)="delete(backup)"
confirmTitle="Delete backup"
confirmText="Do you really want to delete the backup?">
<i class="icon-bin2"></i>
</button>
</div>
</div>
</div>
<sqx-backup *ngFor="let backup of backups; trackBy: trackByBackup"
[backup]="backup">
</sqx-backup>
</ng-container>
</ng-container>

9
src/Squidex/app/features/settings/pages/backups/backups-page.component.ts

@ -11,7 +11,6 @@ import { onErrorResumeNext, switchMap } from 'rxjs/operators';
import {
ApiUrlConfig,
AppsState,
BackupDto,
BackupsState,
ResourceOwner
@ -25,7 +24,6 @@ import {
export class BackupsPageComponent extends ResourceOwner implements OnInit {
constructor(
public readonly apiUrl: ApiUrlConfig,
public readonly appsState: AppsState,
public readonly backupsState: BackupsState
) {
super();
@ -47,12 +45,7 @@ export class BackupsPageComponent extends ResourceOwner implements OnInit {
this.backupsState.start();
}
public delete(backup: BackupDto) {
this.backupsState.delete(backup);
}
public trackByBackup(index: number, item: BackupDto) {
return item.id;
}
}
}

20
src/Squidex/app/features/settings/pages/backups/pipes.ts

@ -1,20 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Pipe, PipeTransform } from '@angular/core';
import { BackupDto, Duration } from '@app/shared';
@Pipe({
name: 'sqxBackupDuration',
pure: true
})
export class BackupDurationPipe implements PipeTransform {
public transform(backup: BackupDto) {
return Duration.create(backup.started, backup.stopped!).toString();
}
}

60
src/Squidex/app/features/settings/pages/clients/client-add-form.component.ts

@ -0,0 +1,60 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { AddClientForm, ClientsState } from '@app/shared';
@Component({
selector: 'sqx-client-add-form',
template: `
<div class="table-items-footer">
<form [formGroup]="addClientForm.form" (ngSubmit)="addClient()">
<div class="row no-gutters">
<div class="col">
<sqx-control-errors for="name" [submitted]="addClientForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter client name" autocomplete="off" sqxTransformInput="LowerCase" />
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="addClientForm.hasNoName | async">Add Client</button>
</div>
<div class="col-auto pl-1">
<button type="reset" class="btn btn-secondary" (click)="cancel()">Cancel</button>
</div>
</div>
</form>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ClientAddFormComponent {
public addClientForm = new AddClientForm(this.formBuilder);
constructor(
private readonly clientsState: ClientsState,
private readonly formBuilder: FormBuilder
) {
}
public addClient() {
const value = this.addClientForm.submit();
if (value) {
this.clientsState.attach({ id: value.name })
.subscribe(() => {
this.addClientForm.submitCompleted();
}, error => {
this.addClientForm.submitFailed(error);
});
}
}
public cancel() {
this.addClientForm.submitCompleted();
}
}

9
src/Squidex/app/features/settings/pages/clients/client.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import {
AccessTokenDto,
@ -16,13 +16,18 @@ import {
ClientsState,
DialogModel,
DialogService,
fadeAnimation,
RoleDto
} from '@app/shared';
@Component({
selector: 'sqx-client',
styleUrls: ['./client.component.scss'],
templateUrl: './client.component.html'
templateUrl: './client.component.html',
animations: [
fadeAnimation
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ClientComponent implements OnChanges {
@Input()

20
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Clients | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Clients"></sqx-title>
<sqx-panel desiredWidth="50rem" [showSidebar]="true">
<ng-container title>
@ -26,23 +26,7 @@
</sqx-client>
</ng-container>
<div class="table-items-footer" *ngIf="clientsState.canCreate | async">
<form [formGroup]="addClientForm.form" (ngSubmit)="attachClient()">
<div class="row no-gutters">
<div class="col">
<sqx-control-errors for="name" [submitted]="addClientForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter client name" autocomplete="off" sqxTransformInput="LowerCase" />
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="addClientForm.hasNoName | async">Add Client</button>
</div>
<div class="col-auto pl-1">
<button type="reset" class="btn btn-secondary" (click)="cancelAttachClient()">Cancel</button>
</div>
</div>
</form>
</div>
<sqx-client-add-form *ngIf="clientsState.canCreate | async"></sqx-client-add-form>
</ng-container>
</ng-container>

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

@ -6,11 +6,8 @@
*/
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
AddClientForm,
AppsState,
ClientDto,
ClientsState,
RolesState
@ -22,13 +19,9 @@ import {
templateUrl: './clients-page.component.html'
})
export class ClientsPageComponent implements OnInit {
public addClientForm = new AddClientForm(this.formBuilder);
constructor(
public readonly appsState: AppsState,
public readonly clientsState: ClientsState,
public readonly rolesState: RolesState,
private readonly formBuilder: FormBuilder
public readonly rolesState: RolesState
) {
}
@ -42,24 +35,7 @@ export class ClientsPageComponent implements OnInit {
this.clientsState.load(true);
}
public attachClient() {
const value = this.addClientForm.submit();
if (value) {
this.clientsState.attach({ id: value.name })
.subscribe(() => {
this.addClientForm.submitCompleted();
}, error => {
this.addClientForm.submitFailed(error);
});
}
}
public cancelAttachClient() {
this.addClientForm.submitCompleted();
}
public trackByClient(index: number, item: ClientDto) {
public trackByClient(item: ClientDto) {
return item.id;
}
}

24
src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.html

@ -0,0 +1,24 @@
<div class="table-items-header" style="margin: 0">
<sqx-form-alert marginTop="0" marginBottom="2" light="true">
Just enter the email address to invite someone with no account to the app.
</sqx-form-alert>
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters">
<div class="col">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="displayName">
<ng-template let-user="$implicit">
<span class="autocomplete-user">
<img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" />
<span class="user-name autocomplete-user-name">{{user.displayName}}</span>
</span>
</ng-template>
</sqx-autocomplete>
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="assignContributorForm.hasNoUser | async">Add Contributor</button>
</div>
</div>
</form>
</div>

16
src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.scss

@ -0,0 +1,16 @@
@import '_vars';
@import '_mixins';
.autocomplete-user {
& {
@include truncate;
}
&-name {
margin-left: .25rem;
}
}
.table-items-header {
margin: 0;
}

84
src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.ts

@ -0,0 +1,84 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Injectable } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';
import {
AssignContributorForm,
AutocompleteSource,
ContributorsState,
DialogModel,
DialogService,
UsersService
} from '@app/shared';
@Injectable()
export class UsersDataSource implements AutocompleteSource {
constructor(
private readonly contributorsState: ContributorsState,
private readonly usersService: UsersService
) {
}
public find(query: string): Observable<any[]> {
return this.usersService.getUsers(query).pipe(
withLatestFrom(this.contributorsState.contributors, (users, contributors) => {
const results: any[] = [];
for (let user of users) {
if (!contributors!.find(t => t.contributorId === user.id)) {
results.push(user);
}
}
return results;
}));
}
}
@Component({
selector: 'sqx-contributor-add-form',
styleUrls: ['./contributor-add-form.component.scss'],
templateUrl: './contributor-add-form.component.html',
providers: [
UsersDataSource
]
})
export class ContributorAddFormComponent {
public assignContributorForm = new AssignContributorForm(this.formBuilder);
public importDialog = new DialogModel();
constructor(
public readonly contributorsState: ContributorsState,
public readonly usersDataSource: UsersDataSource,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder
) {
}
public assignContributor() {
const value = this.assignContributorForm.submit();
if (value) {
this.contributorsState.assign(value)
.subscribe(isCreated => {
this.assignContributorForm.submitCompleted();
if (isCreated) {
this.dialogs.notifyInfo('A new user with the entered email address has been created and assigned as contributor.');
} else {
this.dialogs.notifyInfo('User has been added as contributor.');
}
}, error => {
this.assignContributorForm.submitFailed(error);
});
}
}
}

67
src/Squidex/app/features/settings/pages/contributors/contributor.component.ts

@ -0,0 +1,67 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: component-selector
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import {
ContributorDto,
ContributorsState,
RoleDto
} from '@app/shared';
@Component({
selector: '[sqxContributor]',
template: `
<tr>
<td class="cell-user">
<img class="user-picture" title="{{contributor.contributorName}}" [attr.src]="contributor.contributorId | sqxUserPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell" [innerHTML]="contributor.contributorName | sqxHighlight:search"></span>
</td>
<td class="cell-time">
<select class="form-control"
[ngModel]="contributor.role"
(ngModelChange)="changeRole($event)"
[disabled]="!contributor.canUpdate">
<option *ngFor="let role of roles" [ngValue]="role.name">{{role.name}}</option>
</select>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text-danger" [disabled]="!contributor.canRevoke" (click)="remove()">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContributorComponent {
@Input()
public roles: RoleDto[];
@Input()
public search: string;
@Input('sqxContributor')
public contributor: ContributorDto;
constructor(
private readonly contributorsState: ContributorsState
) {
}
public remove() {
this.contributorsState.revoke(this.contributor);
}
public changeRole(role: string) {
this.contributorsState.assign({ contributorId: this.contributor.contributorId, role });
}
}

59
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Contributors | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Contributors"></sqx-title>
<sqx-panel desiredWidth="50rem" [showSidebar]="true">
<ng-container title>
@ -15,7 +15,8 @@
<div class="form-inline">
<input class="form-control" placeholder="Search"
[ngModel]="contributorsState.query | async"
(ngModelChange)="search($event)" />
(ngModelChange)="search($event)"
/>
</div>
</ng-container>
@ -29,30 +30,7 @@
<ng-container *ngIf="contributorsState.contributorsPaged | async; let contributors">
<ng-container *ngIf="contributorsState.canCreate | async">
<div class="table-items-header">
<sqx-form-alert marginTop="0" marginBottom="2" light="true">
Just enter the email address to invite someone with no account to the app.
</sqx-form-alert>
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters">
<div class="col">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="displayName">
<ng-template let-user="$implicit">
<span class="autocomplete-user">
<img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" />
<span class="user-name autocomplete-user-name">{{user.displayName}}</span>
</span>
</ng-template>
</sqx-autocomplete>
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="assignContributorForm.hasNoUser | async">Add Contributor</button>
</div>
</div>
</form>
</div>
<sqx-contributor-add-form></sqx-contributor-add-form>
<div class="import-hint">
<sqx-form-hint class="text-right">
@ -63,33 +41,14 @@
<ng-container *ngIf="contributors.length > 0; else noContributors">
<table class="table table-items table-fixed" *ngIf="rolesState.roles | async; let roles">
<tbody *ngFor="let contributor of contributors; trackBy: trackByContributor">
<tr>
<td class="cell-user">
<img class="user-picture" title="{{contributor.contributorName}}" [attr.src]="contributor.contributorId | sqxUserPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell" [innerHTML]="contributor.contributorName | sqxHighlight:contributorsState.snapshot.queryRegex"></span>
</td>
<td class="cell-time">
<select class="form-control"
[ngModel]="contributor.role"
(ngModelChange)="changeRole(contributor, $event)"
[disabled]="!contributor.canUpdate">
<option *ngFor="let role of roles" [ngValue]="role.name">{{role.name}}</option>
</select>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text-danger" [disabled]="!contributor.canRevoke" (click)="remove(contributor)">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
<tbody *ngFor="let contributor of contributors; trackBy: trackByContributor"
[sqxContributor]="contributor"
[search]="contributorsState.snapshot.queryRegex"
[roles]="roles">
</tbody>
</table>
<sqx-pager [pager]="contributorsState.contributorsPager | async" [hideWhenButtonsDisabled]="true" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
<sqx-pager [pager]="contributorsState.contributorsPager | async" [autoHide]="true" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</ng-container>
<ng-template #noContributors>

14
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss

@ -1,22 +1,8 @@
@import '_vars';
@import '_mixins';
.autocomplete-user {
& {
@include truncate;
}
&-name {
margin-left: .25rem;
}
}
.import-hint {
margin-bottom: 1rem;
font-weight: normal;
font-size: 90%;
}
.table-items-header {
margin: 0;
}

77
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -5,66 +5,26 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Injectable, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import {
AppsState,
AssignContributorForm,
AutocompleteSource,
ContributorDto,
ContributorsState,
DialogModel,
DialogService,
RolesState,
UsersService
RolesState
} from '@app/shared';
@Injectable()
export class UsersDataSource implements AutocompleteSource {
constructor(
private readonly contributorsState: ContributorsState,
private readonly usersService: UsersService
) {
}
public find(query: string): Observable<any[]> {
return this.usersService.getUsers(query).pipe(
withLatestFrom(this.contributorsState.contributors, (users, contributors) => {
const results: any[] = [];
for (let user of users) {
if (!contributors!.find(t => t.contributorId === user.id)) {
results.push(user);
}
}
return results;
}));
}
}
@Component({
selector: 'sqx-contributors-page',
styleUrls: ['./contributors-page.component.scss'],
templateUrl: './contributors-page.component.html',
providers: [
UsersDataSource
]
templateUrl: './contributors-page.component.html'
})
export class ContributorsPageComponent implements OnInit {
public assignContributorForm = new AssignContributorForm(this.formBuilder);
public importDialog = new DialogModel();
constructor(
public readonly appsState: AppsState,
public readonly contributorsState: ContributorsState,
public readonly rolesState: RolesState,
public readonly usersDataSource: UsersDataSource,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder
public readonly rolesState: RolesState
) {
}
@ -90,34 +50,7 @@ export class ContributorsPageComponent implements OnInit {
this.contributorsState.search(query);
}
public remove(contributor: ContributorDto) {
this.contributorsState.revoke(contributor);
}
public changeRole(contributor: ContributorDto, role: string) {
this.contributorsState.assign({ contributorId: contributor.contributorId, role });
}
public assignContributor() {
const value = this.assignContributorForm.submit();
if (value) {
this.contributorsState.assign(value)
.subscribe(isCreated => {
this.assignContributorForm.submitCompleted();
if (isCreated) {
this.dialogs.notifyInfo('A new user with the entered email address has been created and assigned as contributor.');
} else {
this.dialogs.notifyInfo('User has been added as contributor.');
}
}, error => {
this.assignContributorForm.submitFailed(error);
});
}
}
public trackByContributor(index: number, contributor: ContributorDto) {
public trackByContributor(contributor: ContributorDto) {
return contributor.contributorId;
}
}

69
src/Squidex/app/features/settings/pages/languages/language-add-form.component.ts

@ -0,0 +1,69 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
AddLanguageForm,
ImmutableArray,
LanguageDto,
LanguagesState
} from '@app/shared';
@Component({
selector: 'sqx-language-add-form',
template: `
<div class="table-items-footer">
<form [formGroup]="addLanguageForm.form" (ngSubmit)="addLanguage()">
<div class="row no-gutters">
<div class="col">
<select class="form-control" formControlName="language">
<option *ngFor="let language of newLanguages" [ngValue]="language">{{language.englishName}}</option>
</select>
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success">Add Language</button>
</div>
</div>
</form>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LanguageAddFormComponent implements OnChanges {
@Input()
public newLanguages: ImmutableArray<LanguageDto>;
public addLanguageForm = new AddLanguageForm(this.formBuilder);
constructor(
private readonly languagesState: LanguagesState,
private readonly formBuilder: FormBuilder
) {
}
public ngOnChanges() {
if (this.newLanguages.length > 0) {
const language = this.newLanguages.at(0);
this.addLanguageForm.load({ language });
}
}
public addLanguage() {
const value = this.addLanguageForm.submit();
if (value) {
this.languagesState.add(value.language)
.subscribe(() => {
this.addLanguageForm.submitCompleted();
}, error => {
this.addLanguageForm.submitFailed(error);
});
}
}
}

3
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -102,5 +102,4 @@ export class LanguageComponent implements OnChanges {
public trackByLanguage(index: number, language: AppLanguageDto) {
return language.iso2Code;
}
}
}

21
src/Squidex/app/features/settings/pages/languages/languages-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Languages | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Languages"></sqx-title>
<sqx-panel desiredWidth="50rem" [showSidebar]="true">
<ng-container title>
@ -21,22 +21,9 @@
[fallbackLanguagesNew]="languageInfo.fallbackLanguagesNew">
</sqx-language>
<ng-container *ngIf="languagesState.newLanguages | async; let newLanguages">
<div class="table-items-footer" *ngIf="languagesState.canCreate | async">
<form [formGroup]="addLanguageForm.form" (ngSubmit)="addLanguage()">
<div class="row no-gutters">
<div class="col">
<select class="form-control" formControlName="language">
<option *ngFor="let language of newLanguages" [ngValue]="language">{{language.englishName}}</option>
</select>
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success">Add Language</button>
</div>
</div>
</form>
</div>
</ng-container>
<sqx-language-add-form *ngIf="languagesState.canCreate | async"
[newLanguages]="languagesState.newLanguages | async">
</sqx-language-add-form>
</ng-container>
</ng-container>

42
src/Squidex/app/features/settings/pages/languages/languages-page.component.ts

@ -6,41 +6,21 @@
*/
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
AddLanguageForm,
AppLanguageDto,
AppsState,
LanguagesState,
ResourceOwner
} from '@app/shared';
import { AppLanguageDto, LanguagesState } from '@app/shared';
@Component({
selector: 'sqx-languages-page',
styleUrls: ['./languages-page.component.scss'],
templateUrl: './languages-page.component.html'
})
export class LanguagesPageComponent extends ResourceOwner implements OnInit {
public addLanguageForm = new AddLanguageForm(this.formBuilder);
export class LanguagesPageComponent implements OnInit {
constructor(
public readonly appsState: AppsState,
public readonly languagesState: LanguagesState,
private readonly formBuilder: FormBuilder
public readonly languagesState: LanguagesState
) {
super();
}
public ngOnInit() {
this.own(
this.languagesState.newLanguages
.subscribe(languages => {
if (languages.length > 0) {
this.addLanguageForm.load({ language: languages.at(0) });
}
}));
this.languagesState.load();
}
@ -48,21 +28,7 @@ export class LanguagesPageComponent extends ResourceOwner implements OnInit {
this.languagesState.load(true);
}
public addLanguage() {
const value = this.addLanguageForm.submit();
if (value) {
this.languagesState.add(value.language)
.subscribe(() => {
this.addLanguageForm.submitCompleted();
}, error => {
this.addLanguageForm.submitFailed(error);
});
}
}
public trackByLanguage(index: number, language: { language: AppLanguageDto }) {
return language.language.iso2Code;
}
}
}

2
src/Squidex/app/features/settings/pages/more/more-page.component.html

@ -1,5 +1,3 @@
<sqx-title message="{app} | More | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-panel desiredWidth="46rem" [showClose]="false">
<ng-container title>
Settings

19
src/Squidex/app/features/settings/pages/more/more-page.component.ts

@ -35,7 +35,7 @@ export class MorePageComponent extends ResourceOwner implements OnInit {
public updateForm = new UpdateAppForm(this.formBuilder);
constructor(
public readonly appsState: AppsState,
private readonly appsState: AppsState,
private readonly formBuilder: FormBuilder,
private readonly router: Router
) {
@ -46,16 +46,14 @@ export class MorePageComponent extends ResourceOwner implements OnInit {
this.own(
this.appsState.selectedApp
.subscribe(app => {
if (app) {
this.app = app;
this.app = app;
this.isDeletable = app.canDelete;
this.isEditable = app.canUpdateGeneral;
this.isImageEditable = app.canUpdateImage;
this.isDeletable = app.canDelete;
this.isEditable = app.canUpdateGeneral;
this.isImageEditable = app.canUpdateImage;
this.updateForm.load(app);
this.updateForm.setEnabled(this.isEditable);
}
this.updateForm.load(app);
this.updateForm.setEnabled(this.isEditable);
}));
}
@ -114,5 +112,4 @@ export class MorePageComponent extends ResourceOwner implements OnInit {
this.router.navigate(['/app']);
});
}
}
}

12
src/Squidex/app/features/settings/pages/patterns/pattern.component.ts

@ -5,11 +5,12 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
EditPatternForm,
fadeAnimation,
PatternDto,
PatternsState
} from '@app/shared';
@ -17,7 +18,11 @@ import {
@Component({
selector: 'sqx-pattern',
styleUrls: ['./pattern.component.scss'],
templateUrl: './pattern.component.html'
templateUrl: './pattern.component.html',
animations: [
fadeAnimation
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PatternComponent implements OnChanges {
@Input()
@ -77,5 +82,4 @@ export class PatternComponent implements OnChanges {
}
}
}
}
}

2
src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Patterns | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Patterns"></sqx-title>
<sqx-panel desiredWidth="63rem" [showSidebar]="true">
<ng-container title>

2
src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts

@ -8,7 +8,6 @@
import { Component, OnInit } from '@angular/core';
import {
AppsState,
PatternDto,
PatternsState
} from '@app/shared';
@ -20,7 +19,6 @@ import {
})
export class PatternsPageComponent implements OnInit {
constructor(
public readonly appsState: AppsState,
public readonly patternsState: PatternsState
) {
}

44
src/Squidex/app/features/settings/pages/plans/plan.component.html

@ -0,0 +1,44 @@
<div class="card plan float-left">
<div class="card-header text-center">
<h4 class="card-title">{{planInfo.plan.name}}</h4>
<h5 class="plan-price">{{planInfo.plan.costs}}</h5>
<small class="text-muted">Per Month</small>
</div>
<div class="card-body">
<div class="plan-fact text-center">
<div>
<strong>{{planInfo.plan.maxApiCalls | sqxKNumber}}</strong> API Calls
</div>
<div>
{{planInfo.plan.maxAssetSize | sqxFileSize}} Storage
</div>
<div>
{{planInfo.plan.maxContributors}} Contributors
</div>
</div>
<button *ngIf="planInfo.isSelected" class="btn btn-block btn-text-success plan-selected">
&#10003; Selected
</button>
<button *ngIf="!planInfo.isSelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="changeMonthly()">
Change
</button>
</div>
<div class="card-footer" *ngIf="planInfo.plan.yearlyId">
<div class="text-center">
<h5 class="plan-price">{{planInfo.plan.yearlyCosts}}</h5>
<small class="text-muted">Per Year</small>
</div>
<button *ngIf="planInfo.isYearlySelected" class="btn btn-block btn-text-success plan-selected">
&#10003; Selected
</button>
<button *ngIf="!planInfo.isYearlySelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="changeYearly()">
Change
</button>
</div>
</div>

34
src/Squidex/app/features/settings/pages/plans/plan.component.scss

@ -0,0 +1,34 @@
@import '_vars';
@import '_mixins';
.plan {
& {
min-width: 13rem;
max-width: 20rem;
margin: .5rem;
}
&-price {
color: $color-theme-blue;
margin-top: 0;
margin-bottom: 0;
}
&-selected {
pointer-events: none;
}
&-fact {
line-height: 1.8rem;
}
.btn {
margin-top: 1rem;
}
}
.card-footer,
.card-header,
.card-body {
padding: 1rem;
}

41
src/Squidex/app/features/settings/pages/plans/plan.component.ts

@ -0,0 +1,41 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import {
fadeAnimation,
PlanInfo,
PlansState
} from '@app/shared';
@Component({
selector: 'sqx-plan',
styleUrls: ['./plan.component.scss'],
templateUrl: './plan.component.html',
animations: [
fadeAnimation
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PlanComponent {
@Input()
public planInfo: PlanInfo;
constructor(
public readonly plansState: PlansState
) {
}
public changeMonthly() {
this.plansState.change(this.planInfo.plan.id);
}
public changeYearly() {
this.plansState.change(this.planInfo.plan.yearlyId);
}
}

51
src/Squidex/app/features/settings/pages/plans/plans-page.component.html

@ -1,8 +1,8 @@
<sqx-title message="{app} | Plans | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Subscription"></sqx-title>
<sqx-panel desiredWidth="64rem" [showSidebar]="true" [scrollX]="true">
<ng-container title>
Update Plan
Subscription
</ng-container>
<ng-container menu>
@ -24,50 +24,9 @@
</div>
<div class="clearfix">
<div class="card plan float-left" *ngFor="let planInfo of plans; trackBy: trackByPlan">
<div class="card-header text-center">
<h4 class="card-title">{{planInfo.plan.name}}</h4>
<h5 class="plan-price">{{planInfo.plan.costs}}</h5>
<small class="text-muted">Per Month</small>
</div>
<div class="card-body">
<div class="plan-fact text-center">
<div>
<strong>{{planInfo.plan.maxApiCalls | sqxKNumber}}</strong> API Calls
</div>
<div>
{{planInfo.plan.maxAssetSize | sqxFileSize}} Storage
</div>
<div>
{{planInfo.plan.maxContributors}} Contributors
</div>
</div>
<button *ngIf="planInfo.isSelected" class="btn btn-block btn-text-success plan-selected">
&#10003; Selected
</button>
<button *ngIf="!planInfo.isSelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="change(planInfo.plan.id)">
Change
</button>
</div>
<div class="card-footer" *ngIf="planInfo.plan.yearlyId">
<div class="text-center">
<h5 class="plan-price">{{planInfo.plan.yearlyCosts}}</h5>
<small class="text-muted">Per Year</small>
</div>
<button *ngIf="planInfo.isYearlySelected" class="btn btn-block btn-text-success plan-selected">
&#10003; Selected
</button>
<button *ngIf="!planInfo.isYearlySelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="change(planInfo.plan.yearlyId)">
Change
</button>
</div>
</div>
<sqx-plan *ngFor="let planInfo of plans; trackBy: trackByPlan"
[planInfo]="planInfo">
</sqx-plan>
</div>
<div *ngIf="plansState.hasPortal | async" class="billing-portal-link">

16
src/Squidex/app/features/settings/pages/plans/plans-page.component.ts

@ -10,7 +10,6 @@ import { ActivatedRoute } from '@angular/router';
import {
ApiUrlConfig,
AppsState,
PlanDto,
PlansState
} from '@app/shared';
@ -26,7 +25,6 @@ export class PlansPageComponent implements OnInit {
public portalUrl = this.apiUrl.buildUrl('/portal/');
constructor(
public readonly appsState: AppsState,
public readonly plansState: PlansState,
private readonly apiUrl: ApiUrlConfig,
private readonly route: ActivatedRoute
@ -34,9 +32,10 @@ export class PlansPageComponent implements OnInit {
}
public ngOnInit() {
this.route.queryParams.subscribe(params => {
this.overridePlanId = params['planId'];
}).unsubscribe();
this.route.queryParams
.subscribe(params => {
this.overridePlanId = params['planId'];
}).unsubscribe();
this.plansState.load(false, this.overridePlanId);
}
@ -45,12 +44,7 @@ export class PlansPageComponent implements OnInit {
this.plansState.load(true, this.overridePlanId);
}
public change(planId: string) {
this.plansState.change(planId);
}
public trackByPlan(index: number, planInfo: { plan: PlanDto }) {
return planInfo.plan.id;
}
}
}

60
src/Squidex/app/features/settings/pages/roles/role-add-form.component.ts

@ -0,0 +1,60 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { AddRoleForm, RolesState } from '@app/shared';
@Component({
selector: 'sqx-role-add-form',
template: `
<div class="table-items-footer">
<form [formGroup]="addRoleForm.form" (ngSubmit)="addRole()">
<div class="row no-gutters">
<div class="col">
<sqx-control-errors for="name" [submitted]="addRoleForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter role name" autocomplete="off" />
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="addRoleForm.hasNoName | async">Add role</button>
</div>
<div class="col-auto pl-1">
<button type="reset" class="btn btn-secondary" (click)="cancel()">Cancel</button>
</div>
</div>
</form>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RoleAddFormComponent {
public addRoleForm = new AddRoleForm(this.formBuilder);
constructor(
private readonly rolesState: RolesState,
private readonly formBuilder: FormBuilder
) {
}
public addRole() {
const value = this.addRoleForm.submit();
if (value) {
this.rolesState.add(value)
.subscribe(() => {
this.addRoleForm.submitCompleted();
}, error => {
this.addRoleForm.submitFailed(error);
});
}
}
public cancel() {
this.addRoleForm.submitCompleted();
}
}

2
src/Squidex/app/features/settings/pages/roles/role.component.html

@ -18,7 +18,7 @@
<button type="button" class="btn btn-text-danger"
[disabled]="!role.canDelete"
(sqxConfirmClick)="remove()"
(sqxConfirmClick)="delete()"
confirmTitle="Delete role"
confirmText="Do you really want to delete the role?">
<i class="icon-bin2"></i>

16
src/Squidex/app/features/settings/pages/roles/role.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnChanges, ViewChild } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
@ -24,7 +24,8 @@ import {
templateUrl: './role.component.html',
animations: [
fadeAnimation
]
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RoleComponent implements OnChanges {
@Input()
@ -60,12 +61,12 @@ export class RoleComponent implements OnChanges {
this.isEditing = !this.isEditing;
}
public removePermission(index: number) {
this.editForm.remove(index);
public delete() {
this.rolesState.delete(this.role);
}
public remove() {
this.rolesState.delete(this.role);
public removePermission(index: number) {
this.editForm.remove(index);
}
public addPermission() {
@ -95,5 +96,4 @@ export class RoleComponent implements OnChanges {
});
}
}
}
}

20
src/Squidex/app/features/settings/pages/roles/roles-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Roles | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Roles"></sqx-title>
<sqx-panel desiredWidth="50rem" [showSidebar]="true">
<ng-container title>
@ -17,23 +17,7 @@
<ng-container *ngIf="(rolesState.isLoaded | async) && (rolesState.roles | async); let roles">
<sqx-role *ngFor="let role of roles" [role]="role" [allPermissions]="allPermissions"></sqx-role>
<div class="table-items-footer" *ngIf="rolesState.canCreate | async">
<form [formGroup]="addRoleForm.form" (ngSubmit)="addRole()">
<div class="row no-gutters">
<div class="col">
<sqx-control-errors for="name" [submitted]="addRoleForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter role name" autocomplete="off" />
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="addRoleForm.hasNoName | async">Add role</button>
</div>
<div class="col-auto pl-1">
<button type="reset" class="btn btn-secondary" (click)="cancelAddRole()">Cancel</button>
</div>
</div>
</form>
</div>
<sqx-role-add-form *ngIf="rolesState.canCreate | async"></sqx-role-add-form>
</ng-container>
</ng-container>

31
src/Squidex/app/features/settings/pages/roles/roles-page.component.ts

@ -6,11 +6,9 @@
*/
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Observable, of } from 'rxjs';
import {
AddRoleForm,
AppsState,
AutocompleteSource,
RoleDto,
@ -36,15 +34,12 @@ class PermissionsAutocomplete implements AutocompleteSource {
templateUrl: './roles-page.component.html'
})
export class RolesPageComponent implements OnInit {
public addRoleForm = new AddRoleForm(this.formBuilder);
public allPermissions: AutocompleteSource = new PermissionsAutocomplete(this.appsState, this.rolesService);
constructor(
public readonly appsState: AppsState,
private readonly appsState: AppsState,
public readonly rolesService: RolesService,
public readonly rolesState: RolesState,
private readonly formBuilder: FormBuilder
public readonly rolesState: RolesState
) {
}
@ -56,25 +51,7 @@ export class RolesPageComponent implements OnInit {
this.rolesState.load(true);
}
public cancelAddRole() {
this.addRoleForm.submitCompleted();
}
public addRole() {
const value = this.addRoleForm.submit();
if (value) {
this.rolesState.add(value)
.subscribe(() => {
this.addRoleForm.submitCompleted();
}, error => {
this.addRoleForm.submitFailed(error);
});
}
}
public trackByRole(index: number, role: RoleDto) {
public trackByRole(role: RoleDto) {
return role.name;
}
}
}

29
src/Squidex/app/features/settings/pages/workflows/schema-tag-converter.ts

@ -5,15 +5,36 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Converter, SchemaDto, TagValue } from '@app/shared';
import { Subscription } from 'rxjs';
import {
Converter,
SchemaDto,
SchemasState,
TagValue
} from '@app/shared';
export class SchemaTagConverter implements Converter {
public readonly suggestions: TagValue[];
private schemasSubscription: Subscription;
private schemas: SchemaDto[] = [];
public suggestions: TagValue[] = [];
constructor(
private readonly schemas: SchemaDto[]
readonly schemasState: SchemasState
) {
this.suggestions = schemas.map(x => new TagValue(x.id, x.name, x.id));
this.schemasSubscription =
schemasState.changes.subscribe(state => {
if (state.isLoaded) {
this.schemas = state.schemas.values;
this.suggestions = this.schemas.map(x => new TagValue(x.id, x.name, x.id));
}
});
}
public destroy() {
this.schemasSubscription.unsubscribe();
}
public convertInput(input: string): TagValue<any> | null {

60
src/Squidex/app/features/settings/pages/workflows/workflow-add-form.component.ts

@ -0,0 +1,60 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { AddWorkflowForm, WorkflowsState } from '@app/shared';
@Component({
selector: 'sqx-workflow-add-form',
template: `
<div class="table-items-footer">
<form [formGroup]="addWorkflowForm.form" (ngSubmit)="addWorkflow()">
<div class="row no-gutters">
<div class="col">
<sqx-control-errors for="name" [submitted]="addWorkflowForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter workflow name" autocomplete="off" />
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="addWorkflowForm.hasNoName | async">Add Workflow</button>
</div>
<div class="col-auto pl-1">
<button type="reset" class="btn btn-secondary" (click)="cancel()">Cancel</button>
</div>
</div>
</form>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class WorkflowAddFormComponent {
public addWorkflowForm = new AddWorkflowForm(this.formBuilder);
constructor(
private readonly workflowsState: WorkflowsState,
private readonly formBuilder: FormBuilder
) {
}
public addWorkflow() {
const value = this.addWorkflowForm.submit();
if (value) {
this.workflowsState.add(value.name)
.subscribe(() => {
this.addWorkflowForm.submitCompleted();
}, error => {
this.addWorkflowForm.submitFailed(error);
});
}
}
public cancel() {
this.addWorkflowForm.submitCompleted();
}
}

27
src/Squidex/app/features/settings/pages/workflows/workflow-step.component.ts

@ -23,18 +23,6 @@ import {
templateUrl: './workflow-step.component.html'
})
export class WorkflowStepComponent implements OnChanges {
@Input()
public workflow: WorkflowDto;
@Input()
public step: WorkflowStep;
@Input()
public roles: RoleDto[];
@Input()
public disabled: boolean;
@Output()
public makeInitial = new EventEmitter();
@ -56,6 +44,18 @@ export class WorkflowStepComponent implements OnChanges {
@Output()
public remove = new EventEmitter();
@Input()
public workflow: WorkflowDto;
@Input()
public step: WorkflowStep;
@Input()
public roles: RoleDto[];
@Input()
public disabled: boolean;
public onBlur = { updateOn: 'blur' };
public openSteps: WorkflowStep[];
@ -91,5 +91,4 @@ export class WorkflowStepComponent implements OnChanges {
public trackByTransition(index: number, transition: WorkflowTransition) {
return transition.to;
}
}
}

2
src/Squidex/app/features/settings/pages/workflows/workflow-transition.component.html

@ -33,7 +33,7 @@
<span class="select-placeholder" *ngIf="!transition.role || transition.role === ''">Add Role</span>
</div>
<div class="col-auto pl-2">
<button type="button" class="btn btn-text-danger" (click)="remove.emit()" [disabled]="disabled">
<button type="button" class="btn btn-text-danger" (click)="emitRemove()" [disabled]="disabled">
<i class="icon-bin2"></i>
</button>
</div>

19
src/Squidex/app/features/settings/pages/workflows/workflow-transition.component.ts

@ -19,6 +19,12 @@ import {
templateUrl: './workflow-transition.component.html'
})
export class WorkflowTransitionComponent {
@Output()
public update = new EventEmitter<WorkflowTransitionValues>();
@Output()
public remove = new EventEmitter();
@Input()
public transition: WorkflowTransitionView;
@ -28,12 +34,6 @@ export class WorkflowTransitionComponent {
@Input()
public disabled: boolean;
@Output()
public update = new EventEmitter<WorkflowTransitionValues>();
@Output()
public remove = new EventEmitter();
public onBlur = { updateOn: 'blur' };
public changeExpression(expression: string) {
@ -44,8 +44,11 @@ export class WorkflowTransitionComponent {
this.update.emit({ role: role || '' });
}
public emitRemove() {
this.remove.emit();
}
public trackByRole(index: number, role: RoleDto) {
return role.name;
}
}
}

3
src/Squidex/app/features/settings/pages/workflows/workflow.component.ts

@ -125,5 +125,4 @@ export class WorkflowComponent implements OnChanges {
public trackByStep(step: WorkflowStep) {
return step.name;
}
}
}

20
src/Squidex/app/features/settings/pages/workflows/workflows-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | Workflows | Settings" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title>
<sqx-title message="Workflows"></sqx-title>
<sqx-panel desiredWidth="60rem" [showSidebar]="true" [isLazyLoaded]="false">
<ng-container title>
@ -35,23 +35,7 @@
[workflow]="workflow" [roles]="roles" [schemasSource]="schemasSource">
</sqx-workflow>
<div class="table-items-footer" *ngIf="workflowsState.canCreate | async">
<form [formGroup]="addWorkflowForm.form" (ngSubmit)="addWorkflow()">
<div class="row no-gutters">
<div class="col">
<sqx-control-errors for="name" [submitted]="addWorkflowForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter workflow name" autocomplete="off" />
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="addWorkflowForm.hasNoName | async">Add Workflow</button>
</div>
<div class="col-auto pl-1">
<button type="reset" class="btn btn-secondary" (click)="cancelAddWorkflow()">Cancel</button>
</div>
</div>
</form>
</div>
<sqx-workflow-add-form *ngIf="workflowsState.canCreate | async"></sqx-workflow-add-form>
</ng-container>
</ng-container>
</ng-container>

50
src/Squidex/app/features/settings/pages/workflows/workflows-page.component.ts

@ -5,13 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Component, OnDestroy, OnInit } from '@angular/core';
import {
AddWorkflowForm,
AppsState,
ResourceOwner,
RolesState,
SchemasState,
WorkflowDto,
@ -25,56 +21,34 @@ import { SchemaTagConverter } from './schema-tag-converter';
styleUrls: ['./workflows-page.component.scss'],
templateUrl: './workflows-page.component.html'
})
export class WorkflowsPageComponent extends ResourceOwner implements OnInit {
public addWorkflowForm = new AddWorkflowForm(this.formBuilder);
export class WorkflowsPageComponent implements OnInit, OnDestroy {
public schemasSource: SchemaTagConverter;
constructor(
public readonly appsState: AppsState,
public readonly rolesState: RolesState,
public readonly schemasState: SchemasState,
public readonly workflowsState: WorkflowsState,
private readonly formBuilder: FormBuilder
public readonly workflowsState: WorkflowsState
) {
super();
}
public ngOnInit() {
this.own(this.schemasState.changes.subscribe(s => {
if (s.isLoaded) {
this.schemasSource = new SchemaTagConverter(s.schemas.values);
}
}));
this.rolesState.load();
this.schemasSource = new SchemaTagConverter(this.schemasState);
this.schemasState.load();
this.workflowsState.load();
}
public reload() {
this.workflowsState.load(true);
this.workflowsState.load();
}
public addWorkflow() {
const value = this.addWorkflowForm.submit();
if (value) {
this.workflowsState.add(value.name)
.subscribe(() => {
this.addWorkflowForm.submitCompleted();
}, error => {
this.addWorkflowForm.submitFailed(error);
});
}
public ngOnDestroy() {
this.schemasSource.destroy();
}
public cancelAddWorkflow() {
this.addWorkflowForm.submitCompleted();
public reload() {
this.workflowsState.load(true);
}
public trackByWorkflow(index: number, workflow: WorkflowDto) {
public trackByWorkflow(workflow: WorkflowDto) {
return workflow.id;
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save