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