mirror of https://github.com/Squidex/squidex.git
105 changed files with 1373 additions and 471 deletions
@ -1,20 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.0</TargetFramework> |
|||
<LangVersion>8.0</LangVersion> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" /> |
|||
<ProjectReference Include="..\..\src\Squidex.Web\Squidex.Web.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> |
|||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> |
|||
</ItemGroup> |
|||
<PropertyGroup> |
|||
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,48 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Schemas |
|||
{ |
|||
public sealed class FieldNames : ReadOnlyCollection<string> |
|||
{ |
|||
private static readonly List<string> EmptyNames = new List<string>(); |
|||
|
|||
public static readonly FieldNames Empty = new FieldNames(new List<string>()); |
|||
|
|||
public FieldNames(params string[] fields) |
|||
: base(fields?.ToList() ?? EmptyNames) |
|||
{ |
|||
} |
|||
|
|||
public FieldNames(IList<string> list) |
|||
: base(list ?? EmptyNames) |
|||
{ |
|||
} |
|||
|
|||
public FieldNames Add(string field) |
|||
{ |
|||
var list = this.ToList(); |
|||
|
|||
list.Add(field); |
|||
|
|||
return new FieldNames(list); |
|||
} |
|||
|
|||
public FieldNames Remove(string field) |
|||
{ |
|||
var list = this.ToList(); |
|||
|
|||
list.Remove(field); |
|||
|
|||
return new FieldNames(list); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Schemas |
|||
{ |
|||
[EventType(nameof(SchemaUIFieldsConfigured))] |
|||
public sealed class SchemaUIFieldsConfigured : SchemaEvent |
|||
{ |
|||
public FieldNames FieldsInLists { get; set; } |
|||
|
|||
public FieldNames FieldsInReferences { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Schemas.Models |
|||
{ |
|||
public sealed class ConfigureUIFieldsDto |
|||
{ |
|||
/// <summary>
|
|||
/// The name of fields that are used in content lists.
|
|||
/// </summary>
|
|||
public FieldNames? FieldsInLists { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The name of fields that are used in content references.
|
|||
/// </summary>
|
|||
public FieldNames? FieldsInReferences { get; set; } |
|||
|
|||
public ConfigureUIFields ToCommand() |
|||
{ |
|||
return SimpleMapper.Map(this, new ConfigureUIFields()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
<div class="field-list field-list-assigned" |
|||
#assignedList="cdkDropList" |
|||
cdkDropList |
|||
[cdkDropListData]="fieldsAdded" |
|||
[cdkDropListConnectedTo]="[availableList]" |
|||
(cdkDropListDropped)="drop($event)"> |
|||
|
|||
<label>Assigned fields</label> |
|||
|
|||
<div class="empty-hint" *ngIf="fieldsAdded.length === 0"> |
|||
<sqx-form-hint>{{emptyText}}</sqx-form-hint> |
|||
</div> |
|||
|
|||
<div *ngFor="let field of fieldsAdded" class="table-items-row" cdkDrag> |
|||
<i class="icon-drag2 drag-handle"></i> <span>{{field.displayName}}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="field-list field-list-available" |
|||
#availableList="cdkDropList" |
|||
cdkDropList |
|||
cdkDropListSortingDisabled |
|||
[cdkDropListData]="fieldsNotAdded" |
|||
[cdkDropListConnectedTo]="[assignedList]" |
|||
(cdkDropListDropped)="drop($event)"> |
|||
|
|||
<label>Unassigned fields</label> |
|||
|
|||
<div *ngFor="let field of fieldsNotAdded" class="table-items-row" cdkDrag> |
|||
<i class="icon-drag2 drag-handle"></i> <span>{{field.displayName}}</span> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,34 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.field-list { |
|||
& { |
|||
overflow-x: hidden; |
|||
overflow-y: auto; |
|||
padding: $panel-padding; |
|||
padding-top: 1rem; |
|||
} |
|||
|
|||
&-assigned { |
|||
@include absolute(0, 50%, 0, 0); |
|||
border-right: 1px solid $color-border; |
|||
} |
|||
|
|||
&-available { |
|||
@include absolute(0, 0, 0, 50%); |
|||
} |
|||
} |
|||
|
|||
.cdk-drop-list-dragging { |
|||
.empty-hint { |
|||
display: none; |
|||
} |
|||
} |
|||
|
|||
.drag-handle { |
|||
margin-right: 1rem; |
|||
} |
|||
|
|||
label { |
|||
margin-bottom: 1rem; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
// tslint:disable: readonly-array
|
|||
|
|||
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; |
|||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; |
|||
|
|||
import { FieldDto, SchemaDetailsDto } from '@app/shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-field-list', |
|||
styleUrls: ['./field-list.component.scss'], |
|||
templateUrl: './field-list.component.html', |
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class FieldListComponent implements OnChanges { |
|||
@Input() |
|||
public emptyText = ''; |
|||
|
|||
@Input() |
|||
public schema: SchemaDetailsDto; |
|||
|
|||
@Input() |
|||
public fieldNames: ReadonlyArray<string>; |
|||
|
|||
@Output() |
|||
public fieldNamesChange = new EventEmitter<ReadonlyArray<string>>(); |
|||
|
|||
public fieldsAdded: FieldDto[]; |
|||
public fieldsNotAdded: FieldDto[]; |
|||
|
|||
public ngOnChanges() { |
|||
this.fieldsAdded = this.fieldNames.map(n => this.schema.contentFields.find(y => y.name === n)!).filter(x => !!x); |
|||
this.fieldsNotAdded = this.schema.contentFields.filter(n => this.fieldNames.indexOf(n.name) < 0); |
|||
} |
|||
|
|||
public drop(event: CdkDragDrop<FieldDto[]>) { |
|||
if (event.previousContainer === event.container) { |
|||
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); |
|||
} else { |
|||
transferArrayItem( |
|||
event.previousContainer.data, |
|||
event.container.data, |
|||
event.previousIndex, |
|||
event.currentIndex); |
|||
} |
|||
|
|||
const newNames = this.fieldsAdded.map(x => x.name); |
|||
|
|||
this.fieldNamesChange.emit(newNames); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
<form class="inner-form" (ngSubmit)="saveSchema()"> |
|||
<div class="inner-header"> |
|||
<ul class="nav nav-tabs2"> |
|||
<li class="nav-item" *ngFor="let tab of selectableTabs"> |
|||
<a class="nav-link" [class.active]="selectedTab === tab" (click)="selectTab(tab)">{{tab}}</a> |
|||
</li> |
|||
</ul> |
|||
|
|||
<button type="submit" class="float-right btn btn-primary" [class.invisible]="!isEditable">Save</button> |
|||
</div> |
|||
|
|||
<div class="inner-main"> |
|||
<sqx-field-list [class.hidden]="selectedTab !== 'List Fields'" |
|||
emptyText="Drop field here or reorder them to show the fields in the content list. When no list field is defined, the first field is used." |
|||
[schema]="schema" |
|||
[fieldNames]="state.fieldsInLists" |
|||
(fieldNamesChange)="setFieldsInLists($event)"> |
|||
</sqx-field-list> |
|||
<sqx-field-list [class.hidden]="selectedTab !== 'Reference Fields'" |
|||
emptyText="Drop field here or reorder them to show the fields when referenced by another content. When no reference field is defined, the list fields are used instead." |
|||
[schema]="schema" |
|||
[fieldNames]="state.fieldsInReferences" |
|||
(fieldNamesChange)="setFieldsInReferences($event)"> |
|||
</sqx-field-list> |
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,21 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
:host, |
|||
.inner-form, |
|||
.inner-main { |
|||
@include flex-box; |
|||
@include flex-grow(1); |
|||
@include flex-direction(column); |
|||
} |
|||
|
|||
.inner-header, |
|||
.inner-main { |
|||
position: relative; |
|||
} |
|||
|
|||
.inner-header { |
|||
background: $color-theme-secondary; |
|||
border-bottom: 1px solid $color-border; |
|||
padding: 1rem $panel-padding; |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component, Input, OnChanges } from '@angular/core'; |
|||
|
|||
import { |
|||
DialogService, |
|||
SchemaDetailsDto, |
|||
SchemasState |
|||
} from '@app/shared'; |
|||
|
|||
type State = { fieldsInLists: ReadonlyArray<string>, fieldsInReferences: ReadonlyArray<string> }; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-schema-ui-form', |
|||
styleUrls: ['./schema-ui-form.component.scss'], |
|||
templateUrl: './schema-ui-form.component.html' |
|||
}) |
|||
export class SchemaUIFormComponent implements OnChanges { |
|||
@Input() |
|||
public schema: SchemaDetailsDto; |
|||
|
|||
public selectableTabs: ReadonlyArray<string> = ['List Fields', 'Reference Fields']; |
|||
public selectedTab = this.selectableTabs[0]; |
|||
|
|||
public isEditable = false; |
|||
|
|||
public state: State = { |
|||
fieldsInLists: [], |
|||
fieldsInReferences: [] |
|||
}; |
|||
|
|||
constructor( |
|||
private readonly dialogs: DialogService, |
|||
private readonly schemasState: SchemasState |
|||
) { |
|||
} |
|||
|
|||
public ngOnChanges() { |
|||
this.isEditable = this.schema.canUpdate; |
|||
|
|||
this.state = { |
|||
fieldsInLists: this.schema.fieldsInLists, |
|||
fieldsInReferences: this.schema.fieldsInReferences |
|||
}; |
|||
} |
|||
|
|||
public setFieldsInLists(names: ReadonlyArray<string>) { |
|||
this.state.fieldsInLists = names; |
|||
} |
|||
|
|||
public setFieldsInReferences(names: ReadonlyArray<string>) { |
|||
this.state.fieldsInReferences = names; |
|||
} |
|||
|
|||
public selectTab(tab: string) { |
|||
this.selectedTab = tab; |
|||
} |
|||
|
|||
public saveSchema() { |
|||
if (!this.isEditable) { |
|||
return; |
|||
} |
|||
|
|||
this.schemasState.configureUIFields(this.schema, this.state) |
|||
.subscribe(() => { |
|||
this.dialogs.notifyInfo('UI fields updated successfully.'); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-logo', |
|||
template: ` |
|||
<img [class.hidden]="!isLoading" class="loader" src="./images/loader-white.gif" /> |
|||
|
|||
<i [class.hidden]="isLoading" class="icon-logo"></i> |
|||
`,
|
|||
styles: [` |
|||
.loader { |
|||
position: absolute; top: 12px; left: 40px; |
|||
}` |
|||
], |
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class LogoComponent { |
|||
@Input() |
|||
public isLoading = false; |
|||
} |
|||
Binary file not shown.
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue