Browse Source

UI updated

pull/422/head
Sebastian 6 years ago
parent
commit
e455d2f7cc
  1. 19
      src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  2. 7
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  3. 7
      src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs
  4. 61
      src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  5. 22
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  6. 9
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  7. 44
      src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs
  8. 33
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs
  9. 2
      src/Squidex/app/features/content/declarations.ts
  10. 6
      src/Squidex/app/features/content/module.ts
  11. 100
      src/Squidex/app/features/content/shared/content-selector-item.component.ts
  12. 10
      src/Squidex/app/features/content/shared/content-value-editor.component.ts
  13. 2
      src/Squidex/app/features/content/shared/content.component.html
  14. 2
      src/Squidex/app/features/content/shared/content.component.ts
  15. 146
      src/Squidex/app/features/content/shared/contents-selector.component.html
  16. 11
      src/Squidex/app/features/content/shared/contents-selector.component.scss
  17. 45
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  18. 34
      src/Squidex/app/features/content/shared/field-editor.component.html
  19. 24
      src/Squidex/app/features/content/shared/reference-item.component.scss
  20. 104
      src/Squidex/app/features/content/shared/reference-item.component.ts
  21. 18
      src/Squidex/app/features/content/shared/references-editor.component.html
  22. 71
      src/Squidex/app/features/content/shared/references-editor.component.ts
  23. 8
      src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html
  24. 12
      src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts
  25. 3
      src/Squidex/app/features/settings/pages/workflows/workflow.component.ts
  26. 11
      src/Squidex/app/features/settings/pages/workflows/workflows-page.component.ts
  27. 4
      src/Squidex/app/shared/components/references-dropdown.component.ts
  28. 1
      src/Squidex/app/shared/internal.ts
  29. 2
      src/Squidex/app/shared/module.ts
  30. 6
      src/Squidex/app/shared/services/contents.service.spec.ts
  31. 20
      src/Squidex/app/shared/services/contents.service.ts
  32. 80
      src/Squidex/app/shared/services/schemas.service.ts
  33. 6
      src/Squidex/app/shared/services/schemas.types.ts
  34. 4
      src/Squidex/app/shared/state/contents.state.ts
  35. 2
      src/Squidex/app/shared/state/query.ts
  36. 16
      src/Squidex/app/shared/state/schema-tag-converter.ts
  37. 4
      src/Squidex/package-lock.json

19
src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -59,6 +59,25 @@ namespace Squidex.Domain.Apps.Core.Schemas
return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : Guid.Empty;
}
public static IEnumerable<RootField> ReferenceFields(this Schema schema)
{
var references = schema.Fields.Where(x => x.RawProperties.IsReferenceField);
if (references.Any())
{
return references;
}
references = schema.Fields.Where(x => x.RawProperties.IsListField);
if (references.Any())
{
return references;
}
return schema.Fields.Take(1);
}
public static IEnumerable<IField<ReferencesFieldProperties>> ResolvingReferences(this Schema schema)
{
return schema.Fields.OfType<IField<ReferencesFieldProperties>>()

7
src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents
@ -45,6 +46,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
public string StatusColor { get; set; }
public string SchemaName { get; set; }
public string SchemaDisplayName { get; set; }
public RootField[] ReferenceFields { get; set; }
public bool CanUpdate { get; set; }
public bool IsPending { get; set; }

7
src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs

@ -6,6 +6,7 @@
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents
{
@ -15,6 +16,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
string StatusColor { get; }
string SchemaName { get; }
string SchemaDisplayName { get; }
RootField[] ReferenceFields { get; }
StatusInfo[] Nexts { get; }
NamedContentData ReferenceData { get; }

61
src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -69,18 +69,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var result = SimpleMapper.Map(content, new ContentEntity());
await ResolveColorAsync(content, result, cache);
if (ShouldEnrichWithStatuses(context))
if (ShouldEnrich(context))
{
await ResolveNextsAsync(content, result, context);
await ResolveCanUpdateAsync(content, result);
}
await ResolveColorAsync(content, result, cache);
result.CacheDependencies = new HashSet<object>
{
appVersion
};
if (ShouldEnrichWithStatuses(context))
{
await ResolveNextsAsync(content, result, context);
await ResolveCanUpdateAsync(content, result);
}
}
results.Add(result);
}
@ -89,16 +87,31 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var schema = await ContentQuery.GetSchemaOrThrowAsync(context, group.Key.ToString());
var schemaIdentity = schema.Id.ToString();
var schemaVersion = schema.Version.ToString();
foreach (var content in group)
{
content.CacheDependencies.Add(schema.Id);
content.CacheDependencies.Add(schema.Version);
content.CacheDependencies = new HashSet<object>
{
schema.Id,
schema.Version
};
}
if (ShouldEnrichWithReferences(context))
if (ShouldEnrichWithSchema(context))
{
var referenceFields = schema.SchemaDef.ReferenceFields().ToArray();
var schemaName = schema.SchemaDef.Name;
var schemaDisplayName = schema.DisplayName();
foreach (var content in group)
{
content.ReferenceFields = referenceFields;
content.SchemaName = schemaName;
content.SchemaDisplayName = schemaDisplayName;
}
}
if (ShouldEnrich(context))
{
await ResolveReferencesAsync(schema, group, context);
}
@ -152,9 +165,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
content.CacheDependencies.Add(referencedSchema.Id);
content.CacheDependencies.Add(referencedSchema.Version);
var value =
formatted.GetOrAdd(reference,
x => x.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig));
var value = formatted.GetOrAdd(reference, x => Format(x, context, referencedSchema));
fieldReference.AddJsonValue(partitionValue.Key, value);
}
@ -175,6 +186,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
}
private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema)
{
return content.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig);
}
private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents)
{
var text = $"{referencedContents.Count} Reference(s)";
@ -242,12 +258,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
return info.Color;
}
private static bool ShouldEnrichWithSchema(Context context)
{
return context.IsFrontendClient;
}
private static bool ShouldEnrichWithStatuses(Context context)
{
return context.IsFrontendClient || context.IsResolveFlow();
}
private static bool ShouldEnrichWithReferences(Context context)
private static bool ShouldEnrich(Context context)
{
return context.IsFrontendClient && !context.IsNoEnrichment();
}

