mirror of https://github.com/Squidex/squidex.git
538 changed files with 2955 additions and 3008 deletions
@ -1,213 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
|
|||
#pragma warning disable IDE0044 // Add readonly modifier
|
|||
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
|||
|
|||
namespace Squidex.Infrastructure.Collections |
|||
{ |
|||
public class ArrayDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue> where TKey : notnull |
|||
{ |
|||
private static readonly KeyValuePair<TKey, TValue>[] EmptyItems = Array.Empty<KeyValuePair<TKey, TValue>>(); |
|||
private readonly IEqualityComparer<TKey> keyComparer; |
|||
private KeyValuePair<TKey, TValue>[] items; |
|||
|
|||
public TValue this[TKey key] |
|||
{ |
|||
get |
|||
{ |
|||
if (!TryGetValue(key, out var value)) |
|||
{ |
|||
throw new KeyNotFoundException(); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
|
|||
public IEnumerable<TKey> Keys |
|||
{ |
|||
get { return items.Select(x => x.Key); } |
|||
} |
|||
|
|||
public IEnumerable<TValue> Values |
|||
{ |
|||
get { return items.Select(x => x.Value); } |
|||
} |
|||
|
|||
public int Count |
|||
{ |
|||
get { return items.Length; } |
|||
} |
|||
|
|||
public ArrayDictionary() |
|||
: this(EqualityComparer<TKey>.Default, EmptyItems) |
|||
{ |
|||
} |
|||
|
|||
public ArrayDictionary(KeyValuePair<TKey, TValue>[] items) |
|||
: this(EqualityComparer<TKey>.Default, items) |
|||
{ |
|||
} |
|||
|
|||
public ArrayDictionary(IEqualityComparer<TKey> keyComparer, KeyValuePair<TKey, TValue>[] items) |
|||
{ |
|||
Guard.NotNull(items); |
|||
Guard.NotNull(keyComparer); |
|||
|
|||
this.items = items; |
|||
|
|||
this.keyComparer = keyComparer; |
|||
} |
|||
|
|||
public bool IsUnchanged(KeyValuePair<TKey, TValue>[] values) |
|||
{ |
|||
return ReferenceEquals(values, items); |
|||
} |
|||
|
|||
public ArrayDictionary<TKey, TValue> With(TKey key, TValue value, IEqualityComparer<TValue>? valueComparer = null) |
|||
{ |
|||
return With<ArrayDictionary<TKey, TValue>>(key, value, valueComparer); |
|||
} |
|||
|
|||
public TArray With<TArray>(TKey key, TValue value, IEqualityComparer<TValue>? valueComparer = null) where TArray : ArrayDictionary<TKey, TValue> |
|||
{ |
|||
var index = IndexOf(key); |
|||
|
|||
if (index < 0) |
|||
{ |
|||
var result = new KeyValuePair<TKey, TValue>[items.Length + 1]; |
|||
|
|||
Array.Copy(items, 0, result, 0, items.Length); |
|||
|
|||
result[^1] = new KeyValuePair<TKey, TValue>(key, value); |
|||
|
|||
return Create<TArray>(result); |
|||
} |
|||
|
|||
var existing = items[index].Value; |
|||
|
|||
if (valueComparer == null || !valueComparer.Equals(value, existing)) |
|||
{ |
|||
var result = new KeyValuePair<TKey, TValue>[items.Length]; |
|||
|
|||
Array.Copy(items, 0, result, 0, items.Length); |
|||
|
|||
result[index] = new KeyValuePair<TKey, TValue>(key, value); |
|||
|
|||
return Create<TArray>(result); |
|||
} |
|||
|
|||
return Self<TArray>(); |
|||
} |
|||
|
|||
public ArrayDictionary<TKey, TValue> Without(TKey key) |
|||
{ |
|||
return Without<ArrayDictionary<TKey, TValue>>(key); |
|||
} |
|||
|
|||
public TArray Without<TArray>(TKey key) where TArray : ArrayDictionary<TKey, TValue> |
|||
{ |
|||
var index = IndexOf(key); |
|||
|
|||
if (index < 0) |
|||
{ |
|||
return Self<TArray>(); |
|||
} |
|||
|
|||
if (Count == 1) |
|||
{ |
|||
return Create<TArray>(EmptyItems); |
|||
} |
|||
|
|||
var result = new KeyValuePair<TKey, TValue>[items.Length - 1]; |
|||
|
|||
Array.Copy(items, 0, result, 0, index); |
|||
Array.Copy(items, index + 1, result, index, items.Length - index - 1); |
|||
|
|||
return Create<TArray>(result); |
|||
} |
|||
|
|||
private TArray Self<TArray>() where TArray : ArrayDictionary<TKey, TValue> |
|||
{ |
|||
return (this as TArray)!; |
|||
} |
|||
|
|||
private TArray Create<TArray>(KeyValuePair<TKey, TValue>[] newItems) where TArray : ArrayDictionary<TKey, TValue> |
|||
{ |
|||
if (ReferenceEquals(items, newItems)) |
|||
{ |
|||
return Self<TArray>(); |
|||
} |
|||
|
|||
var newClone = (TArray)MemberwiseClone(); |
|||
|
|||
newClone.items = newItems; |
|||
|
|||
return newClone; |
|||
} |
|||
|
|||
public bool ContainsKey(TKey key) |
|||
{ |
|||
var index = IndexOf(key); |
|||
|
|||
return index >= 0; |
|||
} |
|||
|
|||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) |
|||
{ |
|||
var index = IndexOf(key); |
|||
|
|||
if (index >= 0) |
|||
{ |
|||
value = items[index].Value; |
|||
|
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
value = default!; |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
private int IndexOf(TKey key) |
|||
{ |
|||
for (var i = 0; i < items.Length; i++) |
|||
{ |
|||
if (keyComparer.Equals(items[i].Key, key)) |
|||
{ |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() |
|||
{ |
|||
return GetEnumerable(items).GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return items.GetEnumerator(); |
|||
} |
|||
|
|||
private static IEnumerable<T2> GetEnumerable<T2>(IEnumerable<T2> array) |
|||
{ |
|||
return array; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
#pragma warning disable IDE0044 // Add readonly modifier
|
|||
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
|||
|
|||
namespace Squidex.Infrastructure.Collections |
|||
{ |
|||
public class ImmutableDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue> where TKey : notnull |
|||
{ |
|||
private static readonly Dictionary<TKey, TValue> EmptyInner = new Dictionary<TKey, TValue>(); |
|||
private Dictionary<TKey, TValue> inner; |
|||
|
|||
public TValue this[TKey key] |
|||
{ |
|||
get |
|||
{ |
|||
if (!TryGetValue(key, out var value)) |
|||
{ |
|||
throw new KeyNotFoundException(); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
|
|||
public IEnumerable<TKey> Keys |
|||
{ |
|||
get { return inner.Keys; } |
|||
} |
|||
|
|||
public IEnumerable<TValue> Values |
|||
{ |
|||
get { return inner.Values; } |
|||
} |
|||
|
|||
public int Count |
|||
{ |
|||
get { return inner.Count; } |
|||
} |
|||
|
|||
public ImmutableDictionary() |
|||
: this(EmptyInner) |
|||
{ |
|||
} |
|||
|
|||
public ImmutableDictionary(Dictionary<TKey, TValue> inner) |
|||
{ |
|||
Guard.NotNull(inner); |
|||
|
|||
this.inner = inner; |
|||
} |
|||
|
|||
public ImmutableDictionary<TKey, TValue> With(TKey key, TValue value, IEqualityComparer<TValue>? valueComparer = null) |
|||
{ |
|||
return With<ImmutableDictionary<TKey, TValue>>(key, value, valueComparer); |
|||
} |
|||
|
|||
public TArray With<TArray>(TKey key, TValue value, IEqualityComparer<TValue>? valueComparer = null) where TArray : ImmutableDictionary<TKey, TValue> |
|||
{ |
|||
if (!TryGetValue(key, out var found) || !IsEqual(value, found, valueComparer)) |
|||
{ |
|||
var clone = new Dictionary<TKey, TValue>(inner) |
|||
{ |
|||
[key] = value |
|||
}; |
|||
|
|||
return Create<TArray>(clone); |
|||
} |
|||
|
|||
return Self<TArray>(); |
|||
} |
|||
|
|||
private static bool IsEqual(TValue lhs, TValue rhs, IEqualityComparer<TValue>? comparer = null) |
|||
{ |
|||
return comparer == null || comparer.Equals(lhs, rhs); |
|||
} |
|||
|
|||
public ImmutableDictionary<TKey, TValue> Without(TKey key) |
|||
{ |
|||
return Without<ImmutableDictionary<TKey, TValue>>(key); |
|||
} |
|||
|
|||
public TArray Without<TArray>(TKey key) where TArray : ImmutableDictionary<TKey, TValue> |
|||
{ |
|||
if (!inner.ContainsKey(key)) |
|||
{ |
|||
return Self<TArray>(); |
|||
} |
|||
|
|||
if (Count == 1) |
|||
{ |
|||
return Create<TArray>(EmptyInner); |
|||
} |
|||
|
|||
var clone = new Dictionary<TKey, TValue>(inner); |
|||
|
|||
clone.Remove(key); |
|||
|
|||
return Create<TArray>(clone); |
|||
} |
|||
|
|||
private TArray Self<TArray>() where TArray : ImmutableDictionary<TKey, TValue> |
|||
{ |
|||
return (this as TArray)!; |
|||
} |
|||
|
|||
private TArray Create<TArray>(Dictionary<TKey, TValue> clone) where TArray : ImmutableDictionary<TKey, TValue> |
|||
{ |
|||
var newClone = (TArray)MemberwiseClone(); |
|||
|
|||
newClone.inner = clone; |
|||
|
|||
return newClone; |
|||
} |
|||
|
|||
public bool ContainsKey(TKey key) |
|||
{ |
|||
return inner.ContainsKey(key); |
|||
} |
|||
|
|||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) |
|||
{ |
|||
return inner.TryGetValue(key, out value); |
|||
} |
|||
|
|||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() |
|||
{ |
|||
return GetEnumerable(inner).GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return inner.GetEnumerator(); |
|||
} |
|||
|
|||
private static IEnumerable<TItem> GetEnumerable<TItem>(IEnumerable<TItem> collection) |
|||
{ |
|||
return collection; |
|||
} |
|||
} |
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -1,2 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -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,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> |
|||
@ -1,2 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -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,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; |
|||
} |
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -1,2 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.btn-status { |
|||
margin-left: 2rem; |
|||
} |
|||
@ -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> |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.status { |
|||
@include truncate; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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`; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.array-container { |
|||
background: $color-border; |
|||
margin-bottom: 1rem; |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
:host ::ng-deep { |
|||
.ui-separator { |
|||
border-color: $color-border !important; |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.disabled { |
|||
pointer-events: none; |
|||
} |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.field { |
|||
&-required { |
|||
color: $color-theme-error; |
|||
@ -1,6 +1,3 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
$color-user-background: rgba(0, 0, 0, .5); |
|||
$color-background: #000; |
|||
|
|||
@ -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,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; |
|||
} |
|||
} |
|||
@ -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> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue