Browse Source

Queryies in UI (#476)

* Queryies in UI

* Temp

* Separate templates and scss files.

* Smaller folders

* More files moved.

* Restructured

* Default imports for css

* Add headers

* Code simplified

* Preparation for Angular 9

* Compile fix.
pull/479/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
c912277116
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs
  2. 12
      frontend/app-config/webpack.config.js
  3. 2
      frontend/app/app.component.scss
  4. 2
      frontend/app/features/administration/administration-area.component.scss
  5. 3
      frontend/app/features/administration/pages/cluster/cluster-page.component.scss
  6. 24
      frontend/app/features/administration/pages/event-consumers/event-consumer.component.html
  7. 0
      frontend/app/features/administration/pages/event-consumers/event-consumer.component.scss
  8. 28
      frontend/app/features/administration/pages/event-consumers/event-consumer.component.ts
  9. 3
      frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.scss
  10. 3
      frontend/app/features/administration/pages/restore/restore-page.component.scss
  11. 3
      frontend/app/features/administration/pages/users/user-page.component.scss
  12. 20
      frontend/app/features/administration/pages/users/user.component.html
  13. 0
      frontend/app/features/administration/pages/users/user.component.scss
  14. 24
      frontend/app/features/administration/pages/users/user.component.ts
  15. 2
      frontend/app/features/administration/pages/users/users-page.component.scss
  16. 7
      frontend/app/features/administration/state/users.forms.ts
  17. 3
      frontend/app/features/api/api-area.component.scss
  18. 3
      frontend/app/features/api/pages/graphql/graphql-page.component.scss
  19. 3
      frontend/app/features/apps/pages/apps-page.component.scss
  20. 2
      frontend/app/features/apps/pages/news-dialog.component.html
  21. 3
      frontend/app/features/apps/pages/news-dialog.component.scss
  22. 4
      frontend/app/features/apps/pages/news-dialog.component.ts
  23. 2
      frontend/app/features/apps/pages/onboarding-dialog.component.html
  24. 3
      frontend/app/features/apps/pages/onboarding-dialog.component.scss
  25. 4
      frontend/app/features/apps/pages/onboarding-dialog.component.ts
  26. 1
      frontend/app/features/assets/declarations.ts
  27. 6
      frontend/app/features/assets/module.ts
  28. 18
      frontend/app/features/assets/pages/asset-tags.component.html
  29. 0
      frontend/app/features/assets/pages/asset-tags.component.scss
  30. 42
      frontend/app/features/assets/pages/asset-tags.component.ts
  31. 29
      frontend/app/features/assets/pages/assets-filters-page.component.html
  32. 2
      frontend/app/features/assets/pages/assets-filters-page.component.scss
  33. 5
      frontend/app/features/assets/pages/assets-filters-page.component.ts
  34. 3
      frontend/app/features/assets/pages/assets-page.component.scss
  35. 33
      frontend/app/features/content/declarations.ts
  36. 8
      frontend/app/features/content/module.ts
  37. 2
      frontend/app/features/content/pages/comments/comments-page.component.scss
  38. 4
      frontend/app/features/content/pages/content/content-field.component.html
  39. 3
      frontend/app/features/content/pages/content/content-field.component.scss
  40. 6
      frontend/app/features/content/pages/content/content-field.component.ts
  41. 3
      frontend/app/features/content/pages/content/content-history-page.component.scss
  42. 3
      frontend/app/features/content/pages/content/content-page.component.scss
  43. 17
      frontend/app/features/content/pages/content/field-languages.component.html
  44. 0
      frontend/app/features/content/pages/content/field-languages.component.scss
  45. 37
      frontend/app/features/content/pages/content/field-languages.component.ts
  46. 23
      frontend/app/features/content/pages/contents/contents-filters-page.component.html
  47. 3
      frontend/app/features/content/pages/contents/contents-filters-page.component.scss
  48. 9
      frontend/app/features/content/pages/contents/contents-filters-page.component.ts
  49. 4
      frontend/app/features/content/pages/contents/contents-page.component.scss
  50. 6
      frontend/app/features/content/pages/contents/custom-view-editor.component.html
  51. 3
      frontend/app/features/content/pages/contents/custom-view-editor.component.scss
  52. 28
      frontend/app/features/content/pages/contents/custom-view-editor.component.ts
  53. 3
      frontend/app/features/content/pages/schemas/schemas-page.component.scss
  54. 138
      frontend/app/features/content/shared/content-list-field.component.ts
  55. 119
      frontend/app/features/content/shared/content-list-header.component.ts
  56. 69
      frontend/app/features/content/shared/content-selector-item.component.ts
  57. 2
      frontend/app/features/content/shared/content-status.component.scss
  58. 73
      frontend/app/features/content/shared/content-value-editor.component.ts
  59. 42
      frontend/app/features/content/shared/content-value.component.ts
  60. 2
      frontend/app/features/content/shared/due-time-selector.component.scss
  61. 13
      frontend/app/features/content/shared/due-time-selector.component.ts
  62. 0
      frontend/app/features/content/shared/forms/array-editor.component.html
  63. 3
      frontend/app/features/content/shared/forms/array-editor.component.scss
  64. 0
      frontend/app/features/content/shared/forms/array-editor.component.ts
  65. 12
      frontend/app/features/content/shared/forms/array-item.component.html
  66. 3
      frontend/app/features/content/shared/forms/array-item.component.scss
  67. 16
      frontend/app/features/content/shared/forms/array-item.component.ts
  68. 0
      frontend/app/features/content/shared/forms/assets-editor.component.html
  69. 3
      frontend/app/features/content/shared/forms/assets-editor.component.scss
  70. 9
      frontend/app/features/content/shared/forms/assets-editor.component.ts
  71. 0
      frontend/app/features/content/shared/forms/field-editor.component.html
  72. 3
      frontend/app/features/content/shared/forms/field-editor.component.scss
  73. 0
      frontend/app/features/content/shared/forms/field-editor.component.ts
  74. 0
      frontend/app/features/content/shared/forms/stock-photo-editor.component.html
  75. 3
      frontend/app/features/content/shared/forms/stock-photo-editor.component.scss
  76. 4
      frontend/app/features/content/shared/forms/stock-photo-editor.component.ts
  77. 0
      frontend/app/features/content/shared/list/content-list-cell.directive.ts
  78. 62
      frontend/app/features/content/shared/list/content-list-field.component.html
  79. 0
      frontend/app/features/content/shared/list/content-list-field.component.scss
  80. 76
      frontend/app/features/content/shared/list/content-list-field.component.ts
  81. 56
      frontend/app/features/content/shared/list/content-list-header.component.html
  82. 0
      frontend/app/features/content/shared/list/content-list-header.component.scss
  83. 63
      frontend/app/features/content/shared/list/content-list-header.component.ts
  84. 48
      frontend/app/features/content/shared/list/content-value-editor.component.html
  85. 0
      frontend/app/features/content/shared/list/content-value-editor.component.scss
  86. 25
      frontend/app/features/content/shared/list/content-value-editor.component.ts
  87. 6
      frontend/app/features/content/shared/list/content-value.component.html
  88. 14
      frontend/app/features/content/shared/list/content-value.component.scss
  89. 25
      frontend/app/features/content/shared/list/content-value.component.ts
  90. 8
      frontend/app/features/content/shared/list/content.component.html
  91. 3
      frontend/app/features/content/shared/list/content.component.scss
  92. 16
      frontend/app/features/content/shared/list/content.component.ts
  93. 2
      frontend/app/features/content/shared/preview-button.component.scss
  94. 104
      frontend/app/features/content/shared/reference-item.component.ts
  95. 18
      frontend/app/features/content/shared/references/content-selector-item.component.html
  96. 0
      frontend/app/features/content/shared/references/content-selector-item.component.scss
  97. 52
      frontend/app/features/content/shared/references/content-selector-item.component.ts
  98. 0
      frontend/app/features/content/shared/references/content-selector.component.html
  99. 3
      frontend/app/features/content/shared/references/content-selector.component.scss
  100. 8
      frontend/app/features/content/shared/references/content-selector.component.ts

1
backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs

@ -16,6 +16,7 @@ namespace Squidex.Areas.Frontend.Middlewares
public sealed class WebpackMiddleware public sealed class WebpackMiddleware
{ {
private const string WebpackUrl = "http://localhost:3000/index.html"; private const string WebpackUrl = "http://localhost:3000/index.html";
private readonly RequestDelegate next; private readonly RequestDelegate next;
public WebpackMiddleware(RequestDelegate next) public WebpackMiddleware(RequestDelegate next)

12
frontend/app-config/webpack.config.js

@ -37,6 +37,8 @@ module.exports = function (env) {
const isCoverage = env && env.coverage; const isCoverage = env && env.coverage;
const isAot = isProduction; const isAot = isProduction;
const configFile = isTests ? 'tsconfig.spec.json' : 'tsconfig.app.json';
const config = { const config = {
mode: isProduction ? 'production' : 'development', mode: isProduction ? 'production' : 'development',
@ -66,7 +68,9 @@ module.exports = function (env) {
], ],
plugins: [ plugins: [
new plugins.TsconfigPathsPlugin() new plugins.TsconfigPathsPlugin({
configFile
})
] ]
}, },
@ -145,6 +149,10 @@ module.exports = function (env) {
}, { }, {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
prependData: `
@import '_vars';
@import '_mixins';
`,
sassOptions: { sassOptions: {
includePaths: [root('app', 'theme')] includePaths: [root('app', 'theme')]
} }
@ -285,7 +293,7 @@ module.exports = function (env) {
entryModule: 'app/app.module#AppModule', entryModule: 'app/app.module#AppModule',
sourceMap: !isProduction, sourceMap: !isProduction,
skipCodeGeneration: !isAot, skipCodeGeneration: !isAot,
tsConfigPath: './tsconfig.json' tsConfigPath: configFile
}) })
); );
} }

2
frontend/app/app.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

2
frontend/app/features/administration/administration-area.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

3
frontend/app/features/administration/pages/cluster/cluster-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
iframe { iframe {
@include absolute(0, 0, 0, 0); @include absolute(0, 0, 0, 0);
@include force-height(100%); @include force-height(100%);

24
frontend/app/features/administration/pages/event-consumers/event-consumer.component.html

@ -0,0 +1,24 @@
<tr [class.faulted]="eventConsumer.error && eventConsumer.error?.length > 0">
<td class="cell-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>

0
frontend/app/features/administration/pages/event-consumers/event-consumer.component.scss

28
frontend/app/features/administration/pages/event-consumers/event-consumer.component.ts

@ -13,32 +13,8 @@ import { EventConsumerDto, EventConsumersState } from '@app/features/administrat
@Component({ @Component({
selector: '[sqxEventConsumer]', selector: '[sqxEventConsumer]',
template: ` styleUrls: ['./event-consumer.component.scss'],
<tr [class.faulted]="eventConsumer.error && eventConsumer.error?.length > 0"> templateUrl: './event-consumer.component.html',
<td class="cell-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 changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class EventConsumerComponent { export class EventConsumerComponent {

3
frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.faulted { .faulted {
& { & {
color: $color-theme-error; color: $color-theme-error;

3
frontend/app/features/administration/pages/restore/restore-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
$circle-size: 2rem; $circle-size: 2rem;
h3 { h3 {

3
frontend/app/features/administration/pages/users/user-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.form-group-section { .form-group-section {
margin-top: 2rem; margin-top: 2rem;
} }

20
frontend/app/features/administration/pages/users/user.component.html

@ -0,0 +1,20 @@
<tr [routerLink]="user.id" routerLinkActive="active">
<td class="cell-user">
<img class="user-picture" title="{{user.displayName}}" [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>

0
frontend/app/features/administration/pages/users/user.component.scss

24
frontend/app/features/administration/pages/users/user.component.ts

@ -13,28 +13,8 @@ import { UserDto, UsersState } from '@app/features/administration/internal';
@Component({ @Component({
selector: '[sqxUser]', selector: '[sqxUser]',
template: ` styleUrls: ['./user.component.scss'],
<tr [routerLink]="user.id" routerLinkActive="active"> templateUrl: './user.component.html',
<td class="cell-user">
<img class="user-picture" title="{{user.displayName}}" [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 changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class UserComponent { export class UserComponent {

2
frontend/app/features/administration/pages/users/users-page.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

7
frontend/app/features/administration/state/users.forms.ts

@ -1,3 +1,10 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Form, ValidatorsEx } from '@app/shared'; import { Form, ValidatorsEx } from '@app/shared';

3
frontend/app/features/api/api-area.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.nav-link { .nav-link {
padding-bottom: .6rem; padding-bottom: .6rem;
padding-top: .6rem; padding-top: .6rem;

3
frontend/app/features/api/pages/graphql/graphql-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
// Graphiql hints // Graphiql hints
::ng-deep { ::ng-deep {
@import '~graphiql/dist/show-hint'; @import '~graphiql/dist/show-hint';

3
frontend/app/features/apps/pages/apps-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.apps { .apps {
&-title { &-title {
font-size: 1.4rem; font-size: 1.4rem;

2
frontend/app/features/apps/pages/news-dialog.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog large="true" (close)="emitClose()"> <sqx-modal-dialog large="true" (close)="close.emit()">
<ng-container title> <ng-container title>
New Features New Features
</ng-container> </ng-container>

3
frontend/app/features/apps/pages/news-dialog.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
:host ::ng-deep { :host ::ng-deep {
img { img {
@include box-shadow(0, 4px, 20px, .2); @include box-shadow(0, 4px, 20px, .2);

4
frontend/app/features/apps/pages/news-dialog.component.ts

@ -21,10 +21,6 @@ export class NewsDialogComponent {
@Input() @Input()
public features: ReadonlyArray<FeatureDto>; public features: ReadonlyArray<FeatureDto>;
public emitClose() {
this.close.emit();
}
public trackByFeature(index: number, feature: FeatureDto) { public trackByFeature(index: number, feature: FeatureDto) {
return feature; return feature;
} }

2
frontend/app/features/apps/pages/onboarding-dialog.component.html

@ -1,6 +1,6 @@
<sqx-modal-dialog [showHeader]="false"> <sqx-modal-dialog [showHeader]="false">
<ng-container content> <ng-container content>
<a class="header-right modal-close" (click)="emitClose()">Skip Tour</a> <a class="header-right modal-close" (click)="close.emit()">Skip Tour</a>
<div class="onboarding-step" *ngIf="step === 0"> <div class="onboarding-step" *ngIf="step === 0">
<img @fade class="header-left" src="./images/logo-white-small.png" /> <img @fade class="header-left" src="./images/logo-white-small.png" />

3
frontend/app/features/apps/pages/onboarding-dialog.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
$size-width: 825px; $size-width: 825px;
$size-height: 576px; $size-height: 576px;

4
frontend/app/features/apps/pages/onboarding-dialog.component.ts

@ -23,10 +23,6 @@ export class OnboardingDialogComponent {
@Output() @Output()
public close = new EventEmitter(); public close = new EventEmitter();
public emitClose() {
this.close.emit();
}
public next() { public next() {
this.step = this.step + 1; this.step = this.step + 1;
} }

1
frontend/app/features/assets/declarations.ts

@ -7,3 +7,4 @@
export * from './pages/assets-filters-page.component'; export * from './pages/assets-filters-page.component';
export * from './pages/assets-page.component'; export * from './pages/assets-page.component';
export * from './pages/asset-tags.component';

6
frontend/app/features/assets/module.ts

@ -12,7 +12,8 @@ import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { import {
AssetsFiltersPageComponent, AssetsFiltersPageComponent,
AssetsPageComponent AssetsPageComponent,
AssetTagsComponent
} from './declarations'; } from './declarations';
const routes: Routes = [ const routes: Routes = [
@ -36,7 +37,8 @@ const routes: Routes = [
], ],
declarations: [ declarations: [
AssetsFiltersPageComponent, AssetsFiltersPageComponent,
AssetsPageComponent AssetsPageComponent,
AssetTagsComponent
] ]
}) })
export class SqxFeatureAssetsModule {} export class SqxFeatureAssetsModule {}

18
frontend/app/features/assets/pages/asset-tags.component.html

@ -0,0 +1,18 @@
<a class="sidebar-item" (click)="reset.emit()" [class.active]="isEmpty()">
<div class="row">
<div class="col">
All tags
</div>
</div>
</a>
<a class="sidebar-item" *ngFor="let tag of tags; trackBy: trackByTag" (click)="toggle.emit(tag.name)" [class.active]="isSelected(tag)">
<div class="row">
<div class="col">
{{tag.name}}
</div>
<div class="col-auto">
{{tag.count}}
</div>
</div>
</a>

0
frontend/app/features/assets/pages/asset-tags.component.scss

42
frontend/app/features/assets/pages/asset-tags.component.ts

@ -0,0 +1,42 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { Tag, TagsSelected } from '@app/shared';
@Component({
selector: 'sqx-asset-tags',
styleUrls: ['./asset-tags.component.scss'],
templateUrl: './asset-tags.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetTagsComponent {
@Output()
public reset = new EventEmitter();
@Output()
public toggle = new EventEmitter<string>();
@Input()
public tags: ReadonlyArray<Tag>;
@Input()
public tagsSelected: TagsSelected;
public isEmpty() {
return Object.keys(this.tagsSelected).length === 0;
}
public isSelected(tag: Tag) {
return this.tagsSelected[tag.name] === true;
}
public trackByTag(index: number, tag: Tag) {
return tag.name;
}
}

29
frontend/app/features/assets/pages/assets-filters-page.component.html

@ -6,32 +6,19 @@
<ng-container content> <ng-container content>
<h3>Tags</h3> <h3>Tags</h3>
<a class="sidebar-item" (click)="resetTags()" [class.active]="assetsState.isTagSelectionEmpty()"> <sqx-asset-tags
<div class="row"> (reset)="resetTags()"
<div class="col"> [tags]="assetsState.tags | async"
All tags [tagsSelected]="assetsState.tagsSelected | async"
</div> (toggle)="toggleTag($event)">
</div> </sqx-asset-tags>
</a>
<a class="sidebar-item" *ngFor="let tag of assetsState.tags | async; trackBy: trackByTag" (click)="toggleTag(tag.name)"
[class.active]="assetsState.isTagSelected(tag.name)">
<div class="row">
<div class="col">
{{tag.name}}
</div>
<div class="col-auto">
{{tag.count}}
</div>
</div>
</a>
<hr /> <hr />
<sqx-shared-queries types="contents" <sqx-shared-queries types="contents"
[queryUsed]="assetsState.assetsQuery | async"
[queries]="assetsQueries" [queries]="assetsQueries"
[queryUsed]="isQueryUsed" (search)="search($event)">
(search)="search($event.query)">
</sqx-shared-queries> </sqx-shared-queries>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

2
frontend/app/features/assets/pages/assets-filters-page.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

5
frontend/app/features/assets/pages/assets-filters-page.component.ts

@ -11,7 +11,6 @@ import {
AssetsState, AssetsState,
Queries, Queries,
Query, Query,
SavedQuery,
UIState UIState
} from '@app/shared'; } from '@app/shared';
@ -29,10 +28,6 @@ export class AssetsFiltersPageComponent {
) { ) {
} }
public isQueryUsed = (query: SavedQuery) => {
return this.assetsState.isQueryUsed(query);
}
public search(query: Query) { public search(query: Query) {
this.assetsState.search(query); this.assetsState.search(query);
} }

3
frontend/app/features/assets/pages/assets-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.search ::ng-deep { .search ::ng-deep {
.form-control { .form-control {
@include border-radius-right; @include border-radius-right;

33
frontend/app/features/content/declarations.ts

@ -15,21 +15,24 @@ export * from './pages/contents/contents-page.component';
export * from './pages/contents/custom-view-editor.component'; export * from './pages/contents/custom-view-editor.component';
export * from './pages/schemas/schemas-page.component'; 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-list-cell.directive';
export * from './shared/content-list-field.component';
export * from './shared/content-list-header.component';
export * from './shared/content-selector-item.component';
export * from './shared/content-status.component'; export * from './shared/content-status.component';
export * from './shared/content-value-editor.component';
export * from './shared/content-value.component';
export * from './shared/content.component';
export * from './shared/contents-selector.component';
export * from './shared/due-time-selector.component'; export * from './shared/due-time-selector.component';
export * from './shared/field-editor.component';
export * from './shared/preview-button.component'; export * from './shared/preview-button.component';
export * from './shared/reference-item.component';
export * from './shared/references-editor.component'; export * from './shared/forms/array-editor.component';
export * from './shared/stock-photo-editor.component'; export * from './shared/forms/array-item.component';
export * from './shared/forms/assets-editor.component';
export * from './shared/forms/field-editor.component';
export * from './shared/forms/stock-photo-editor.component';
export * from './shared/list/content-list-cell.directive';
export * from './shared/list/content-list-field.component';
export * from './shared/list/content-list-header.component';
export * from './shared/list/content-value-editor.component';
export * from './shared/list/content-value.component';
export * from './shared/list/content.component';
export * from './shared/references/content-selector-item.component';
export * from './shared/references/content-selector.component';
export * from './shared/references/reference-item.component';
export * from './shared/references/references-editor.component';

8
frontend/app/features/content/module.ts

@ -32,10 +32,10 @@ import {
ContentListHeaderComponent, ContentListHeaderComponent,
ContentListWidthPipe, ContentListWidthPipe,
ContentPageComponent, ContentPageComponent,
ContentSelectorComponent,
ContentSelectorItemComponent, ContentSelectorItemComponent,
ContentsFiltersPageComponent, ContentsFiltersPageComponent,
ContentsPageComponent, ContentsPageComponent,
ContentsSelectorComponent,
ContentStatusComponent, ContentStatusComponent,
ContentValueComponent, ContentValueComponent,
ContentValueEditorComponent, ContentValueEditorComponent,
@ -118,16 +118,16 @@ const routes: Routes = [
CommentsPageComponent, CommentsPageComponent,
ContentComponent, ContentComponent,
ContentFieldComponent, ContentFieldComponent,
ContentHistoryPageComponent,
ContentListCellDirective, ContentListCellDirective,
ContentListWidthPipe,
ContentListFieldComponent, ContentListFieldComponent,
ContentListHeaderComponent, ContentListHeaderComponent,
ContentHistoryPageComponent, ContentListWidthPipe,
ContentPageComponent, ContentPageComponent,
ContentSelectorComponent,
ContentSelectorItemComponent, ContentSelectorItemComponent,
ContentsFiltersPageComponent, ContentsFiltersPageComponent,
ContentsPageComponent, ContentsPageComponent,
ContentsSelectorComponent,
ContentStatusComponent, ContentStatusComponent,
ContentValueComponent, ContentValueComponent,
ContentValueEditorComponent, ContentValueEditorComponent,

2
frontend/app/features/content/pages/comments/comments-page.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

4
frontend/app/features/content/pages/content/content-field.component.html

@ -9,7 +9,7 @@
<sqx-field-languages <sqx-field-languages
[field]="field" [field]="field"
[language]="language" [language]="language"
(languageChange)="emitLanguageChange($event)" (languageChange)="languageChange.emit($event)"
[languages]="languages" [languages]="languages"
[showAllControls]="showAllControls" [showAllControls]="showAllControls"
(showAllControlsChange)="changeShowAllControls($event)"> (showAllControlsChange)="changeShowAllControls($event)">
@ -67,7 +67,7 @@
<sqx-field-languages <sqx-field-languages
[field]="field" [field]="field"
[language]="language" [language]="language"
(languageChange)="emitLanguageChange($event)" (languageChange)="languageChange.emit($event)"
[languages]="languages" [languages]="languages"
[showAllControls]="showAllControls" [showAllControls]="showAllControls"
(showAllControlsChange)="changeShowAllControls($event)"> (showAllControlsChange)="changeShowAllControls($event)">

3
frontend/app/features/content/pages/content/content-field.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.table-items-row { .table-items-row {
border-left-width: 4px; border-left-width: 4px;
position: relative; position: relative;

6
frontend/app/features/content/pages/content/content-field.component.ts

@ -90,7 +90,7 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
this.isDifferent = this.isDifferent =
value$(this.fieldForm).pipe( value$(this.fieldForm).pipe(
combineLatest(value$(this.fieldFormCompare), combineLatest(value$(this.fieldFormCompare),
(lhs, rhs) => !Types.jsJsonEquals(lhs, rhs))); (lhs, rhs) => !Types.equals(lhs, rhs)));
} }
} }
@ -184,10 +184,6 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
} }
} }
public emitLanguageChange(language: AppLanguageDto) {
this.languageChange.emit(language);
}
public prefix(language: AppLanguageDto) { public prefix(language: AppLanguageDto) {
return `(${language.iso2Code})`; return `(${language.iso2Code})`;
} }

3
frontend/app/features/content/pages/content/content-history-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
:host ::ng-deep { :host ::ng-deep {
.user-ref { .user-ref {
font-weight: 500; font-weight: 500;

3
frontend/app/features/content/pages/content/content-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.btn-status { .btn-status {
margin-left: 2rem; margin-left: 2rem;
} }

17
frontend/app/features/content/pages/content/field-languages.component.html

@ -0,0 +1,17 @@
<ng-container *ngIf="field.isLocalizable && languages.length > 1">
<button *ngIf="!field.properties.isComplexUI" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="toggleShowAllControls()">
{{showAllControls ? 'Single Language' : 'All Languages'}}
</button>
<ng-container *ngIf="field.properties.isComplexUI || !showAllControls">
<sqx-language-selector size="sm" #buttonLanguages
[selectedLanguage]="language"
(selectedLanguageChange)="languageChange.emit($event)"
[languages]="languages">
</sqx-language-selector>
<sqx-onboarding-tooltip helpId="languages" [for]="buttonLanguages" position="top-right" after="120000">
Please remember to check all languages when you see validation errors.
</sqx-onboarding-tooltip>
</ng-container>
</ng-container>

0
frontend/app/features/content/pages/content/field-languages.component.scss

37
frontend/app/features/content/pages/content/field-languages.component.ts

@ -11,43 +11,30 @@ import { AppLanguageDto, RootFieldDto } from '@app/shared';
@Component({ @Component({
selector: 'sqx-field-languages', selector: 'sqx-field-languages',
template: ` styleUrls: ['./field-languages.component.scss'],
<ng-container *ngIf="field.isLocalizable && languages.length > 1"> templateUrl: './field-languages.component.html',
<button *ngIf="!field.properties.isComplexUI" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="showAllControlsChange.emit(!showAllControls)">
{{showAllControls ? 'Single Language' : 'All Languages'}}
</button>
<ng-container *ngIf="field.properties.isComplexUI || !showAllControls">
<sqx-language-selector size="sm" #buttonLanguages
[selectedLanguage]="language"
(selectedLanguageChange)="languageChange.emit($event)"
[languages]="languages">
</sqx-language-selector>
<sqx-onboarding-tooltip helpId="languages" [for]="buttonLanguages" position="top-right" after="120000">
Please remember to check all languages when you see validation errors.
</sqx-onboarding-tooltip>
</ng-container>
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class FieldLanguagesComponent { export class FieldLanguagesComponent {
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@Output() @Output()
public showAllControlsChange = new EventEmitter<boolean>(); public showAllControlsChange = new EventEmitter<boolean>();
@Input()
public field: RootFieldDto;
@Input() @Input()
public showAllControls: boolean; public showAllControls: boolean;
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@Input() @Input()
public language: AppLanguageDto; public language: AppLanguageDto;
@Input() @Input()
public languages: ReadonlyArray<AppLanguageDto>; public languages: ReadonlyArray<AppLanguageDto>;
@Input()
public field: RootFieldDto;
public toggleShowAllControls() {
this.showAllControlsChange.emit(this.showAllControls);
}
} }

23
frontend/app/features/content/pages/contents/contents-filters-page.component.html

@ -4,29 +4,30 @@
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<a class="sidebar-item" *ngFor="let default of schemaQueries.defaultQueries; trackBy: trackByQuery" (click)="search(default.query)" <sqx-query-list
[class.active]="isQueryUsed(default)"> [queryUsed]="contentsState.contentsQuery | async"
{{default.name}} [queries]="schemaQueries.defaultQueries"
</a> (search)="search($event)">
</sqx-query-list>
<hr /> <hr />
<div class="sidebar-section"> <div class="sidebar-section">
<h3>Status Queries</h3> <h3>Status Queries</h3>
<a class="sidebar-item status" *ngFor="let status of contentsState.statusQueries | async; trackBy: trackByQuery" (click)="search(status.query)" <sqx-query-list
[class.active]="isQueryUsed(status)"> [queryUsed]="contentsState.contentsQuery | async"
[queries]="contentsState.statusQueries | async"
<i class="icon-circle" [style.color]="status.color"></i> {{status.name}} (search)="search($event)">
</a> </sqx-query-list>
</div> </div>
<hr /> <hr />
<sqx-shared-queries types="contents" <sqx-shared-queries types="contents"
[queryUsed]="contentsState.contentsQuery | async"
[queries]="schemaQueries" [queries]="schemaQueries"
[queryUsed]="isQueryUsed" (search)="search($event)">
(search)="search($event.query)">
</sqx-shared-queries> </sqx-shared-queries>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

3
frontend/app/features/content/pages/contents/contents-filters-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.status { .status {
@include truncate; @include truncate;
} }

9
frontend/app/features/content/pages/contents/contents-filters-page.component.ts

@ -12,7 +12,6 @@ import {
Queries, Queries,
Query, Query,
ResourceOwner, ResourceOwner,
SavedQuery,
SchemasState, SchemasState,
UIState UIState
} from '@app/shared'; } from '@app/shared';
@ -41,15 +40,7 @@ export class ContentsFiltersPageComponent extends ResourceOwner implements OnIni
})); }));
} }
public isQueryUsed = (query: SavedQuery) => {
return this.contentsState.isQueryUsed(query);
}
public search(query: Query) { public search(query: Query) {
this.contentsState.search(query); this.contentsState.search(query);
} }
public trackByQuery(index: number, query: { name: string }) {
return query.name;
}
} }

4
frontend/app/features/content/pages/contents/contents-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.content { .content {
cursor: pointer; cursor: pointer;
} }
@ -27,6 +24,7 @@
&-button { &-button {
@include absolute(null, 1rem, -2.375rem); @include absolute(null, 1rem, -2.375rem);
z-index: 1000;
} }
} }

6
frontend/app/features/content/pages/contents/custom-view-editor.component.html

@ -12,11 +12,11 @@
[cdkDropListData]="fieldNames" [cdkDropListData]="fieldNames"
(cdkDropListDropped)="drop($event)"> (cdkDropListDropped)="drop($event)">
<div *ngFor="let field of fieldNames; trackBy: random" cdkDrag> <div *ngFor="let field of fieldNames" cdkDrag>
<i class="icon-drag2 drag-handle"></i> <i class="icon-drag2 drag-handle"></i>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" checked (change)="removeField(field)" id="field_{{field}}"> <input class="form-check-input" type="checkbox" checked (click)="removeField(field)" id="field_{{field}}">
<label class="form-check-label" for="field_{{field}}"> <label class="form-check-label" for="field_{{field}}">
{{field}} {{field}}
</label> </label>
@ -31,7 +31,7 @@
<i class="icon-drag2 drag-handle invisible"></i> <i class="icon-drag2 drag-handle invisible"></i>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" (change)="addField(field)" id="field_{{field}}"> <input class="form-check-input" type="checkbox" (click)="addField(field)" id="field_{{field}}">
<label class="form-check-label" for="field_{{field}}"> <label class="form-check-label" for="field_{{field}}">
{{field}} {{field}}
</label> </label>

3
frontend/app/features/content/pages/contents/custom-view-editor.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.container { .container {
max-height: 400px; max-height: 400px;
overflow-x: hidden; overflow-x: hidden;

28
frontend/app/features/content/pages/contents/custom-view-editor.component.ts

@ -17,14 +17,14 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Out
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class CustomViewEditorComponent implements OnChanges { export class CustomViewEditorComponent implements OnChanges {
@Input() @Output()
public allFields: ReadonlyArray<string>; public fieldNamesChange = new EventEmitter<ReadonlyArray<string>>();
@Input() @Input()
public fieldNames: ReadonlyArray<string>; public fieldNames: ReadonlyArray<string>;
@Output() @Input()
public fieldNamesChange = new EventEmitter<ReadonlyArray<string>>(); public allFields: ReadonlyArray<string>;
public fieldsNotAdded: ReadonlyArray<string>; public fieldsNotAdded: ReadonlyArray<string>;
@ -32,29 +32,25 @@ export class CustomViewEditorComponent implements OnChanges {
this.fieldsNotAdded = this.allFields.filter(n => this.fieldNames.indexOf(n) < 0); this.fieldsNotAdded = this.allFields.filter(n => this.fieldNames.indexOf(n) < 0);
} }
public random() {
return Math.random();
}
public drop(event: CdkDragDrop<string[]>) { public drop(event: CdkDragDrop<string[]>) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
this.updateFields(event.container.data); this.updateFieldNames(event.container.data);
}
public updateFields(fieldNames: ReadonlyArray<string>) {
this.fieldNamesChange.emit(fieldNames);
} }
public resetDefault() { public resetDefault() {
this.updateFields([]); this.updateFieldNames([]);
} }
public addField(field: string) { public addField(field: string) {
this.updateFields([...this.fieldNames, field]); this.updateFieldNames([...this.fieldNames, field]);
} }
public removeField(field: string) { public removeField(field: string) {
this.updateFields(this.fieldNames.filter(x => x !== field)); this.updateFieldNames(this.fieldNames.filter(x => x !== field));
}
private updateFieldNames(fieldNames: ReadonlyArray<string>) {
this.fieldNamesChange.emit(fieldNames);
} }
} }

3
frontend/app/features/content/pages/schemas/schemas-page.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.btn-collapse { .btn-collapse {
& { & {
font-size: 1.2rem; font-size: 1.2rem;

138
frontend/app/features/content/shared/content-list-field.component.ts

@ -1,138 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
ContentDto,
getContentValue,
LanguageDto,
MetaFields,
RootFieldDto,
TableField,
Types
} from '@app/shared';
@Component({
selector: 'sqx-content-list-field',
template: `
<ng-container [ngSwitch]="fieldName">
<ng-container *ngSwitchCase="metaFields.id">
<small class="truncate">{{content.id}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.created">
<small class="truncate">{{content.created | sqxFromNow}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByAvatar">
<img class="user-picture" title="{{content.createdBy | sqxUserNameRef}}" [src]="content.createdBy | sqxUserPictureRef" />
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByName">
<small class="truncate">{{content.createdBy | sqxUserNameRef}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModified">
<small class="truncate">{{content.lastModified | sqxFromNow}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByAvatar">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [src]="content.lastModifiedBy | sqxUserPictureRef" />
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByName">
<small class="truncate">{{content.lastModifiedBy | sqxUserNameRef}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.status">
<span class="truncate">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
{{content.status}}
</span>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusNext">
<span class="truncate" *ngIf="content.scheduleJob; let job">
{{job.status}} at {{job.dueTime | sqxShortDate}}
</span>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusColor">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
</ng-container>
<ng-container *ngSwitchCase="metaFields.version">
<small class="truncate">{{content.version.value}}</small>
</ng-container>
<ng-container *ngSwitchDefault>
<ng-container *ngIf="isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-value-editor [form]="patchForm" [field]="field"></sqx-content-value-editor>
</ng-container>
<ng-template #displayTemplate>
<sqx-content-value [value]="value"></sqx-content-value>
</ng-template>
</ng-container>
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentListFieldComponent implements OnChanges {
@Input()
public field: TableField;
@Input()
public content: ContentDto;
@Input()
public patchAllowed: boolean;
@Input()
public patchForm: FormGroup;
@Input()
public language: LanguageDto;
public value: any;
public ngOnChanges() {
this.reset();
}
public reset() {
if (Types.is(this.field, RootFieldDto)) {
const { value, formatted } = getContentValue(this.content, this.language, this.field);
if (this.patchForm) {
const formControl = this.patchForm.controls[this.field.name];
if (formControl) {
formControl.setValue(value);
}
}
this.value = formatted;
}
}
public get metaFields() {
return MetaFields;
}
public get isInlineEditable() {
return Types.is(this.field, RootFieldDto) ? this.field.isInlineEditable : false;
}
public get fieldName() {
return Types.is(this.field, RootFieldDto) ? this.field.name : this.field;
}
}

119
frontend/app/features/content/shared/content-list-header.component.ts

@ -1,119 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import {
LanguageDto,
MetaFields,
Query,
RootFieldDto,
TableField,
Types
} from '@app/shared';
@Component({
selector: 'sqx-content-list-header',
template: `
<ng-container [ngSwitch]="fieldName">
<ng-container *ngSwitchCase="metaFields.id">
<sqx-table-header text="Id"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.created">
<sqx-table-header text="Created"
[sortable]="true"
[fieldPath]="'created'"
[query]="query"
(queryChange)="queryChange.emit($event)"
[language]="language">
</sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByAvatar">
<sqx-table-header text="By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByName">
<sqx-table-header text="Created By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModified">
<sqx-table-header text="Updated"
[sortable]="true"
[fieldPath]="'lastModified'"
[query]="query"
(queryChange)="queryChange.emit($event)"
[language]="language">
</sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByAvatar">
<sqx-table-header text="By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByName">
<sqx-table-header text="Modified By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.status">
<sqx-table-header text="Status"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusNext">
<sqx-table-header text="Next Status"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusColor">
<sqx-table-header text="Status"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.version">
<sqx-table-header text="Version"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchDefault>
<sqx-table-header [text]="fieldDisplayName"
[sortable]="isSortable"
[fieldPath]="fieldPath"
[query]="query"
(queryChange)="queryChange.emit($event)"
[language]="language">
</sqx-table-header>
</ng-container>
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentListHeaderComponent {
@Input()
public field: TableField;
@Output()
public queryChange = new EventEmitter<Query>();
@Input()
public query: Query;
@Input()
public language: LanguageDto;
public get metaFields() {
return MetaFields;
}
public get isSortable() {
return Types.is(this.field, RootFieldDto) ? this.field.properties.isSortable : false;
}
public get fieldName() {
return Types.is(this.field, RootFieldDto) ? this.field.name : this.field;
}
public get fieldDisplayName() {
return Types.is(this.field, RootFieldDto) ? this.field.displayName : '';
}
public get fieldPath() {
if (Types.isString(this.field)) {
return this.field;
} else if (this.field.isLocalizable && this.language) {
return `data.${this.field.name}.${this.language.iso2Code}`;
} else {
return `data.${this.field.name}.iv`;
}
}
}

69
frontend/app/features/content/shared/content-selector-item.component.ts

@ -1,69 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import {
ContentDto,
LanguageDto,
SchemaDetailsDto
} from '@app/shared';
/* tslint:disable:component-selector */
@Component({
selector: '[sqxContentSelectorItem]',
template: `
<tr (click)="toggle()">
<td class="cell-select" sqxStopClick>
<input type="checkbox" class="form-check"
[disabled]="!selectable"
[ngModel]="selected || !selectable"
(ngModelChange)="emitSelectedChange($event)" />
</td>
<td sqxContentListCell="meta.lastModifiedBy.avatar">
<sqx-content-list-field field="meta.lastModifiedBy.avatar" [content]="content" [language]="language"></sqx-content-list-field>
</td>
<td *ngFor="let field of schema.defaultReferenceFields" [sqxContentListCell]="field">
<sqx-content-list-field [field]="field" [content]="content" [language]="language"></sqx-content-list-field>
</td>
</tr>
<tr class="spacer"></tr>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentSelectorItemComponent {
@Output()
public selectedChange = new EventEmitter<boolean>();
@Input()
public selected = false;
@Input()
public selectable = true;
@Input()
public language: LanguageDto;
@Input()
public schema: SchemaDetailsDto;
@Input('sqxContentSelectorItem')
public content: ContentDto;
public toggle() {
if (this.selectable) {
this.emitSelectedChange(!this.selected);
}
}
public emitSelectedChange(isSelected: boolean) {
this.selectedChange.emit(isSelected);
}
}

2
frontend/app/features/content/shared/content-status.component.scss

@ -1,5 +1,3 @@
@import '_vars';
@import '_mixins';
.content-status { .content-status {
&.default { &.default {

73
frontend/app/features/content/shared/content-value-editor.component.ts

@ -1,73 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FieldDto } from '@app/shared';
@Component({
selector: 'sqx-content-value-editor',
template: `
<div [formGroup]="form">
<ng-container [ngSwitch]="field.properties.fieldType">
<ng-container *ngSwitchCase="'Number'">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Stars'">
<sqx-stars [formControlName]="field.name" [maximumStars]="field.rawProperties.maxValue"></sqx-stars>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Slug'">
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" sqxTransformInput="Slugify" />
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControlName]="field.name" [threeStates]="!field.properties.isRequired"></sqx-toggle>
</ng-container>
<ng-container *ngSwitchCase="'Checkbox'">
<ng-container class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" [formControlName]="field.name" sqxIndeterminateValue />
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentValueEditorComponent {
@Input()
public field: FieldDto;
@Input()
public form: FormGroup;
}

42
frontend/app/features/content/shared/content-value.component.ts

@ -1,42 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { HtmlValue, Types } from '@app/shared';
@Component({
selector: 'sqx-content-value',
template: `
<ng-container *ngIf="isPlain; else html">
<span class="truncate">{{value}}</span>
</ng-container>
<ng-template #html>
<span class="html-value" [innerHTML]="value.html"></span>
</ng-template>
`,
styles: [`
.html-value {
position: relative;
}
::ng-deep .html-value img {
position: absolute;
min-height: 50px;
max-height: 50px;
margin-top: -25px;
}`
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentValueComponent {
@Input()
public value: any;
public get isPlain() {
return !Types.is(this.value, HtmlValue);
}
}

2
frontend/app/features/content/shared/due-time-selector.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

13
frontend/app/features/content/shared/due-time-selector.component.ts

@ -16,25 +16,26 @@ import { DialogModel } from '@app/shared';
templateUrl: './due-time-selector.component.html' templateUrl: './due-time-selector.component.html'
}) })
export class DueTimeSelectorComponent { export class DueTimeSelectorComponent {
private dueTimeResult: Subject<string | null>;
public dueTimeDialog = new DialogModel(); public dueTimeDialog = new DialogModel();
public dueTime: string | null = ''; public dueTime: string | null = '';
public dueTimeFunction: Subject<string | null>;
public dueTimeAction: string | null = ''; public dueTimeAction: string | null = '';
public dueTimeMode = 'Immediately'; public dueTimeMode = 'Immediately';
public selectDueTime(action: string): Observable<string | null> { public selectDueTime(action: string): Observable<string | null> {
this.dueTimeAction = action; this.dueTimeAction = action;
this.dueTimeFunction = new Subject<string | null>(); this.dueTimeResult = new Subject<string | null>();
this.dueTimeDialog.show(); this.dueTimeDialog.show();
return this.dueTimeFunction; return this.dueTimeResult;
} }
public confirmStatusChange() { public confirmStatusChange() {
const result = this.dueTimeMode === 'Immediately' ? null : this.dueTime; const result = this.dueTimeMode === 'Immediately' ? null : this.dueTime;
this.dueTimeFunction.next(result); this.dueTimeResult.next(result);
this.dueTimeFunction.complete(); this.dueTimeResult.complete();
this.cancelStatusChange(); this.cancelStatusChange();
} }
@ -42,7 +43,7 @@ export class DueTimeSelectorComponent {
public cancelStatusChange() { public cancelStatusChange() {
this.dueTimeMode = 'Immediately'; this.dueTimeMode = 'Immediately';
this.dueTimeDialog.hide(); this.dueTimeDialog.hide();
this.dueTimeFunction = null!; this.dueTimeResult = null!;
this.dueTime = null; this.dueTime = null;
} }
} }

0
frontend/app/features/content/shared/array-editor.component.html → frontend/app/features/content/shared/forms/array-editor.component.html

3
frontend/app/features/content/shared/array-editor.component.scss → frontend/app/features/content/shared/forms/array-editor.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.array-container { .array-container {
background: $color-border; background: $color-border;
margin-bottom: 1rem; margin-bottom: 1rem;

0
frontend/app/features/content/shared/array-editor.component.ts → frontend/app/features/content/shared/forms/array-editor.component.ts

12
frontend/app/features/content/shared/array-item.component.html → frontend/app/features/content/shared/forms/array-item.component.html

@ -11,16 +11,16 @@
</div> </div>
</div> </div>
<div class="col-auto pr-4"> <div class="col-auto pr-4">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="emitMoveTop()"> <button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="moveTop()">
<i class="icon-caret-top"></i> <i class="icon-caret-top"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="emitMoveUp()"> <button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="moveUp()">
<i class="icon-caret-up"></i> <i class="icon-caret-up"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="emitMoveDown()"> <button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveDown()">
<i class="icon-caret-down"></i> <i class="icon-caret-down"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="emitMoveBottom()"> <button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveBottom()">
<i class="icon-caret-bottom"></i> <i class="icon-caret-bottom"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="!isHidden" (click)="expand()" title="Expand this item"> <button type="button" class="btn btn-text-secondary" [class.hidden]="!isHidden" (click)="expand()" title="Expand this item">
@ -31,11 +31,11 @@
</button> </button>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled" (click)="emitClone()"> <button type="button" class="btn btn-text-secondary" [disabled]="isDisabled" (click)="clone.emit()">
<i class="icon-clone"></i> <i class="icon-clone"></i>
</button> </button>
<button type="button" class="btn btn-text-danger" [disabled]="isDisabled" (click)="emitRemove()"> <button type="button" class="btn btn-text-danger" [disabled]="isDisabled" (click)="remove.emit()">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
</button> </button>
</div> </div>

3
frontend/app/features/content/shared/array-item.component.scss → frontend/app/features/content/shared/forms/array-item.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
:host ::ng-deep { :host ::ng-deep {
.ui-separator { .ui-separator {
border-color: $color-border !important; border-color: $color-border !important;

16
frontend/app/features/content/shared/array-item.component.ts → frontend/app/features/content/shared/forms/array-item.component.ts

@ -155,27 +155,19 @@ export class ArrayItemComponent implements OnChanges, OnDestroy {
this.changeDetector.markForCheck(); this.changeDetector.markForCheck();
} }
public emitClone() { public moveTop() {
this.clone.emit();
}
public emitRemove() {
this.remove.emit();
}
public emitMoveTop() {
this.move.emit(0); this.move.emit(0);
} }
public emitMoveUp() { public moveUp() {
this.move.emit(this.index - 1); this.move.emit(this.index - 1);
} }
public emitMoveDown() { public moveDown() {
this.move.emit(this.index + 1); this.move.emit(this.index + 1);
} }
public emitMoveBottom() { public moveBottom() {
this.move.emit(99999); this.move.emit(99999);
} }

0
frontend/app/features/content/shared/assets-editor.component.html → frontend/app/features/content/shared/forms/assets-editor.component.html

3
frontend/app/features/content/shared/assets-editor.component.scss → frontend/app/features/content/shared/forms/assets-editor.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.disabled { .disabled {
pointer-events: none; pointer-events: none;
} }

9
frontend/app/features/content/shared/assets-editor.component.ts → frontend/app/features/content/shared/forms/assets-editor.component.ts

@ -45,11 +45,12 @@ interface State {
selector: 'sqx-assets-editor', selector: 'sqx-assets-editor',
styleUrls: ['./assets-editor.component.scss'], styleUrls: ['./assets-editor.component.scss'],
templateUrl: './assets-editor.component.html', templateUrl: './assets-editor.component.html',
providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR], providers: [
SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
// tslint:disable-next-line: readonly-array export class AssetsEditorComponent extends StatefulControlComponent<State, ReadonlyArray<string>> implements OnInit {
export class AssetsEditorComponent extends StatefulControlComponent<State, string[]> implements OnInit {
@Input() @Input()
public isCompact = false; public isCompact = false;
@ -70,7 +71,7 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
public writeValue(obj: any) { public writeValue(obj: any) {
if (Types.isArrayOfString(obj)) { if (Types.isArrayOfString(obj)) {
if (!Types.isEquals(obj, this.snapshot.assets.map(x => x.id))) { if (!Types.equals(obj, this.snapshot.assets.map(x => x.id))) {
const assetIds: string[] = obj; const assetIds: string[] = obj;
this.assetsService.getAssets(this.appsState.appName, 0, 0, undefined, undefined, obj) this.assetsService.getAssets(this.appsState.appName, 0, 0, undefined, undefined, obj)

0
frontend/app/features/content/shared/field-editor.component.html → frontend/app/features/content/shared/forms/field-editor.component.html

3
frontend/app/features/content/shared/field-editor.component.scss → frontend/app/features/content/shared/forms/field-editor.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.field { .field {
&-required { &-required {
color: $color-theme-error; color: $color-theme-error;

0
frontend/app/features/content/shared/field-editor.component.ts → frontend/app/features/content/shared/forms/field-editor.component.ts

0
frontend/app/features/content/shared/stock-photo-editor.component.html → frontend/app/features/content/shared/forms/stock-photo-editor.component.html

3
frontend/app/features/content/shared/stock-photo-editor.component.scss → frontend/app/features/content/shared/forms/stock-photo-editor.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
$color-user-background: rgba(0, 0, 0, .5); $color-user-background: rgba(0, 0, 0, .5);
$color-background: #000; $color-background: #000;

4
frontend/app/features/content/shared/stock-photo-editor.component.ts → frontend/app/features/content/shared/forms/stock-photo-editor.component.ts

@ -30,7 +30,9 @@ export const SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
selector: 'sqx-stock-photo-editor', selector: 'sqx-stock-photo-editor',
styleUrls: ['./stock-photo-editor.component.scss'], styleUrls: ['./stock-photo-editor.component.scss'],
templateUrl: './stock-photo-editor.component.html', templateUrl: './stock-photo-editor.component.html',
providers: [SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR], providers: [
SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class StockPhotoEditorComponent extends StatefulControlComponent<State, string> implements OnInit { export class StockPhotoEditorComponent extends StatefulControlComponent<State, string> implements OnInit {

0
frontend/app/features/content/shared/content-list-cell.directive.ts → frontend/app/features/content/shared/list/content-list-cell.directive.ts

62
frontend/app/features/content/shared/list/content-list-field.component.html

@ -0,0 +1,62 @@
<ng-container [ngSwitch]="fieldName">
<ng-container *ngSwitchCase="metaFields.id">
<small class="truncate">{{content.id}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.created">
<small class="truncate">{{content.created | sqxFromNow}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByAvatar">
<img class="user-picture" title="{{content.createdBy | sqxUserNameRef}}" [src]="content.createdBy | sqxUserPictureRef" />
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByName">
<small class="truncate">{{content.createdBy | sqxUserNameRef}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModified">
<small class="truncate">{{content.lastModified | sqxFromNow}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByAvatar">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [src]="content.lastModifiedBy | sqxUserPictureRef" />
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByName">
<small class="truncate">{{content.lastModifiedBy | sqxUserNameRef}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.status">
<span class="truncate">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
{{content.status}}
</span>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusNext">
<span class="truncate" *ngIf="content.scheduleJob">
{{content.scheduleJob.status}} at {{content.scheduleJob.dueTime | sqxShortDate}}
</span>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusColor">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
</ng-container>
<ng-container *ngSwitchCase="metaFields.version">
<small class="truncate">{{content.version.value}}</small>
</ng-container>
<ng-container *ngSwitchDefault>
<ng-container *ngIf="isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-value-editor [form]="patchForm" [field]="field"></sqx-content-value-editor>
</ng-container>
<ng-template #displayTemplate>
<sqx-content-value [value]="value"></sqx-content-value>
</ng-template>
</ng-container>
</ng-container>

0
frontend/app/features/content/shared/list/content-list-field.component.scss

76
frontend/app/features/content/shared/list/content-list-field.component.ts

@ -0,0 +1,76 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
ContentDto,
getContentValue,
LanguageDto,
MetaFields,
RootFieldDto,
TableField,
Types
} from '@app/shared';
@Component({
selector: 'sqx-content-list-field',
styleUrls: ['./content-list-field.component.scss'],
templateUrl: './content-list-field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentListFieldComponent implements OnChanges {
@Input()
public field: TableField;
@Input()
public content: ContentDto;
@Input()
public patchAllowed: boolean;
@Input()
public patchForm: FormGroup;
@Input()
public language: LanguageDto;
public value: any;
public ngOnChanges() {
this.reset();
}
public reset() {
if (Types.is(this.field, RootFieldDto)) {
const { value, formatted } = getContentValue(this.content, this.language, this.field);
if (this.patchForm) {
const formControl = this.patchForm.controls[this.field.name];
if (formControl) {
formControl.setValue(value);
}
}
this.value = formatted;
}
}
public get metaFields() {
return MetaFields;
}
public get isInlineEditable() {
return Types.is(this.field, RootFieldDto) ? this.field.isInlineEditable : false;
}
public get fieldName() {
return Types.is(this.field, RootFieldDto) ? this.field.name : this.field;
}
}

56
frontend/app/features/content/shared/list/content-list-header.component.html

@ -0,0 +1,56 @@
<ng-container [ngSwitch]="fieldName">
<ng-container *ngSwitchCase="metaFields.id">
<sqx-table-header text="Id"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.created">
<sqx-table-header text="Created"
[sortable]="true"
[fieldPath]="'created'"
[query]="query"
(queryChange)="queryChange.emit($event)"
[language]="language">
</sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByAvatar">
<sqx-table-header text="By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByName">
<sqx-table-header text="Created By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModified">
<sqx-table-header text="Updated"
[sortable]="true"
[fieldPath]="'lastModified'"
[query]="query"
(queryChange)="queryChange.emit($event)"
[language]="language">
</sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByAvatar">
<sqx-table-header text="By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByName">
<sqx-table-header text="Modified By"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.status">
<sqx-table-header text="Status"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusNext">
<sqx-table-header text="Next Status"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusColor">
<sqx-table-header text="Status"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchCase="metaFields.version">
<sqx-table-header text="Version"></sqx-table-header>
</ng-container>
<ng-container *ngSwitchDefault>
<sqx-table-header [text]="fieldDisplayName"
[sortable]="isSortable"
[fieldPath]="fieldPath"
[query]="query"
(queryChange)="queryChange.emit($event)"
[language]="language">
</sqx-table-header>
</ng-container>
</ng-container>

0
frontend/app/features/content/shared/list/content-list-header.component.scss

63
frontend/app/features/content/shared/list/content-list-header.component.ts

@ -0,0 +1,63 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import {
LanguageDto,
MetaFields,
Query,
RootFieldDto,
TableField,
Types
} from '@app/shared';
@Component({
selector: 'sqx-content-list-header',
styleUrls: ['./content-list-header.component.scss'],
templateUrl: './content-list-header.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentListHeaderComponent {
@Input()
public field: TableField;
@Output()
public queryChange = new EventEmitter<Query>();
@Input()
public query: Query;
@Input()
public language: LanguageDto;
public get metaFields() {
return MetaFields;
}
public get isSortable() {
return Types.is(this.field, RootFieldDto) ? this.field.properties.isSortable : false;
}
public get fieldName() {
return Types.is(this.field, RootFieldDto) ? this.field.name : this.field;
}
public get fieldDisplayName() {
return Types.is(this.field, RootFieldDto) ? this.field.displayName : '';
}
public get fieldPath() {
if (Types.isString(this.field)) {
return this.field;
} else if (this.field.isLocalizable && this.language) {
return `data.${this.field.name}.${this.language.iso2Code}`;
} else {
return `data.${this.field.name}.iv`;
}
}
}

48
frontend/app/features/content/shared/list/content-value-editor.component.html

@ -0,0 +1,48 @@
<div [formGroup]="form">
<ng-container [ngSwitch]="field.properties.fieldType">
<ng-container *ngSwitchCase="'Number'">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Stars'">
<sqx-stars [formControlName]="field.name" [maximumStars]="field.rawProperties.maxValue"></sqx-stars>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Slug'">
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" sqxTransformInput="Slugify" />
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControlName]="field.name" [threeStates]="!field.properties.isRequired"></sqx-toggle>
</ng-container>
<ng-container *ngSwitchCase="'Checkbox'">
<ng-container class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" [formControlName]="field.name" sqxIndeterminateValue />
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</div>

0
frontend/app/features/content/shared/list/content-value-editor.component.scss

25
frontend/app/features/content/shared/list/content-value-editor.component.ts

@ -0,0 +1,25 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FieldDto } from '@app/shared';
@Component({
selector: 'sqx-content-value-editor',
styleUrls: ['./content-value-editor.component.scss'],
templateUrl: './content-value-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentValueEditorComponent {
@Input()
public field: FieldDto;
@Input()
public form: FormGroup;
}

6
frontend/app/features/content/shared/list/content-value.component.html

@ -0,0 +1,6 @@
<ng-container *ngIf="isPlain; else html">
<span class="truncate">{{value}}</span>
</ng-container>
<ng-template #html>
<span class="html-value" [innerHTML]="value.html"></span>
</ng-template>

14
frontend/app/features/content/shared/list/content-value.component.scss

@ -0,0 +1,14 @@
:ng-deep {
.html-value {
img {
margin-top: -25px;
max-height: 50px;
min-height: 50px;
position: absolute;
}
}
}
.html-value {
position: relative;
}

25
frontend/app/features/content/shared/list/content-value.component.ts

@ -0,0 +1,25 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { HtmlValue, Types } from '@app/shared';
@Component({
selector: 'sqx-content-value',
styleUrls: ['./content-value.component.scss'],
templateUrl: './content-value.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentValueComponent {
@Input()
public value: any;
public get isPlain() {
return !Types.is(this.value, HtmlValue);
}
}

8
frontend/app/features/content/shared/content.component.html → frontend/app/features/content/shared/list/content.component.html

@ -2,7 +2,7 @@
<td class="cell-select inline-edit" sqxStopClick> <td class="cell-select inline-edit" sqxStopClick>
<input type="checkbox" class="form-check" <input type="checkbox" class="form-check"
[ngModel]="selected" [ngModel]="selected"
(ngModelChange)="emitSelectedChange($event)" /> (ngModelChange)="selectedChange.emit($event)" />
<ng-container *ngIf="isDirty"> <ng-container *ngIf="isDirty">
<div class="edit-menu"> <div class="edit-menu">
@ -25,7 +25,7 @@
<ng-container *sqxModal="dropdown;closeAlways:true"> <ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> <div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade>
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> <a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="statusChange.emit(info.status)">
Change to Change to
<sqx-content-status <sqx-content-status
@ -35,14 +35,14 @@
small="true"> small="true">
</sqx-content-status> </sqx-content-status>
</a> </a>
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> <a class="dropdown-item" (click)="clone.emit(); dropdown.hide()" *ngIf="canClone">
Clone Clone
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete" <a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="emitDelete()" (sqxConfirmClick)="delete.emit()"
confirmTitle="Delete content" confirmTitle="Delete content"
confirmText="Do you really want to delete the content?"> confirmText="Do you really want to delete the content?">
Delete Delete

3
frontend/app/features/content/shared/content.component.scss → frontend/app/features/content/shared/list/content.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
.inline-edit { .inline-edit {
& { & {
position: relative; position: relative;

16
frontend/app/features/content/shared/content.component.ts → frontend/app/features/content/shared/list/content.component.ts

@ -125,20 +125,4 @@ export class ContentComponent implements OnChanges {
this.fields.forEach(x => x.reset()); this.fields.forEach(x => x.reset());
} }
public emitSelectedChange(isSelected: boolean) {
this.selectedChange.emit(isSelected);
}
public emitDelete() {
this.delete.emit();
}
public emitChangeStatus(status: string) {
this.statusChange.emit(status);
}
public emitClone() {
this.clone.emit();
}
} }

2
frontend/app/features/content/shared/preview-button.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

104
frontend/app/features/content/shared/reference-item.component.ts

@ -1,104 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import {
AppLanguageDto,
ContentDto,
getContentValue
} from '@app/shared';
/* tslint:disable:component-selector */
@Component({
selector: '[sqxReferenceItem]',
styleUrls: ['./reference-item.component.scss'],
template: `
<tr>
<td class="cell-select">
<ng-content></ng-content>
</td>
<td sqxContentListCell="meta.lastModifiedBy.avatar">
<sqx-content-list-field field="meta.lastModifiedBy.avatar" [content]="content" [language]="language"></sqx-content-list-field>
</td>
<td class="cell-auto cell-content" *ngFor="let value of values">
<sqx-content-value [value]="value"></sqx-content-value>
</td>
<td class="cell-label" *ngIf="!isCompact">
<span class="badge badge-pill truncate-inline badge-primary">{{content.schemaDisplayName}}</span>
</td>
<td class="cell-actions">
<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]="['../..', content.schemaName, 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>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReferenceItemComponent implements OnChanges {
@Output()
public delete = new EventEmitter();
@Input()
public language: AppLanguageDto;
@Input()
public isCompact = false;
@Input()
public columnCount = 0;
@Input('sqxReferenceItem')
public content: ContentDto;
public values: ReadonlyArray<any> = [];
public ngOnChanges(changes: SimpleChanges) {
this.updateValues();
}
public emitDelete() {
this.delete.emit();
}
private updateValues() {
const values = [];
for (let i = 0; i < this.columnCount; i++) {
const field = this.content.referenceFields[i];
if (field) {
const { formatted } = getContentValue(this.content, this.language, field);
values.push(formatted);
} else {
values.push('');
}
}
this.values = values;
}
}

18
frontend/app/features/content/shared/references/content-selector-item.component.html

@ -0,0 +1,18 @@
<tr (click)="toggle()">
<td class="cell-select" sqxStopClick>
<input type="checkbox" class="form-check"
[disabled]="!selectable"
[ngModel]="selected || !selectable"
(ngModelChange)="select($event)"
/>
</td>
<td sqxContentListCell="meta.lastModifiedBy.avatar">
<sqx-content-list-field field="meta.lastModifiedBy.avatar" [content]="content" [language]="language"></sqx-content-list-field>
</td>
<td *ngFor="let field of schema.defaultReferenceFields" [sqxContentListCell]="field">
<sqx-content-list-field [field]="field" [content]="content" [language]="language"></sqx-content-list-field>
</td>
</tr>
<tr class="spacer"></tr>

0
frontend/app/features/content/shared/references/content-selector-item.component.scss

52
frontend/app/features/content/shared/references/content-selector-item.component.ts

@ -0,0 +1,52 @@
/*
* 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 {
ContentDto,
LanguageDto,
SchemaDetailsDto
} from '@app/shared';
@Component({
selector: '[sqxContentSelectorItem]',
styleUrls: ['./content-selector-item.component.scss'],
templateUrl: './content-selector-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentSelectorItemComponent {
@Output()
public selectedChange = new EventEmitter<boolean>();
@Input()
public selected = false;
@Input()
public selectable = true;
@Input()
public language: LanguageDto;
@Input()
public schema: SchemaDetailsDto;
@Input('sqxContentSelectorItem')
public content: ContentDto;
public toggle() {
if (this.selectable) {
this.select(!this.selected);
}
}
public select(isSelected: boolean) {
this.selectedChange.emit(isSelected);
}
}

0
frontend/app/features/content/shared/contents-selector.component.html → frontend/app/features/content/shared/references/content-selector.component.html

3
frontend/app/features/content/shared/contents-selector.component.scss → frontend/app/features/content/shared/references/content-selector.component.scss

@ -1,6 +1,3 @@
@import '_vars';
@import '_mixins';
:host ::ng-deep { :host ::ng-deep {
.modal-body { .modal-body {
background: $color-background; background: $color-background;

8
frontend/app/features/content/shared/contents-selector.component.ts → frontend/app/features/content/shared/references/content-selector.component.ts

@ -24,14 +24,14 @@ import {
} from '@app/shared'; } from '@app/shared';
@Component({ @Component({
selector: 'sqx-contents-selector', selector: 'sqx-content-selector',
styleUrls: ['./contents-selector.component.scss'], styleUrls: ['./content-selector.component.scss'],
templateUrl: './contents-selector.component.html', templateUrl: './content-selector.component.html',
providers: [ providers: [
ManualContentsState ManualContentsState
] ]
}) })
export class ContentsSelectorComponent extends ResourceOwner implements OnInit { export class ContentSelectorComponent extends ResourceOwner implements OnInit {
@Output() @Output()
public select = new EventEmitter<ReadonlyArray<ContentDto>>(); public select = new EventEmitter<ReadonlyArray<ContentDto>>();

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

Loading…
Cancel
Save