22
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -7,7 +7,9 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using NodaTime;
using Squidex.Areas.Api.Controllers.Schemas.Models;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities;
@ -84,6 +86,21 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary>
public string StatusColor { get; set; }
/// <summary>
/// The name of the schema.
/// </summary>
public string SchemaName { get; set; }
/// <summary>
/// The display name of the schema.
/// </summary>
public string SchemaDisplayName { get; set; }
/// <summary>
/// The reference fields.
/// </summary>
public FieldDto[] ReferenceFields { get; set; }
/// <summary>
/// The version of the content.
/// </summary>
@ -104,6 +121,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
response.DataDraft = content.DataDraft;
}
if (content.ReferenceFields != null)
{
response.ReferenceFields = content.ReferenceFields.Select(FieldDto.FromField).ToArray();
}
if (content.ScheduleJob != null)
{
response.ScheduleJob = SimpleMapper.Map(content.ScheduleJob, new ScheduleJobDto());

9
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -44,9 +44,14 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
Items = contents.Select(x => ContentDto.FromContent(context, x, controller)).ToArray()
};
await result.AssignStatusesAsync(workflow, schema);
if (schema != null)
{
await result.AssignStatusesAsync(workflow, schema);
result.CreateLinks(controller, schema.AppId.Name, schema.SchemaDef.Name);
}
return result.CreateLinks(controller, schema.AppId.Name, schema.SchemaDef.Name);
return result;
}
private async Task AssignStatusesAsync(IContentWorkflow workflow, ISchemaEntity schema)

44
src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs

@ -7,7 +7,10 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Squidex.Areas.Api.Controllers.Schemas.Models.Converters;
using Squidex.Areas.Api.Controllers.Schemas.Models.Fields;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Schemas.Models
@ -58,6 +61,47 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// </summary>
public List<NestedFieldDto> Nested { get; set; }
public static NestedFieldDto FromField(NestedField field)
{
var properties = FieldPropertiesDtoFactory.Create(field.RawProperties);
var result =
SimpleMapper.Map(field,
new NestedFieldDto
{
FieldId = field.Id,
Properties = properties
});
return result;
}
public static FieldDto FromField(RootField field)
{
var properties = FieldPropertiesDtoFactory.Create(field.RawProperties);
var result =
SimpleMapper.Map(field,
new FieldDto
{
FieldId = field.Id,
Properties = properties,
Partitioning = field.Partitioning.Key
});
if (field is IArrayField arrayField)
{
result.Nested = new List<NestedFieldDto>();
foreach (var nestedField in arrayField.Fields)
{
result.Nested.Add(FromField(nestedField));
}
}
return result;
}
public void CreateLinks(ApiController controller, string app, string schema, bool allowUpdate)
{
allowUpdate = allowUpdate && !IsLocked;

33
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs

@ -7,8 +7,6 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Squidex.Areas.Api.Controllers.Schemas.Models.Converters;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Reflection;
using Squidex.Shared;
@ -54,36 +52,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
foreach (var field in schema.SchemaDef.Fields)
{
var fieldPropertiesDto = FieldPropertiesDtoFactory.Create(field.RawProperties);
var fieldDto =
SimpleMapper.Map(field,
new FieldDto
{
FieldId = field.Id,
Properties = fieldPropertiesDto,
Partitioning = field.Partitioning.Key
});
if (field is IArrayField arrayField)
{
fieldDto.Nested = new List<NestedFieldDto>();
foreach (var nestedField in arrayField.Fields)
{
var nestedFieldPropertiesDto = FieldPropertiesDtoFactory.Create(nestedField.RawProperties);
var nestedFieldDto =
SimpleMapper.Map(nestedField,
new NestedFieldDto
{
FieldId = nestedField.Id,
Properties = nestedFieldPropertiesDto
});
fieldDto.Nested.Add(nestedFieldDto);
}
}
result.Fields.Add(fieldDto);
result.Fields.Add(FieldDto.FromField(field));
}
result.CreateLinks(controller, app);

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

@ -21,8 +21,10 @@ export * from './shared/content.component';
export * from './shared/content-status.component';
export * from './shared/content-value.component';
export * from './shared/content-value-editor.component';
export * from './shared/content-selector-item.component';
export * from './shared/contents-selector.component';
export * from './shared/due-time-selector.component';
export * from './shared/field-editor.component';
export * from './shared/preview-button.component';
export * from './shared/reference-item.component';
export * from './shared/references-editor.component';

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

@ -29,6 +29,7 @@ import {
ContentFieldComponent,
ContentHistoryPageComponent,
ContentPageComponent,
ContentSelectorItemComponent,
ContentsFiltersPageComponent,
ContentsPageComponent,
ContentsSelectorComponent,
@ -39,6 +40,7 @@ import {
FieldEditorComponent,
FieldLanguagesComponent,
PreviewButtonComponent,
ReferenceItemComponent,
ReferencesEditorComponent,
SchemasPageComponent
} from './declarations';
@ -110,10 +112,11 @@ const routes: Routes = [
ArrayItemComponent,
AssetsEditorComponent,
CommentsPageComponent,
ContentComponent,
ContentFieldComponent,
ContentHistoryPageComponent,
ContentComponent,
ContentPageComponent,
ContentSelectorItemComponent,
ContentsFiltersPageComponent,
ContentsPageComponent,
ContentsSelectorComponent,
@ -124,6 +127,7 @@ const routes: Routes = [
FieldEditorComponent,
FieldLanguagesComponent,
PreviewButtonComponent,
ReferenceItemComponent,
ReferencesEditorComponent,
SchemasPageComponent
]

100
src/Squidex/app/features/content/shared/content-selector-item.component.ts

@ -0,0 +1,100 @@
/*
* 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,
RootFieldDto
} from '@app/shared';
/* tslint:disable:component-selector */
@Component({
selector: '[sqxContentSelectorItem]',
template: `
<tr (click)="toggle()">
<td class="cell-select">
<input type="checkbox" class="form-check"
[disabled]="!selectable"
[ngModel]="selected || !selectable"
(ngModelChange)="emitSelectedChange($event)" />
</td>
<td class="cell-user">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<td class="cell-auto cell-content" *ngFor="let value of values">
<sqx-content-value [value]="value"></sqx-content-value>
</td>
<td class="cell-time">
<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>
</tr>
<tr class="spacer"></tr>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentSelectorItemComponent implements OnChanges {
@Output()
public selectedChange = new EventEmitter<boolean>();
@Input()
public selected = false;
@Input()
public selectable = true;
@Input()
public language: AppLanguageDto;
@Input()
public fields: RootFieldDto[];
@Input('sqxContentSelectorItem')
public content: ContentDto;
public values: any[] = [];
public ngOnChanges(changes: SimpleChanges) {
if (changes['content'] || changes['language']) {
this.updateValues();
}
}
public toggle() {
if (this.selectable) {
this.emitSelectedChange(!this.selected);
}
}
public emitSelectedChange(isSelected: boolean) {
this.selectedChange.emit(isSelected);
}
private updateValues() {
this.values = [];
for (let field of this.fields) {
const { formatted } = getContentValue(this.content, this.language, field);
this.values.push(formatted);
}
}
}

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

@ -16,20 +16,20 @@ import { FieldDto } from '@app/shared';
<div [formGroup]="form">
<ng-container [ngSwitch]="field.properties.fieldType">
<ng-container *ngSwitchCase="'Number'">
<ng-container [ngSwitch]="field.properties['editor']">
<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="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</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.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" />
</ng-container>
@ -39,13 +39,13 @@ import { FieldDto } from '@app/shared';
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</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.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControlName]="field.name" [threeStates]="!field.properties.isRequired"></sqx-toggle>
</ng-container>

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

@ -1,4 +1,4 @@
<tr [routerLink]="link" routerLinkActive="active">
<tr [routerLink]="link">
<td class="cell-select" sqxStopClick>
<ng-container *ngIf="!isReference; else referenceTemplate">
<input type="checkbox" class="form-check"

2
src/Squidex/app/features/content/shared/content.component.ts

@ -174,7 +174,7 @@ export class ContentComponent implements OnChanges {
}
}
public trackByField(field: FieldDto) {
public trackByField(index: number, field: FieldDto) {
return field.fieldId + this.schema.id;
}
}

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

@ -1,84 +1,96 @@
<sqx-modal-dialog (close)="emitComplete()" large="true" fullHeight="true" contentClass="grid">
<ng-container title>
Select contents
<div class="row">
<div class="col-selector">
<select class="form-control form-control-dark"
[ngModel]="schema?.id"
(ngModelChange)="selectSchema($event)">
<option *ngFor="let schema of schemasState.schemas | async; let i = index" [ngValue]="schema.id">
Select {{schema.displayName}}
</option>
</select>
</div>
</div>
</ng-container>
<ng-container tabs>
<div class="row no-gutters">
<div class="col-auto offset-lg-4">
<button type="button" class="btn btn-text-secondary" (click)="reload()">
<i class="icon-reset"></i> Refresh
</button>
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Search for content"
[query]="contentsState.contentsQuery | async"
[queryModel]="queryModel"
(queryChange)="search($event)">
</sqx-search-form>
</div>
<ng-container *ngIf="schema">
<div class="col-auto">
<button type="button" class="btn btn-text-secondary" (click)="reload()">
<i class="icon-reset"></i> Refresh
</button>
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Search for content"
[query]="contentsState.contentsQuery | async"
[queryModel]="queryModel"
(queryChange)="search($event)">
</sqx-search-form>
</div>
<div class="-auto pl-1" *ngIf="languages.length > 1">
<sqx-language-selector class="languages-buttons" (selectedLanguageChange)="selectLanguage($event)" [languages]="languages"></sqx-language-selector>
</div>
<div class="-auto pl-1" *ngIf="languages.length > 1">
<sqx-language-selector class="languages-buttons" (selectedLanguageChange)="selectLanguage($event)" [languages]="languages"></sqx-language-selector>
</div>
</ng-container>
</div>
</ng-container>
<ng-container content>
<div class="grid-header">
<table class="table table-items table-fixed" [style.minWidth]="minWidth" #header>
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-check" [ngModel]="selectedAll" (ngModelChange)="selectAll($event)" />
</th>
<th class="cell-user">
<sqx-table-header text="By"></sqx-table-header>
</th>
<th class="cell-content" *ngFor="let field of schema.referenceFields">
<sqx-table-header [text]="field.displayName"
[sortable]="field.properties.isSortable"
[field]="field"
[query]="contentsState.contentsQuery | async"
(queryChange)="search($event)"
[language]="language">
</sqx-table-header>
</th>
<th class="cell-time">
<sqx-table-header text="Updated"
[sortable]="true"
[sortable]="field.properties.isSortable"
[field]="'lastModified'"
[query]="contentsState.contentsQuery | async"
(queryChange)="search($event)"
[language]="language">
</sqx-table-header>
</th>
</tr>
</thead>
</table>
</div>
<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"
[sqxContent]="content"
[selectable]="!isItemAlreadySelected(content)"
[selected]="isItemSelected(content)"
(selectedChange)="selectContent(content)"
[language]="language"
[schema]="schema"
[schemaFields]="schema.referenceFields"
isReadOnly="true">
</tbody>
<ng-container *ngIf="schema">
<div class="grid-header">
<table class="table table-items table-fixed" [style.minWidth]="minWidth" #header>
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-check" [ngModel]="selectedAll" (ngModelChange)="selectAll($event)" />
</th>
<th class="cell-user">
<sqx-table-header text="By"></sqx-table-header>
</th>
<th class="cell-content" *ngFor="let field of schema.referenceFields">
<sqx-table-header [text]="field.displayName"
[sortable]="field.properties.isSortable"
[field]="field"
[query]="contentsState.contentsQuery | async"
(queryChange)="search($event)"
[language]="language">
</sqx-table-header>
</th>
<th class="cell-time">
<sqx-table-header text="Updated"
[sortable]="true"
[sortable]="field.properties.isSortable"
[field]="'lastModified'"
[query]="contentsState.contentsQuery | async"
(queryChange)="search($event)"
[language]="language">
</sqx-table-header>
</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="grid-footer">
<sqx-pager [pager]="contentsState.contentsPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</div>
<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"
[sqxContentSelectorItem]="content"
[fields]="schema.referenceFields"
[selectable]="!isItemAlreadySelected(content)"
[selected]="isItemSelected(content)"
(selectedChange)="selectContent(content)"
[language]="language">
</tbody>
</table>
</div>
</div>
<div class="grid-footer">
<sqx-pager [pager]="contentsState.contentsPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</div>
</ng-container>
</ng-container>
<ng-container footer>

11
src/Squidex/app/features/content/shared/contents-selector.component.scss

@ -11,6 +11,17 @@
}
}
.col-selector {
position: relative;
.form-control {
@include absolute(-.4rem, auto, auto, 0);
max-width: 300px;
min-width: 300px;
color: $color-dark-foreground;
}
}
.table-container {
display: inline-block;
padding: 0;

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

@ -15,7 +15,10 @@ import {
QueryModel,
queryModelFromSchema,
ResourceOwner,
SchemaDetailsDto
SchemaDetailsDto,
SchemaDto,
SchemasState,
Types
} from '@app/shared';
@Component({
@ -23,7 +26,8 @@ import {
styleUrls: ['./contents-selector.component.scss'],
templateUrl: './contents-selector.component.html',
providers: [
ManualContentsState
ManualContentsState,
SchemasState
]
})
export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
@ -42,7 +46,6 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
@Input()
public alreadySelected: ContentDto[];
@Input()
public schema: SchemaDetailsDto;
public queryModel: QueryModel;
@ -54,22 +57,44 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
public minWidth: string;
constructor(
public readonly contentsState: ManualContentsState
public readonly contentsState: ManualContentsState,
public readonly schemasState: SchemasState
) {
super();
}
public ngOnInit() {
this.minWidth = `${200 + (200 * this.schema.referenceFields.length)}px`;
this.own(
this.contentsState.statuses
.subscribe(() => {
this.updateModel();
}));
this.contentsState.schema = this.schema;
this.contentsState.load();
this.own(
this.schemasState.selectedSchema
.subscribe(schema => {
this.schema = schema;
this.minWidth = `${200 + (200 * schema.referenceFields.length)}px`;
this.contentsState.schema = schema;
this.contentsState.load();
this.updateModel();
}));
this.schemasState.load()
.subscribe(() => {
this.selectSchema(this.schemasState.snapshot.schemas.at(0));
});
}
public selectSchema(selected: string | SchemaDto) {
if (Types.is(selected, SchemaDto)) {
this.schemasState.select(selected.id).subscribe();
} else {
this.schemasState.select(selected).subscribe();
}
}
public reload() {
@ -136,7 +161,9 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
}
private updateModel() {
this.queryModel = queryModelFromSchema(this.schema, this.languages, this.contentsState.snapshot.statuses);
if (this.schema) {
this.queryModel = queryModelFromSchema(this.schema, this.languages, this.contentsState.snapshot.statuses);
}
}
public trackByContent(content: ContentDto): string {

34
src/Squidex/app/features/content/shared/field-editor.component.html

@ -34,7 +34,7 @@
<sqx-assets-editor [formControl]="control" [isCompact]="isCompact"></sqx-assets-editor>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControl]="control" [threeStates]="!field.properties.isRequired"></sqx-toggle>
</ng-container>
@ -44,7 +44,7 @@
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'DateTime'">
<sqx-date-time-editor enforceTime="true" [mode]="field.properties['editor']" [formControl]="control"></sqx-date-time-editor>
<sqx-date-time-editor enforceTime="true" [mode]="field.rawProperties.editor" [formControl]="control"></sqx-date-time-editor>
</ng-container>
<ng-container *ngSwitchCase="'Geolocation'">
<sqx-geolocation-editor [isCompact]="isCompact" [formControl]="control"></sqx-geolocation-editor>
@ -53,21 +53,21 @@
<sqx-json-editor [formControl]="control"></sqx-json-editor>
</ng-container>
<ng-container *ngSwitchCase="'Number'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControl]="control" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Stars'">
<sqx-stars [formControl]="control" [maximumStars]="field.properties['maxValue']"></sqx-stars>
<sqx-stars [formControl]="control" [maximumStars]="field.rawProperties.maxValue"></sqx-stars>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="control">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
<ng-container *ngSwitchCase="'Radio'">
<div class="form-check form-check-inline" *ngFor="let value of field.properties['allowedValues']">
<div class="form-check form-check-inline" *ngFor="let value of field.rawProperties.allowedValues">
<input class="form-check-input" type="radio" [value]="value" [formControl]="control" [name]="uniqueId" />
<label class="form-check-label">
{{value}}
@ -77,14 +77,14 @@
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'References'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'List'">
<sqx-references-editor
[formControl]="control"
[allowDuplicates]="field.properties['allowDuplicated']"
[allowDuplicates]="field.rawProperties.allowDuplicated"
[language]="language"
[languages]="languages"
[schemaId]="field.properties['schemaId']"
[schemaIds]="field.rawProperties.schemaIds"
[isCompact]="isCompact">
</sqx-references-editor>
</ng-container>
@ -92,13 +92,13 @@
<sqx-references-dropdown
[formControl]="control"
[language]="language"
[schemaId]="field.properties['schemaId']">
[schemaId]="field.rawProperties.singleId">
</sqx-references-dropdown>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControl]="control" [placeholder]="field.displayPlaceholder" />
</ng-container>
@ -120,11 +120,11 @@
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="control">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
<ng-container *ngSwitchCase="'Radio'">
<div class="form-check form-check-inline" *ngFor="let value of field.properties['allowedValues']">
<div class="form-check form-check-inline" *ngFor="let value of field.rawProperties.allowedValues">
<input class="form-check-input" type="radio" value="{{value}}" [formControl]="control" [name]="uniqueId" />
<label class="form-check-label">
{{value}}
@ -137,16 +137,16 @@
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'Tags'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container [ngSwitch]="field.rawProperties.editor">
<ng-container *ngSwitchCase="'Tags'">
<sqx-tag-editor [formControl]="control" [placeholder]="field.displayPlaceholder" [suggestions]="field.properties['allowedValues']"></sqx-tag-editor>
<sqx-tag-editor [formControl]="control" [placeholder]="field.displayPlaceholder" [suggestions]="field.rawProperties.allowedValues"></sqx-tag-editor>
</ng-container>
<ng-container *ngSwitchCase="'Checkboxes'">
<sqx-checkbox-group [formControl]="control" [values]="field.properties['allowedValues']"></sqx-checkbox-group>
<sqx-checkbox-group [formControl]="control" [values]="field.rawProperties.allowedValues"></sqx-checkbox-group>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select multiple class="form-control" [formControl]="control">
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option>
<option *ngFor="let value of field.rawProperties.allowedValues" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
</ng-container>

24
src/Squidex/app/features/content/shared/reference-item.component.scss

@ -0,0 +1,24 @@
@import '_vars';
@import '_mixins';
.reference-edit {
& {
position: relative;
}
&:hover {
.reference-menu {
display: block;
}
}
.reference-menu {
@include absolute(0, -.25rem, auto, auto);
display: none;
padding-left: 2rem;
min-height: 2.4rem;
max-height: 2.4rem;
white-space: nowrap;
background: $color-table-background;
}
}

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

@ -0,0 +1,104 @@
/*
* 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">
<i class="icon-drag2 drag-handle"></i>
</td>
<td class="cell-user" *ngIf="!isCompact">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<td class="cell-auto cell-content" *ngFor="let value of values">
<sqx-content-value [value]="value"></sqx-content-value>
</td>
<td class="cell-time" *ngIf="!isCompact">
<span class="badge badge-pill truncate 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: any[] = [];
public ngOnChanges(changes: SimpleChanges) {
if (changes['content'] || changes['language']) {
this.updateValues();
}
}
public emitDelete() {
this.delete.emit();
}
private updateValues() {
this.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);
this.values.push(formatted);
} else {
this.values.push('');
}
}
}
}

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

@ -1,30 +1,23 @@
<div class="references-container" [class.disabled]="snapshot.isDisabled">
<ng-container *ngIf="snapshot.schema; let schema">
<ng-container>
<div class="drop-area-container">
<div class="drop-area" (click)="selectorDialog.show()">
Click here to link content items.
</div>
</div>
<table class="table table-items table-fixed" [class.disabled]="snapshot.isDisabled" *ngIf="schema && snapshot.contentItems && snapshot.contentItems.length > 0"
[sqxSortModel]="snapshot.contentItems.mutableValues"
<table class="table table-items table-fixed" [class.disabled]="snapshot.isDisabled" *ngIf="snapshot.contentItems && snapshot.contentItems.length > 0"
[sqxSortModel]="snapshot.contentItems"
(sqxSort)="sort($event)">
<tbody *ngFor="let content of snapshot.contentItems; trackBy: trackByContent"
[sqxContent]="content"
[sqxReferenceItem]="content"
[language]="language"
[isReadOnly]="true"
[isReference]="true"
[isCompact]="isCompact"
[schema]="schema"
[schemaFields]="schema.referenceFields"
[columnCount]="snapshot.columnCount"
(delete)="remove(content)">
</tbody>
</table>
</ng-container>
<div class="invalid" *ngIf="snapshot.schemaInvalid">
Schema not found or not configured yet.
</div>
</div>
<ng-container *sqxModal="selectorDialog;closeAuto:false">
@ -33,7 +26,6 @@
[alreadySelected]="snapshot.contentItems"
[language]="language"
[languages]="languages"
[schema]="snapshot.schema"
(select)="select($event)">
</sqx-contents-selector>
</ng-container>

71
src/Squidex/app/features/content/shared/references-editor.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import {
@ -14,10 +14,6 @@ import {
ContentDto,
ContentsService,
DialogModel,
ImmutableArray,
MathHelper,
SchemaDetailsDto,
SchemasService,
StatefulControlComponent,
Types
} from '@app/shared';
@ -27,10 +23,9 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
};
interface State {
schema?: SchemaDetailsDto | null;
schemaInvalid: boolean;
contentItems: ContentDto[];
contentItems: ImmutableArray<ContentDto>;
columnCount: number;
}
@Component({
@ -40,9 +35,9 @@ interface State {
providers: [SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReferencesEditorComponent extends StatefulControlComponent<State, string[]> implements OnInit {
export class ReferencesEditorComponent extends StatefulControlComponent<State, string[]> {
@Input()
public schemaId: string;
public schemaIds: string[];
@Input()
public language: AppLanguageDto;
@ -56,63 +51,51 @@ export class ReferencesEditorComponent extends StatefulControlComponent<State, s
@Input()
public allowDuplicates = true;
@Input()
public columnCount = 0;
public selectorDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly contentsService: ContentsService,
private readonly schemasService: SchemasService
private readonly contentsService: ContentsService
) {
super(changeDetector, {
schemaInvalid: false,
schema: null,
contentItems: ImmutableArray.empty()
});
}
public ngOnInit() {
if (this.schemaId === MathHelper.EMPTY_GUID) {
this.next(s => ({ ...s, schemaInvalid: true }));
return;
}
this.schemasService.getSchema(this.appsState.appName, this.schemaId)
.subscribe(schema => {
this.next(s => ({ ...s, schema }));
}, () => {
this.next(s => ({ ...s, schemaInvalid: true }));
});
super(changeDetector, { contentItems: [], columnCount: 0 });
}
public writeValue(obj: any) {
if (Types.isArrayOfString(obj)) {
if (!Types.isEquals(obj, this.snapshot.contentItems.map(x => x.id).values)) {
if (!Types.isEquals(obj, this.snapshot.contentItems.map(x => x.id))) {
const contentIds: string[] = obj;
this.contentsService.getContents(this.appsState.appName, this.schemaId, 10000, 0, undefined, contentIds)
this.contentsService.getContentsByIds(this.appsState.appName, contentIds)
.subscribe(dtos => {
this.setContentItems(ImmutableArray.of(contentIds.map(id => dtos.items.find(c => c.id === id)!).filter(r => !!r)));
this.setContentItems(contentIds.map(id => dtos.items.find(c => c.id === id)!).filter(r => !!r));
if (this.snapshot.contentItems.length !== contentIds.length) {
this.updateValue();
}
}, () => {
this.setContentItems(ImmutableArray.empty());
this.setContentItems([]);
});
}
} else {
this.setContentItems(ImmutableArray.empty());
this.setContentItems([]);
}
}
public setContentItems(contentItems: ImmutableArray<ContentDto>) {
this.next(s => ({ ...s, contentItems }));
public setContentItems(contentItems: ContentDto[]) {
let columnCount = 0;
for (let content of contentItems) {
columnCount = Math.max(columnCount, content.referenceFields.length);
}
this.next(s => ({ ...s, contentItems, columnCount }));
}
public select(contents: ContentDto[]) {
for (let content of contents) {
this.setContentItems(this.snapshot.contentItems.push(content));
}
this.setContentItems([...this.snapshot.contentItems, ...contents]);
if (contents.length > 0) {
this.updateValue();
@ -123,7 +106,7 @@ export class ReferencesEditorComponent extends StatefulControlComponent<State, s
public remove(content: ContentDto) {
if (content) {
this.setContentItems(this.snapshot.contentItems.remove(content));
this.setContentItems(this.snapshot.contentItems.filter(x => x.id !== content.id));
this.updateValue();
}
@ -131,14 +114,14 @@ export class ReferencesEditorComponent extends StatefulControlComponent<State, s
public sort(contents: ContentDto[]) {
if (contents) {
this.setContentItems(ImmutableArray.of(contents));
this.setContentItems(contents);
this.updateValue();
}
}
private updateValue() {
let ids: string[] | null = this.snapshot.contentItems.values.map(x => x.id);
let ids: string[] | null = this.snapshot.contentItems.map(x => x.id);
if (ids.length === 0) {
ids = null;

8
src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html

@ -1,11 +1,11 @@
<div [formGroup]="editForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldSchemaId">Schema</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldSchemaId">Schemas</label>
<div class="col-6">
<select class="form-control" id="{{field.fieldId}}_fieldSchemaId" formControlName="schemaId">
<option *ngFor="let schema of schemasState.schemas | async" [ngValue]="schema.id">{{schema.displayName}}</option>
</select>
<sqx-tag-editor placeholder=", to add schema" formControlName="schemaIds"
[converter]="schemasSource" [suggestedValues]="schemasSource.suggestions">
</sqx-tag-editor>
</div>
</div>

12
src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts

@ -8,7 +8,11 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { FieldDto, ReferencesFieldPropertiesDto, SchemasState } from '@app/shared';
import {
FieldDto,
ReferencesFieldPropertiesDto,
SchemaTagConverter
} from '@app/shared';
@Component({
selector: 'sqx-references-validation',
@ -26,7 +30,7 @@ export class ReferencesValidationComponent implements OnInit {
public properties: ReferencesFieldPropertiesDto;
constructor(
public readonly schemasState: SchemasState
public readonly schemasSource: SchemaTagConverter
) {
}
@ -40,8 +44,8 @@ export class ReferencesValidationComponent implements OnInit {
this.editForm.setControl('minItems',
new FormControl(this.properties.minItems));
this.editForm.setControl('schemaId',
new FormControl(this.properties.schemaId, [
this.editForm.setControl('schemaIds',
new FormControl(this.properties.schemaIds, [
Validators.required
]));
}

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

@ -11,6 +11,7 @@ import {
ErrorDto,
MathHelper,
RoleDto,
SchemaTagConverter,
WorkflowDto,
WorkflowsState,
WorkflowStep,
@ -19,8 +20,6 @@ import {
WorkflowTransitionValues
} from '@app/shared';
import { SchemaTagConverter } from './schema-tag-converter';
@Component({
selector: 'sqx-workflow',
styleUrls: ['./workflow.component.scss'],

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

@ -9,24 +9,20 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import {
RolesState,
SchemasState,
SchemaTagConverter,
WorkflowDto,
WorkflowsState
} from '@app/shared';
import { SchemaTagConverter } from './schema-tag-converter';
@Component({
selector: 'sqx-workflows-page',
styleUrls: ['./workflows-page.component.scss'],
templateUrl: './workflows-page.component.html'
})
export class WorkflowsPageComponent implements OnInit, OnDestroy {
public schemasSource: SchemaTagConverter;
constructor(
public readonly rolesState: RolesState,
public readonly schemasState: SchemasState,
public readonly schemasSource: SchemaTagConverter,
public readonly workflowsState: WorkflowsState
) {
}
@ -34,8 +30,7 @@ export class WorkflowsPageComponent implements OnInit, OnDestroy {
public ngOnInit() {
this.rolesState.load();
this.schemasSource = new SchemaTagConverter(this.schemasState);
this.schemasState.load();
this.schemasSource.load();
this.workflowsState.load();
}

4
src/Squidex/app/shared/components/references-dropdown.component.ts

@ -16,7 +16,6 @@ import {
ContentDto,
ContentsService,
getContentValue,
MathHelper,
SchemaDetailsDto,
SchemasService,
StatefulControlComponent,
@ -112,7 +111,7 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
}
public ngOnInit() {
if (this.schemaId === MathHelper.EMPTY_GUID) {
if (!this.schemaId) {
this.selectionControl.disable();
return;
}
@ -171,6 +170,7 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
schema.referenceFields
.map(f => getContentValue(content, this.languageField, f, false))
.map(v => v.formatted)
.filter(v => !!v)
.join(', ');
return { name, id: content.id };

1
src/Squidex/app/shared/internal.ts

@ -61,6 +61,7 @@ export * from './state/roles.forms';
export * from './state/roles.state';
export * from './state/rule-events.state';
export * from './state/rules.state';
export * from './state/schema-tag-converter';
export * from './state/schemas.forms';
export * from './state/schemas.state';
export * from './state/ui.state';

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

@ -105,6 +105,7 @@ import {
WorkflowsService,
WorkflowsState
} from './declarations';
import { SchemaTagConverter } from './state/schema-tag-converter';
@NgModule({
imports: [
@ -236,6 +237,7 @@ export class SqxSharedModule {
SchemaMustNotBeSingletonGuard,
SchemasService,
SchemasState,
SchemaTagConverter,
TranslationsService,
UIService,
UIState,

6
src/Squidex/app/shared/services/contents.service.spec.ts

@ -367,7 +367,10 @@ describe('ContentsService', () => {
isPending: true,
data: {},
dataDraft: {},
schemaName: 'my-schema',
schemaDisplayName: 'MySchema',
referenceData: {},
referenceFields: [],
version: `${id}`,
_links: {
update: { method: 'PUT', href: `/contents/id${id}` }
@ -391,6 +394,9 @@ export function createContent(id: number, suffix = '') {
true,
{},
{},
'my-schema',
'MySchema',
{},
[],
new Version(`${id}`));
}

20
src/Squidex/app/shared/services/contents.service.ts

@ -27,6 +27,8 @@ import {
import { encodeQuery, Query } from './../state/query';
import { parseField, RootFieldDto } from './schemas.service';
export class ScheduleDto {
constructor(
public readonly status: string,
@ -86,7 +88,10 @@ export class ContentDto {
public readonly isPending: boolean,
public readonly data: ContentData | undefined,
public readonly dataDraft: ContentData,
public readonly schemaName: string,
public readonly schemaDisplayName: string,
public readonly referenceData: ContentReferences,
public readonly referenceFields: RootFieldDto[],
public readonly version: Version
) {
this._links = links;
@ -155,6 +160,18 @@ export class ContentsService {
pretifyError('Failed to load contents. Please reload.'));
}
public getContentsByIds(appName: string, ids: string[]): Observable<ContentsDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/?ids=${ids.join(',')}`);
return this.http.get<{ total: number, items: [], statuses: StatusInfo[] } & Resource>(url).pipe(
map(({ total, items, statuses, _links }) => {
const contents = items.map(x => parseContent(x));
return new ContentsDto(statuses, total, contents, _links);
}),
pretifyError('Failed to load contents. Please reload.'));
}
public getContent(appName: string, schemaName: string, id: string): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
@ -307,6 +324,9 @@ function parseContent(response: any) {
response.isPending === true,
response.data,
response.dataDraft,
response.schemaName,
response.schemaDisplayName,
response.referenceData,
response.referenceFields.map((item: any) => parseField(item)),
new Version(response.version.toString()));
}

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

@ -182,7 +182,7 @@ export class FieldDto {
public readonly canUpdate: boolean;
public get isInlineEditable(): boolean {
return !this.isDisabled && this.properties['inlineEditable'] === true;
return !this.isDisabled && this.rawProperties.inlineEditable === true;
}
public get displayName() {
@ -193,6 +193,10 @@ export class FieldDto {
return this.properties.placeholder || '';
}
public get rawProperties(): any {
return this.properties;
}
constructor(links: ResourceLinks,
public readonly fieldId: number,
public readonly name: string,
@ -608,42 +612,7 @@ function parseSchemas(response: any) {
}
function parseSchemaWithDetails(response: any) {
const fields = response.fields.map((item: any) => {
const propertiesDto =
createProperties(
item.properties.fieldType,
item.properties);
let nested: NestedFieldDto[] | null = null;
if (item.nested && item.nested.length > 0) {
nested = item.nested.map((nestedItem: any) => {
const nestedPropertiesDto =
createProperties(
nestedItem.properties.fieldType,
nestedItem.properties);
return new NestedFieldDto(nestedItem._links,
nestedItem.fieldId,
nestedItem.name,
nestedPropertiesDto,
item.fieldId,
nestedItem.isLocked,
nestedItem.isHidden,
nestedItem.isDisabled);
});
}
return new RootFieldDto(item._links,
item.fieldId,
item.name,
propertiesDto,
item.partitioning,
item.isLocked,
item.isHidden,
item.isDisabled,
nested || []);
});
const fields = response.fields.map((item: any) => parseField(item));
const properties = new SchemaPropertiesDto(response.properties.label, response.properties.hints);
@ -661,3 +630,40 @@ function parseSchemaWithDetails(response: any) {
response.scripts || {},
response.previewUrls || {});
}
export function parseField(item: any) {
const propertiesDto =
createProperties(
item.properties.fieldType,
item.properties);
let nested: NestedFieldDto[] | null = null;
if (item.nested && item.nested.length > 0) {
nested = item.nested.map((nestedItem: any) => {
const nestedPropertiesDto =
createProperties(
nestedItem.properties.fieldType,
nestedItem.properties);
return new NestedFieldDto(nestedItem._links,
nestedItem.fieldId,
nestedItem.name,
nestedPropertiesDto,
item.fieldId,
nestedItem.isLocked,
nestedItem.isHidden,
nestedItem.isDisabled);
});
}
return new RootFieldDto(item._links,
item.fieldId,
item.name,
propertiesDto,
item.partitioning,
item.isLocked,
item.isHidden,
item.isDisabled,
nested || []);
}

6
src/Squidex/app/shared/services/schemas.types.ts

@ -294,7 +294,11 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
public readonly maxItems?: number;
public readonly minItems?: number;
public readonly resolveReference?: boolean;
public readonly schemaId?: string;
public readonly schemaIds?: string[];
public get singleId() {
return this.schemaIds && this.schemaIds.length === 1 ? this.schemaIds[0] : null;
}
public get isSortable() {
return false;

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

@ -144,6 +144,10 @@ export abstract class ContentsStateBase extends State<Snapshot> {
}
private loadInternalCore(isReload = false) {
if (!this.appName) {
return empty();
}
return this.contentsService.getContents(this.appName, this.schemaName,
this.snapshot.contentsPager.pageSize,
this.snapshot.contentsPager.skip,

2
src/Squidex/app/shared/state/query.ts

@ -200,7 +200,7 @@ export function queryModelFromSchema(schema: SchemaDetailsDto, languages: Langua
} else if (field.properties.fieldType === 'DateTime') {
type = TypeDateTime;
} else if (field.properties.fieldType === 'References') {
const extra = field.properties['schemaId'];
const extra = field.rawProperties.singleId;
type = { ...TypeReference, extra };
}

16
src/Squidex/app/features/settings/pages/workflows/schema-tag-converter.ts → src/Squidex/app/shared/state/schema-tag-converter.ts

@ -5,15 +5,15 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { Subscription } from 'rxjs';
import {
Converter,
SchemaDto,
SchemasState,
TagValue
} from '@app/shared';
import { Converter, TagValue } from '@app/framework';
import { SchemaDto } from './../services/schemas.service';
import { SchemasState } from './schemas.state';
@Injectable()
export class SchemaTagConverter implements Converter {
private schemasSubscription: Subscription;
private schemas: SchemaDto[] = [];
@ -33,6 +33,10 @@ export class SchemaTagConverter implements Converter {
});
}
public load() {
this.schemasState.load();
}
public destroy() {
this.schemasSubscription.unsubscribe();
}

4
src/Squidex/package-lock.json

@ -9356,7 +9356,7 @@
},
"node-fetch": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
},
"node-forge": {
@ -16813,7 +16813,7 @@
},
"whatwg-fetch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
},
"which": {

Loading…
Cancel
Save