diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs index b96e3a80a..fbd7da0f4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class AppClients : ArrayDictionary + public sealed class AppClients : ImmutableDictionary { public static readonly AppClients Empty = new AppClients(); @@ -21,8 +21,8 @@ namespace Squidex.Domain.Apps.Core.Apps { } - public AppClients(KeyValuePair[] items) - : base(items) + public AppClients(Dictionary inner) + : base(inner) { } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs index d072bf94d..9fbde2df8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class AppContributors : ArrayDictionary + public sealed class AppContributors : ImmutableDictionary { public static readonly AppContributors Empty = new AppContributors(); @@ -20,8 +20,8 @@ namespace Squidex.Domain.Apps.Core.Apps { } - public AppContributors(KeyValuePair[] items) - : base(items) + public AppContributors(Dictionary inner) + : base(inner) { } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs index 8db73c260..5e3bb59cf 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class AppPatterns : ArrayDictionary + public sealed class AppPatterns : ImmutableDictionary { public static readonly AppPatterns Empty = new AppPatterns(); @@ -21,8 +21,8 @@ namespace Squidex.Domain.Apps.Core.Apps { } - public AppPatterns(KeyValuePair[] items) - : base(items) + public AppPatterns(Dictionary inner) + : base(inner) { } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs index 322d66db1..a03bba8d5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs @@ -29,14 +29,9 @@ namespace Squidex.Domain.Apps.Core.Apps.Json protected override AppClients ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - var json = serializer.Deserialize>(reader); + var json = serializer.Deserialize>(reader)!; - return new AppClients(json.Select(Convert).ToArray()); - } - - private static KeyValuePair Convert(KeyValuePair kvp) - { - return new KeyValuePair(kvp.Key, kvp.Value.ToClient()); + return new AppClients(json.ToDictionary(x => x.Key, x => x.Value.ToClient())); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs index 25131de9a..70225b4ac 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; using Squidex.Infrastructure.Json.Newtonsoft; @@ -29,9 +28,9 @@ namespace Squidex.Domain.Apps.Core.Apps.Json protected override AppContributors ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - var json = serializer.Deserialize>(reader); + var json = serializer.Deserialize>(reader)!; - return new AppContributors(json.ToArray()); + return new AppContributors(json!); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs index 7fae4db0b..adcafa6f4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs @@ -29,14 +29,9 @@ namespace Squidex.Domain.Apps.Core.Apps.Json protected override AppPatterns ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - var json = serializer.Deserialize>(reader); + var json = serializer.Deserialize>(reader)!; - return new AppPatterns(json.Select(Convert).ToArray()); - } - - private static KeyValuePair Convert(KeyValuePair kvp) - { - return new KeyValuePair(kvp.Key, kvp.Value.ToPattern()); + return new AppPatterns(json.ToDictionary(x => x.Key, x => x.Value.ToPattern())); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs index 521469240..c8873a8e1 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs @@ -37,12 +37,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json return Roles.Empty; } - return new Roles(json.Select(Convert)); - } - - private static KeyValuePair Convert(KeyValuePair kvp) - { - return new KeyValuePair(kvp.Key, new Role(kvp.Key, new PermissionSet(kvp.Value))); + return new Roles(json.ToDictionary(x => x.Key, x => new Role(x.Key, new PermissionSet(x.Value)))); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs index 493aa47ce..92b141cf1 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.Apps { public sealed class Roles { - private readonly ArrayDictionary inner; + private readonly ImmutableDictionary inner; public static readonly IReadOnlyDictionary Defaults = new Dictionary { @@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Apps Clean(Permissions.AppWorkflows))) }; - public static readonly Roles Empty = new Roles(new ArrayDictionary()); + public static readonly Roles Empty = new Roles(new ImmutableDictionary()); public int CustomCount { @@ -70,14 +70,14 @@ namespace Squidex.Domain.Apps.Core.Apps get { return inner.Values.Union(Defaults.Values); } } - private Roles(ArrayDictionary roles) + private Roles(ImmutableDictionary roles) { inner = roles; } - public Roles(IEnumerable> items) + public Roles(Dictionary roles) { - inner = new ArrayDictionary(Cleaned(items)); + inner = new ImmutableDictionary(Cleaned(roles)); } [Pure] @@ -172,12 +172,12 @@ namespace Squidex.Domain.Apps.Core.Apps return permission.Substring(1); } - private static KeyValuePair[] Cleaned(IEnumerable> items) + private static Dictionary Cleaned(Dictionary inner) { - return items.Where(x => !Defaults.ContainsKey(x.Key)).ToArray(); + return inner.Where(x => !Defaults.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value); } - private Roles Create(ArrayDictionary newRoles) + private Roles Create(ImmutableDictionary newRoles) { return ReferenceEquals(inner, newRoles) ? this : new Roles(newRoles); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs index d6cf4a9f8..a5c8d14b4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; using Squidex.Infrastructure.Json.Newtonsoft; @@ -31,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json { var json = serializer.Deserialize>(reader); - return new Workflows(json.ToArray()); + return new Workflows(json!); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs index e6094cada..a285b2ac5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs @@ -14,7 +14,7 @@ using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Contents { - public sealed class Workflows : ArrayDictionary + public sealed class Workflows : ImmutableDictionary { public static readonly Workflows Empty = new Workflows(); @@ -22,8 +22,8 @@ namespace Squidex.Domain.Apps.Core.Contents { } - public Workflows(KeyValuePair[] items) - : base(items) + public Workflows(Dictionary inner) + : base(inner) { } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index b9676d94c..7aa3ef993 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -58,14 +58,20 @@ namespace Squidex.Domain.Apps.Entities.Apps { var file = uploadImage.File; - var image = await assetThumbnailGenerator.GetImageInfoAsync(file.OpenRead()); - - if (image == null) + using (var uploadStream = file.OpenRead()) { - throw new ValidationException("File is not an image."); + var image = await assetThumbnailGenerator.GetImageInfoAsync(uploadStream); + + if (image == null) + { + throw new ValidationException("File is not an image."); + } } - await appImageStore.UploadAsync(uploadImage.AppId, file.OpenRead()); + using (var uploadStream = file.OpenRead()) + { + await appImageStore.UploadAsync(uploadImage.AppId, uploadStream); + } } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index 4a268db7b..abfa36ac4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -149,11 +149,14 @@ namespace Squidex.Domain.Apps.Entities.Assets private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, string tempFile) { - using (var hashStream = new HasherStream(command.File.OpenRead(), HashAlgorithmName.SHA256)) + using (var uploadStream = command.File.OpenRead()) { - await assetFileStore.UploadAsync(tempFile, hashStream); + using (var hashStream = new HasherStream(uploadStream, HashAlgorithmName.SHA256)) + { + await assetFileStore.UploadAsync(tempFile, hashStream); - command.FileHash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64(); + command.FileHash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64(); + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs index 52541e19a..36f8c924c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs @@ -29,32 +29,35 @@ namespace Squidex.Domain.Apps.Entities.Assets { if (command.Type == AssetType.Unknown) { - var imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); - - if (imageInfo != null) + using (var uploadStream = command.File.OpenRead()) { - command.Type = AssetType.Image; - - command.Metadata.SetPixelWidth(imageInfo.PixelWidth); - command.Metadata.SetPixelHeight(imageInfo.PixelHeight); + var imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(uploadStream); - if (tags != null) + if (imageInfo != null) { - tags.Add("image"); + command.Type = AssetType.Image; - var wh = imageInfo.PixelWidth + imageInfo.PixelHeight; + command.Metadata.SetPixelWidth(imageInfo.PixelWidth); + command.Metadata.SetPixelHeight(imageInfo.PixelHeight); - if (wh > 2000) - { - tags.Add("image/large"); - } - else if (wh > 1000) + if (tags != null) { - tags.Add("image/medium"); - } - else - { - tags.Add("image/small"); + tags.Add("image"); + + var wh = imageInfo.PixelWidth + imageInfo.PixelHeight; + + if (wh > 2000) + { + tags.Add("image/large"); + } + else if (wh > 1000) + { + tags.Add("image/medium"); + } + else + { + tags.Add("image/small"); + } } } } diff --git a/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs index 57063172e..fc8cc7330 100644 --- a/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs +++ b/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs @@ -77,19 +77,23 @@ namespace Squidex.Infrastructure.Assets.ImageSharp public Task GetImageInfoAsync(Stream source) { - return Task.Run(() => + ImageInfo? result = null; + + try { - try - { - var image = Image.Load(source); + var image = Image.Identify(source); - return new ImageInfo(image.Width, image.Height); - } - catch + if (image != null) { - return null; + result = new ImageInfo(image.Width, image.Height); } - }); + } + catch + { + result = null; + } + + return Task.FromResult(result); } } } diff --git a/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs b/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs deleted file mode 100644 index b24827baf..000000000 --- a/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs +++ /dev/null @@ -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 : IReadOnlyDictionary where TKey : notnull - { - private static readonly KeyValuePair[] EmptyItems = Array.Empty>(); - private readonly IEqualityComparer keyComparer; - private KeyValuePair[] items; - - public TValue this[TKey key] - { - get - { - if (!TryGetValue(key, out var value)) - { - throw new KeyNotFoundException(); - } - - return value; - } - } - - public IEnumerable Keys - { - get { return items.Select(x => x.Key); } - } - - public IEnumerable Values - { - get { return items.Select(x => x.Value); } - } - - public int Count - { - get { return items.Length; } - } - - public ArrayDictionary() - : this(EqualityComparer.Default, EmptyItems) - { - } - - public ArrayDictionary(KeyValuePair[] items) - : this(EqualityComparer.Default, items) - { - } - - public ArrayDictionary(IEqualityComparer keyComparer, KeyValuePair[] items) - { - Guard.NotNull(items); - Guard.NotNull(keyComparer); - - this.items = items; - - this.keyComparer = keyComparer; - } - - public bool IsUnchanged(KeyValuePair[] values) - { - return ReferenceEquals(values, items); - } - - public ArrayDictionary With(TKey key, TValue value, IEqualityComparer? valueComparer = null) - { - return With>(key, value, valueComparer); - } - - public TArray With(TKey key, TValue value, IEqualityComparer? valueComparer = null) where TArray : ArrayDictionary - { - var index = IndexOf(key); - - if (index < 0) - { - var result = new KeyValuePair[items.Length + 1]; - - Array.Copy(items, 0, result, 0, items.Length); - - result[^1] = new KeyValuePair(key, value); - - return Create(result); - } - - var existing = items[index].Value; - - if (valueComparer == null || !valueComparer.Equals(value, existing)) - { - var result = new KeyValuePair[items.Length]; - - Array.Copy(items, 0, result, 0, items.Length); - - result[index] = new KeyValuePair(key, value); - - return Create(result); - } - - return Self(); - } - - public ArrayDictionary Without(TKey key) - { - return Without>(key); - } - - public TArray Without(TKey key) where TArray : ArrayDictionary - { - var index = IndexOf(key); - - if (index < 0) - { - return Self(); - } - - if (Count == 1) - { - return Create(EmptyItems); - } - - var result = new KeyValuePair[items.Length - 1]; - - Array.Copy(items, 0, result, 0, index); - Array.Copy(items, index + 1, result, index, items.Length - index - 1); - - return Create(result); - } - - private TArray Self() where TArray : ArrayDictionary - { - return (this as TArray)!; - } - - private TArray Create(KeyValuePair[] newItems) where TArray : ArrayDictionary - { - if (ReferenceEquals(items, newItems)) - { - return Self(); - } - - 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> IEnumerable>.GetEnumerator() - { - return GetEnumerable(items).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return items.GetEnumerator(); - } - - private static IEnumerable GetEnumerable(IEnumerable array) - { - return array; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary.cs b/backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary.cs similarity index 59% rename from backend/src/Squidex.Infrastructure/Collections/ArrayDictionary.cs rename to backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary.cs index 0ac20b261..de9d8962c 100644 --- a/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary.cs +++ b/backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary.cs @@ -11,11 +11,11 @@ using System.Linq; namespace Squidex.Infrastructure.Collections { - public static class ArrayDictionary + public static class ImmutableDictionary { - public static ArrayDictionary ToArrayDictionary(this IEnumerable source, Func keyExtractor) where TKey : notnull + public static ImmutableDictionary ToImmutableDictionary(this IEnumerable source, Func keyExtractor) where TKey : notnull { - return new ArrayDictionary(source.Select(x => new KeyValuePair(keyExtractor(x), x)).ToArray()); + return new ImmutableDictionary(source.ToDictionary(keyExtractor)); } } } diff --git a/backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary{TKey,TValue}.cs b/backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary{TKey,TValue}.cs new file mode 100644 index 000000000..05dc0ce04 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary{TKey,TValue}.cs @@ -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 : IReadOnlyDictionary where TKey : notnull + { + private static readonly Dictionary EmptyInner = new Dictionary(); + private Dictionary inner; + + public TValue this[TKey key] + { + get + { + if (!TryGetValue(key, out var value)) + { + throw new KeyNotFoundException(); + } + + return value; + } + } + + public IEnumerable Keys + { + get { return inner.Keys; } + } + + public IEnumerable Values + { + get { return inner.Values; } + } + + public int Count + { + get { return inner.Count; } + } + + public ImmutableDictionary() + : this(EmptyInner) + { + } + + public ImmutableDictionary(Dictionary inner) + { + Guard.NotNull(inner); + + this.inner = inner; + } + + public ImmutableDictionary With(TKey key, TValue value, IEqualityComparer? valueComparer = null) + { + return With>(key, value, valueComparer); + } + + public TArray With(TKey key, TValue value, IEqualityComparer? valueComparer = null) where TArray : ImmutableDictionary + { + if (!TryGetValue(key, out var found) || !IsEqual(value, found, valueComparer)) + { + var clone = new Dictionary(inner) + { + [key] = value + }; + + return Create(clone); + } + + return Self(); + } + + private static bool IsEqual(TValue lhs, TValue rhs, IEqualityComparer? comparer = null) + { + return comparer == null || comparer.Equals(lhs, rhs); + } + + public ImmutableDictionary Without(TKey key) + { + return Without>(key); + } + + public TArray Without(TKey key) where TArray : ImmutableDictionary + { + if (!inner.ContainsKey(key)) + { + return Self(); + } + + if (Count == 1) + { + return Create(EmptyInner); + } + + var clone = new Dictionary(inner); + + clone.Remove(key); + + return Create(clone); + } + + private TArray Self() where TArray : ImmutableDictionary + { + return (this as TArray)!; + } + + private TArray Create(Dictionary clone) where TArray : ImmutableDictionary + { + 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> IEnumerable>.GetEnumerator() + { + return GetEnumerable(inner).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return inner.GetEnumerator(); + } + + private static IEnumerable GetEnumerable(IEnumerable collection) + { + return collection; + } + } +} diff --git a/backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs b/backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs index 60d8d7add..e48578cd0 100644 --- a/backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs +++ b/backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs @@ -16,6 +16,7 @@ namespace Squidex.Areas.Frontend.Middlewares public sealed class WebpackMiddleware { private const string WebpackUrl = "http://localhost:3000/index.html"; + private readonly RequestDelegate next; public WebpackMiddleware(RequestDelegate next) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs index 38a6674e0..ccce68319 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Apps; @@ -30,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps [Fact] public void Should_create_roles_without_defaults() { - var roles = new Roles(Roles.Defaults.ToArray()); + var roles = new Roles(new Dictionary(Roles.Defaults)); Assert.Equal(0, roles.CustomCount); } diff --git a/frontend/app-config/webpack.config.js b/frontend/app-config/webpack.config.js index e022f7ede..f2f73a323 100644 --- a/frontend/app-config/webpack.config.js +++ b/frontend/app-config/webpack.config.js @@ -37,6 +37,8 @@ module.exports = function (env) { const isCoverage = env && env.coverage; const isAot = isProduction; + const configFile = isTests ? 'tsconfig.spec.json' : 'tsconfig.app.json'; + const config = { mode: isProduction ? 'production' : 'development', @@ -66,7 +68,9 @@ module.exports = function (env) { ], plugins: [ - new plugins.TsconfigPathsPlugin() + new plugins.TsconfigPathsPlugin({ + configFile + }) ] }, @@ -145,6 +149,10 @@ module.exports = function (env) { }, { loader: 'sass-loader', options: { + prependData: ` + @import '_vars'; + @import '_mixins'; + `, sassOptions: { includePaths: [root('app', 'theme')] } @@ -285,7 +293,7 @@ module.exports = function (env) { entryModule: 'app/app.module#AppModule', sourceMap: !isProduction, skipCodeGeneration: !isAot, - tsConfigPath: './tsconfig.json' + tsConfigPath: configFile }) ); } diff --git a/frontend/app/app.component.scss b/frontend/app/app.component.scss index fbb752506..e69de29bb 100644 --- a/frontend/app/app.component.scss +++ b/frontend/app/app.component.scss @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/frontend/app/features/administration/administration-area.component.scss b/frontend/app/features/administration/administration-area.component.scss index 23f99dbeb..e69de29bb 100644 --- a/frontend/app/features/administration/administration-area.component.scss +++ b/frontend/app/features/administration/administration-area.component.scss @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; diff --git a/frontend/app/features/administration/pages/cluster/cluster-page.component.scss b/frontend/app/features/administration/pages/cluster/cluster-page.component.scss index 4ec84ebe0..8fec000ae 100644 --- a/frontend/app/features/administration/pages/cluster/cluster-page.component.scss +++ b/frontend/app/features/administration/pages/cluster/cluster-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - iframe { @include absolute(0, 0, 0, 0); @include force-height(100%); diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html new file mode 100644 index 000000000..7cb6bc732 --- /dev/null +++ b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html @@ -0,0 +1,24 @@ + + + + + + {{eventConsumer.name}} + + + + {{eventConsumer.position}} + + + + + + + + \ No newline at end of file diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumer.component.scss b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumer.component.ts b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.ts index b0029a3d3..0c10f0a23 100644 --- a/frontend/app/features/administration/pages/event-consumers/event-consumer.component.ts +++ b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.ts @@ -13,32 +13,8 @@ import { EventConsumerDto, EventConsumersState } from '@app/features/administrat @Component({ selector: '[sqxEventConsumer]', - template: ` - - - - - - {{eventConsumer.name}} - - - - {{eventConsumer.position}} - - - - - - - - - `, + styleUrls: ['./event-consumer.component.scss'], + templateUrl: './event-consumer.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class EventConsumerComponent { diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.scss b/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.scss index 954eadba8..d95275ca7 100644 --- a/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.scss +++ b/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .faulted { & { color: $color-theme-error; diff --git a/frontend/app/features/administration/pages/restore/restore-page.component.scss b/frontend/app/features/administration/pages/restore/restore-page.component.scss index 7a16e83c5..97a5ad8c9 100644 --- a/frontend/app/features/administration/pages/restore/restore-page.component.scss +++ b/frontend/app/features/administration/pages/restore/restore-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - $circle-size: 2rem; h3 { diff --git a/frontend/app/features/administration/pages/users/user-page.component.scss b/frontend/app/features/administration/pages/users/user-page.component.scss index 8f81aae0d..68f973372 100644 --- a/frontend/app/features/administration/pages/users/user-page.component.scss +++ b/frontend/app/features/administration/pages/users/user-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .form-group-section { margin-top: 2rem; } diff --git a/frontend/app/features/administration/pages/users/user.component.html b/frontend/app/features/administration/pages/users/user.component.html new file mode 100644 index 000000000..10bbadc86 --- /dev/null +++ b/frontend/app/features/administration/pages/users/user.component.html @@ -0,0 +1,20 @@ + + + + + + {{user.displayName}} + + + {{user.email}} + + + + + + + \ No newline at end of file diff --git a/frontend/app/features/administration/pages/users/user.component.scss b/frontend/app/features/administration/pages/users/user.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/administration/pages/users/user.component.ts b/frontend/app/features/administration/pages/users/user.component.ts index f8a3452ce..864980464 100644 --- a/frontend/app/features/administration/pages/users/user.component.ts +++ b/frontend/app/features/administration/pages/users/user.component.ts @@ -13,28 +13,8 @@ import { UserDto, UsersState } from '@app/features/administration/internal'; @Component({ selector: '[sqxUser]', - template: ` - - - - - - {{user.displayName}} - - - {{user.email}} - - - - - - - - `, + styleUrls: ['./user.component.scss'], + templateUrl: './user.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class UserComponent { diff --git a/frontend/app/features/administration/pages/users/users-page.component.scss b/frontend/app/features/administration/pages/users/users-page.component.scss index fbb752506..e69de29bb 100644 --- a/frontend/app/features/administration/pages/users/users-page.component.scss +++ b/frontend/app/features/administration/pages/users/users-page.component.scss @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/frontend/app/features/administration/state/users.forms.ts b/frontend/app/features/administration/state/users.forms.ts index 2e2ed5ba6..6664e24b0 100644 --- a/frontend/app/features/administration/state/users.forms.ts +++ b/frontend/app/features/administration/state/users.forms.ts @@ -1,3 +1,10 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Form, ValidatorsEx } from '@app/shared'; diff --git a/frontend/app/features/api/api-area.component.scss b/frontend/app/features/api/api-area.component.scss index 6df2bffe7..b2868ce86 100644 --- a/frontend/app/features/api/api-area.component.scss +++ b/frontend/app/features/api/api-area.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .nav-link { padding-bottom: .6rem; padding-top: .6rem; diff --git a/frontend/app/features/api/pages/graphql/graphql-page.component.scss b/frontend/app/features/api/pages/graphql/graphql-page.component.scss index 4cebc196f..65c2f82e1 100644 --- a/frontend/app/features/api/pages/graphql/graphql-page.component.scss +++ b/frontend/app/features/api/pages/graphql/graphql-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - // Graphiql hints ::ng-deep { @import '~graphiql/dist/show-hint'; diff --git a/frontend/app/features/apps/pages/apps-page.component.scss b/frontend/app/features/apps/pages/apps-page.component.scss index d83f82dc7..ab35fad84 100644 --- a/frontend/app/features/apps/pages/apps-page.component.scss +++ b/frontend/app/features/apps/pages/apps-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .apps { &-title { font-size: 1.4rem; diff --git a/frontend/app/features/apps/pages/news-dialog.component.html b/frontend/app/features/apps/pages/news-dialog.component.html index 70c0f1625..e25557216 100644 --- a/frontend/app/features/apps/pages/news-dialog.component.html +++ b/frontend/app/features/apps/pages/news-dialog.component.html @@ -1,4 +1,4 @@ - + New Features diff --git a/frontend/app/features/apps/pages/news-dialog.component.scss b/frontend/app/features/apps/pages/news-dialog.component.scss index 274b04219..bcc87b52b 100644 --- a/frontend/app/features/apps/pages/news-dialog.component.scss +++ b/frontend/app/features/apps/pages/news-dialog.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - :host ::ng-deep { img { @include box-shadow(0, 4px, 20px, .2); diff --git a/frontend/app/features/apps/pages/news-dialog.component.ts b/frontend/app/features/apps/pages/news-dialog.component.ts index 5683be87b..6ffeff783 100644 --- a/frontend/app/features/apps/pages/news-dialog.component.ts +++ b/frontend/app/features/apps/pages/news-dialog.component.ts @@ -21,10 +21,6 @@ export class NewsDialogComponent { @Input() public features: ReadonlyArray; - public emitClose() { - this.close.emit(); - } - public trackByFeature(index: number, feature: FeatureDto) { return feature; } diff --git a/frontend/app/features/apps/pages/onboarding-dialog.component.html b/frontend/app/features/apps/pages/onboarding-dialog.component.html index dce2eaed4..9c9996613 100644 --- a/frontend/app/features/apps/pages/onboarding-dialog.component.html +++ b/frontend/app/features/apps/pages/onboarding-dialog.component.html @@ -1,6 +1,6 @@ - Skip Tour + Skip Tour
diff --git a/frontend/app/features/apps/pages/onboarding-dialog.component.scss b/frontend/app/features/apps/pages/onboarding-dialog.component.scss index 6a55f8584..2771a5399 100644 --- a/frontend/app/features/apps/pages/onboarding-dialog.component.scss +++ b/frontend/app/features/apps/pages/onboarding-dialog.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - $size-width: 825px; $size-height: 576px; diff --git a/frontend/app/features/apps/pages/onboarding-dialog.component.ts b/frontend/app/features/apps/pages/onboarding-dialog.component.ts index 9d2ea10f6..53fe2a8d7 100644 --- a/frontend/app/features/apps/pages/onboarding-dialog.component.ts +++ b/frontend/app/features/apps/pages/onboarding-dialog.component.ts @@ -23,10 +23,6 @@ export class OnboardingDialogComponent { @Output() public close = new EventEmitter(); - public emitClose() { - this.close.emit(); - } - public next() { this.step = this.step + 1; } diff --git a/frontend/app/features/assets/declarations.ts b/frontend/app/features/assets/declarations.ts index 317f83e41..365980ef5 100644 --- a/frontend/app/features/assets/declarations.ts +++ b/frontend/app/features/assets/declarations.ts @@ -6,4 +6,5 @@ */ export * from './pages/assets-filters-page.component'; -export * from './pages/assets-page.component'; \ No newline at end of file +export * from './pages/assets-page.component'; +export * from './pages/asset-tags.component'; \ No newline at end of file diff --git a/frontend/app/features/assets/module.ts b/frontend/app/features/assets/module.ts index bcfb24292..0b65d9fe4 100644 --- a/frontend/app/features/assets/module.ts +++ b/frontend/app/features/assets/module.ts @@ -12,7 +12,8 @@ import { SqxFrameworkModule, SqxSharedModule } from '@app/shared'; import { AssetsFiltersPageComponent, - AssetsPageComponent + AssetsPageComponent, + AssetTagsComponent } from './declarations'; const routes: Routes = [ @@ -36,7 +37,8 @@ const routes: Routes = [ ], declarations: [ AssetsFiltersPageComponent, - AssetsPageComponent + AssetsPageComponent, + AssetTagsComponent ] }) export class SqxFeatureAssetsModule {} \ No newline at end of file diff --git a/frontend/app/features/assets/pages/asset-tags.component.html b/frontend/app/features/assets/pages/asset-tags.component.html new file mode 100644 index 000000000..2db48178b --- /dev/null +++ b/frontend/app/features/assets/pages/asset-tags.component.html @@ -0,0 +1,18 @@ + +
+
+ All tags +
+
+
+ + +
+
+ {{tag.name}} +
+
+ {{tag.count}} +
+
+
\ No newline at end of file diff --git a/frontend/app/features/assets/pages/asset-tags.component.scss b/frontend/app/features/assets/pages/asset-tags.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/assets/pages/asset-tags.component.ts b/frontend/app/features/assets/pages/asset-tags.component.ts new file mode 100644 index 000000000..f011ad8b4 --- /dev/null +++ b/frontend/app/features/assets/pages/asset-tags.component.ts @@ -0,0 +1,42 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +import { Tag, TagsSelected } from '@app/shared'; + +@Component({ + selector: 'sqx-asset-tags', + styleUrls: ['./asset-tags.component.scss'], + templateUrl: './asset-tags.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AssetTagsComponent { + @Output() + public reset = new EventEmitter(); + + @Output() + public toggle = new EventEmitter(); + + @Input() + public tags: ReadonlyArray; + + @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; + } +} \ No newline at end of file diff --git a/frontend/app/features/assets/pages/assets-filters-page.component.html b/frontend/app/features/assets/pages/assets-filters-page.component.html index f3782afbe..917ecfc8d 100644 --- a/frontend/app/features/assets/pages/assets-filters-page.component.html +++ b/frontend/app/features/assets/pages/assets-filters-page.component.html @@ -6,32 +6,19 @@

Tags

- -
-
- All tags -
-
-
- - -
-
- {{tag.name}} -
-
- {{tag.count}} -
-
-
+ +
+ (search)="search($event)">
\ No newline at end of file diff --git a/frontend/app/features/assets/pages/assets-filters-page.component.scss b/frontend/app/features/assets/pages/assets-filters-page.component.scss index fbb752506..e69de29bb 100644 --- a/frontend/app/features/assets/pages/assets-filters-page.component.scss +++ b/frontend/app/features/assets/pages/assets-filters-page.component.scss @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/frontend/app/features/assets/pages/assets-filters-page.component.ts b/frontend/app/features/assets/pages/assets-filters-page.component.ts index a1f6c576c..2435d4159 100644 --- a/frontend/app/features/assets/pages/assets-filters-page.component.ts +++ b/frontend/app/features/assets/pages/assets-filters-page.component.ts @@ -11,7 +11,6 @@ import { AssetsState, Queries, Query, - SavedQuery, UIState } from '@app/shared'; @@ -29,10 +28,6 @@ export class AssetsFiltersPageComponent { ) { } - public isQueryUsed = (query: SavedQuery) => { - return this.assetsState.isQueryUsed(query); - } - public search(query: Query) { this.assetsState.search(query); } diff --git a/frontend/app/features/assets/pages/assets-page.component.scss b/frontend/app/features/assets/pages/assets-page.component.scss index 5976607b7..4be6c4e55 100644 --- a/frontend/app/features/assets/pages/assets-page.component.scss +++ b/frontend/app/features/assets/pages/assets-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .search ::ng-deep { .form-control { @include border-radius-right; diff --git a/frontend/app/features/content/declarations.ts b/frontend/app/features/content/declarations.ts index 0dff4286a..b0c8fee37 100644 --- a/frontend/app/features/content/declarations.ts +++ b/frontend/app/features/content/declarations.ts @@ -15,21 +15,24 @@ export * from './pages/contents/contents-page.component'; export * from './pages/contents/custom-view-editor.component'; export * from './pages/schemas/schemas-page.component'; -export * from './shared/array-editor.component'; -export * from './shared/array-item.component'; -export * from './shared/assets-editor.component'; -export * from './shared/content-list-cell.directive'; -export * from './shared/content-list-field.component'; -export * from './shared/content-list-header.component'; -export * from './shared/content-selector-item.component'; export * from './shared/content-status.component'; -export * from './shared/content-value-editor.component'; -export * from './shared/content-value.component'; -export * from './shared/content.component'; -export * from './shared/contents-selector.component'; export * from './shared/due-time-selector.component'; -export * from './shared/field-editor.component'; export * from './shared/preview-button.component'; -export * from './shared/reference-item.component'; -export * from './shared/references-editor.component'; -export * from './shared/stock-photo-editor.component'; \ No newline at end of file + +export * from './shared/forms/array-editor.component'; +export * from './shared/forms/array-item.component'; +export * from './shared/forms/assets-editor.component'; +export * from './shared/forms/field-editor.component'; +export * from './shared/forms/stock-photo-editor.component'; + +export * from './shared/list/content-list-cell.directive'; +export * from './shared/list/content-list-field.component'; +export * from './shared/list/content-list-header.component'; +export * from './shared/list/content-value-editor.component'; +export * from './shared/list/content-value.component'; +export * from './shared/list/content.component'; + +export * from './shared/references/content-selector-item.component'; +export * from './shared/references/content-selector.component'; +export * from './shared/references/reference-item.component'; +export * from './shared/references/references-editor.component'; \ No newline at end of file diff --git a/frontend/app/features/content/module.ts b/frontend/app/features/content/module.ts index 5e89aae4c..c1bcbd580 100644 --- a/frontend/app/features/content/module.ts +++ b/frontend/app/features/content/module.ts @@ -32,10 +32,10 @@ import { ContentListHeaderComponent, ContentListWidthPipe, ContentPageComponent, + ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, - ContentsSelectorComponent, ContentStatusComponent, ContentValueComponent, ContentValueEditorComponent, @@ -118,16 +118,16 @@ const routes: Routes = [ CommentsPageComponent, ContentComponent, ContentFieldComponent, + ContentHistoryPageComponent, ContentListCellDirective, - ContentListWidthPipe, ContentListFieldComponent, ContentListHeaderComponent, - ContentHistoryPageComponent, + ContentListWidthPipe, ContentPageComponent, + ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, - ContentsSelectorComponent, ContentStatusComponent, ContentValueComponent, ContentValueEditorComponent, diff --git a/frontend/app/features/content/pages/comments/comments-page.component.scss b/frontend/app/features/content/pages/comments/comments-page.component.scss index 23f99dbeb..e69de29bb 100644 --- a/frontend/app/features/content/pages/comments/comments-page.component.scss +++ b/frontend/app/features/content/pages/comments/comments-page.component.scss @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; diff --git a/frontend/app/features/content/pages/content/content-field.component.html b/frontend/app/features/content/pages/content/content-field.component.html index 034c25fdb..970faac5e 100644 --- a/frontend/app/features/content/pages/content/content-field.component.html +++ b/frontend/app/features/content/pages/content/content-field.component.html @@ -9,7 +9,7 @@ @@ -67,7 +67,7 @@ diff --git a/frontend/app/features/content/pages/content/content-field.component.scss b/frontend/app/features/content/pages/content/content-field.component.scss index 28167804c..338171a33 100644 --- a/frontend/app/features/content/pages/content/content-field.component.scss +++ b/frontend/app/features/content/pages/content/content-field.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .table-items-row { border-left-width: 4px; position: relative; diff --git a/frontend/app/features/content/pages/content/content-field.component.ts b/frontend/app/features/content/pages/content/content-field.component.ts index cb6dec4b6..44343aa8d 100644 --- a/frontend/app/features/content/pages/content/content-field.component.ts +++ b/frontend/app/features/content/pages/content/content-field.component.ts @@ -90,7 +90,7 @@ export class ContentFieldComponent implements DoCheck, OnChanges { this.isDifferent = value$(this.fieldForm).pipe( combineLatest(value$(this.fieldFormCompare), - (lhs, rhs) => !Types.jsJsonEquals(lhs, rhs))); + (lhs, rhs) => !Types.equals(lhs, rhs))); } } @@ -184,10 +184,6 @@ export class ContentFieldComponent implements DoCheck, OnChanges { } } - public emitLanguageChange(language: AppLanguageDto) { - this.languageChange.emit(language); - } - public prefix(language: AppLanguageDto) { return `(${language.iso2Code})`; } diff --git a/frontend/app/features/content/pages/content/content-history-page.component.scss b/frontend/app/features/content/pages/content/content-history-page.component.scss index 4ad344afa..c8b4c3272 100644 --- a/frontend/app/features/content/pages/content/content-history-page.component.scss +++ b/frontend/app/features/content/pages/content/content-history-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - :host ::ng-deep { .user-ref { font-weight: 500; diff --git a/frontend/app/features/content/pages/content/content-page.component.scss b/frontend/app/features/content/pages/content/content-page.component.scss index c89a1b370..e639f7c75 100644 --- a/frontend/app/features/content/pages/content/content-page.component.scss +++ b/frontend/app/features/content/pages/content/content-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .btn-status { margin-left: 2rem; } \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/field-languages.component.html b/frontend/app/features/content/pages/content/field-languages.component.html new file mode 100644 index 000000000..b651ab7e5 --- /dev/null +++ b/frontend/app/features/content/pages/content/field-languages.component.html @@ -0,0 +1,17 @@ + + + + + + + + + Please remember to check all languages when you see validation errors. + + + \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/field-languages.component.scss b/frontend/app/features/content/pages/content/field-languages.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/content/pages/content/field-languages.component.ts b/frontend/app/features/content/pages/content/field-languages.component.ts index 57f58868b..90c3a071b 100644 --- a/frontend/app/features/content/pages/content/field-languages.component.ts +++ b/frontend/app/features/content/pages/content/field-languages.component.ts @@ -11,43 +11,30 @@ import { AppLanguageDto, RootFieldDto } from '@app/shared'; @Component({ selector: 'sqx-field-languages', - template: ` - - - - - - - - - Please remember to check all languages when you see validation errors. - - - - `, + styleUrls: ['./field-languages.component.scss'], + templateUrl: './field-languages.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class FieldLanguagesComponent { - @Output() - public languageChange = new EventEmitter(); - @Output() public showAllControlsChange = new EventEmitter(); - @Input() - public field: RootFieldDto; - @Input() public showAllControls: boolean; + @Output() + public languageChange = new EventEmitter(); + @Input() public language: AppLanguageDto; @Input() public languages: ReadonlyArray; + + @Input() + public field: RootFieldDto; + + public toggleShowAllControls() { + this.showAllControlsChange.emit(this.showAllControls); + } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/contents/contents-filters-page.component.html b/frontend/app/features/content/pages/contents/contents-filters-page.component.html index 3cf873ff7..93ef1ba82 100644 --- a/frontend/app/features/content/pages/contents/contents-filters-page.component.html +++ b/frontend/app/features/content/pages/contents/contents-filters-page.component.html @@ -4,29 +4,30 @@ - - {{default.name}} - + +

+ (search)="search($event)">
\ No newline at end of file diff --git a/frontend/app/features/content/pages/contents/contents-filters-page.component.scss b/frontend/app/features/content/pages/contents/contents-filters-page.component.scss index d2ed5db4c..d010862a6 100644 --- a/frontend/app/features/content/pages/contents/contents-filters-page.component.scss +++ b/frontend/app/features/content/pages/contents/contents-filters-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .status { @include truncate; } \ No newline at end of file diff --git a/frontend/app/features/content/pages/contents/contents-filters-page.component.ts b/frontend/app/features/content/pages/contents/contents-filters-page.component.ts index 7a5afb66c..8432c095b 100644 --- a/frontend/app/features/content/pages/contents/contents-filters-page.component.ts +++ b/frontend/app/features/content/pages/contents/contents-filters-page.component.ts @@ -12,7 +12,6 @@ import { Queries, Query, ResourceOwner, - SavedQuery, SchemasState, UIState } from '@app/shared'; @@ -41,15 +40,7 @@ export class ContentsFiltersPageComponent extends ResourceOwner implements OnIni })); } - public isQueryUsed = (query: SavedQuery) => { - return this.contentsState.isQueryUsed(query); - } - public search(query: Query) { this.contentsState.search(query); } - - public trackByQuery(index: number, query: { name: string }) { - return query.name; - } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/contents/contents-page.component.scss b/frontend/app/features/content/pages/contents/contents-page.component.scss index 8cd27279f..97f69b49c 100644 --- a/frontend/app/features/content/pages/contents/contents-page.component.scss +++ b/frontend/app/features/content/pages/contents/contents-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .content { cursor: pointer; } @@ -27,6 +24,7 @@ &-button { @include absolute(null, 1rem, -2.375rem); + z-index: 1000; } } diff --git a/frontend/app/features/content/pages/contents/custom-view-editor.component.html b/frontend/app/features/content/pages/contents/custom-view-editor.component.html index 7694e2233..44bf8b6f1 100644 --- a/frontend/app/features/content/pages/contents/custom-view-editor.component.html +++ b/frontend/app/features/content/pages/contents/custom-view-editor.component.html @@ -7,16 +7,16 @@
-
-
+
- + @@ -31,7 +31,7 @@
- + diff --git a/frontend/app/features/content/pages/contents/custom-view-editor.component.scss b/frontend/app/features/content/pages/contents/custom-view-editor.component.scss index 40149ec7f..9f16730f8 100644 --- a/frontend/app/features/content/pages/contents/custom-view-editor.component.scss +++ b/frontend/app/features/content/pages/contents/custom-view-editor.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .container { max-height: 400px; overflow-x: hidden; diff --git a/frontend/app/features/content/pages/contents/custom-view-editor.component.ts b/frontend/app/features/content/pages/contents/custom-view-editor.component.ts index 44fa1c0bd..a21df2f08 100644 --- a/frontend/app/features/content/pages/contents/custom-view-editor.component.ts +++ b/frontend/app/features/content/pages/contents/custom-view-editor.component.ts @@ -17,14 +17,14 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Out changeDetection: ChangeDetectionStrategy.OnPush }) export class CustomViewEditorComponent implements OnChanges { - @Input() - public allFields: ReadonlyArray; + @Output() + public fieldNamesChange = new EventEmitter>(); @Input() public fieldNames: ReadonlyArray; - @Output() - public fieldNamesChange = new EventEmitter>(); + @Input() + public allFields: ReadonlyArray; public fieldsNotAdded: ReadonlyArray; @@ -32,29 +32,25 @@ export class CustomViewEditorComponent implements OnChanges { this.fieldsNotAdded = this.allFields.filter(n => this.fieldNames.indexOf(n) < 0); } - public random() { - return Math.random(); - } - public drop(event: CdkDragDrop) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); - this.updateFields(event.container.data); - } - - public updateFields(fieldNames: ReadonlyArray) { - this.fieldNamesChange.emit(fieldNames); + this.updateFieldNames(event.container.data); } public resetDefault() { - this.updateFields([]); + this.updateFieldNames([]); } public addField(field: string) { - this.updateFields([...this.fieldNames, field]); + this.updateFieldNames([...this.fieldNames, field]); } public removeField(field: string) { - this.updateFields(this.fieldNames.filter(x => x !== field)); + this.updateFieldNames(this.fieldNames.filter(x => x !== field)); + } + + private updateFieldNames(fieldNames: ReadonlyArray) { + this.fieldNamesChange.emit(fieldNames); } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/schemas/schemas-page.component.scss b/frontend/app/features/content/pages/schemas/schemas-page.component.scss index a54223a3d..b0392cfd1 100644 --- a/frontend/app/features/content/pages/schemas/schemas-page.component.scss +++ b/frontend/app/features/content/pages/schemas/schemas-page.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .btn-collapse { & { font-size: 1.2rem; diff --git a/frontend/app/features/content/shared/content-list-field.component.ts b/frontend/app/features/content/shared/content-list-field.component.ts deleted file mode 100644 index c419be5f3..000000000 --- a/frontend/app/features/content/shared/content-list-field.component.ts +++ /dev/null @@ -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: ` - - - {{content.id}} - - - {{content.created | sqxFromNow}} - - - - - - {{content.createdBy | sqxUserNameRef}} - - - {{content.lastModified | sqxFromNow}} - - - - - - {{content.lastModifiedBy | sqxUserNameRef}} - - - - - - - {{content.status}} - - - - - {{job.status}} at {{job.dueTime | sqxShortDate}} - - - - - - - - {{content.version.value}} - - - - - - - - - - - - `, - 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; - } -} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-list-header.component.ts b/frontend/app/features/content/shared/content-list-header.component.ts deleted file mode 100644 index e35e31b72..000000000 --- a/frontend/app/features/content/shared/content-list-header.component.ts +++ /dev/null @@ -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: ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - `, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContentListHeaderComponent { - @Input() - public field: TableField; - - @Output() - public queryChange = new EventEmitter(); - - @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`; - } - } -} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-selector-item.component.ts b/frontend/app/features/content/shared/content-selector-item.component.ts deleted file mode 100644 index ba882ba56..000000000 --- a/frontend/app/features/content/shared/content-selector-item.component.ts +++ /dev/null @@ -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: ` - - - - - - - - - - - - - - - `, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContentSelectorItemComponent { - @Output() - public selectedChange = new EventEmitter(); - - @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); - } -} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-status.component.scss b/frontend/app/features/content/shared/content-status.component.scss index 1d78232ef..d7ea4cbbd 100644 --- a/frontend/app/features/content/shared/content-status.component.scss +++ b/frontend/app/features/content/shared/content-status.component.scss @@ -1,5 +1,3 @@ -@import '_vars'; -@import '_mixins'; .content-status { &.default { diff --git a/frontend/app/features/content/shared/content-value-editor.component.ts b/frontend/app/features/content/shared/content-value-editor.component.ts deleted file mode 100644 index 7e50ea128..000000000 --- a/frontend/app/features/content/shared/content-value-editor.component.ts +++ /dev/null @@ -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: ` -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- `, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContentValueEditorComponent { - @Input() - public field: FieldDto; - - @Input() - public form: FormGroup; -} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-value.component.ts b/frontend/app/features/content/shared/content-value.component.ts deleted file mode 100644 index 2ef4f9d47..000000000 --- a/frontend/app/features/content/shared/content-value.component.ts +++ /dev/null @@ -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: ` - - {{value}} - - - - - `, - 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); - } -} \ No newline at end of file diff --git a/frontend/app/features/content/shared/due-time-selector.component.scss b/frontend/app/features/content/shared/due-time-selector.component.scss index fbb752506..e69de29bb 100644 --- a/frontend/app/features/content/shared/due-time-selector.component.scss +++ b/frontend/app/features/content/shared/due-time-selector.component.scss @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/frontend/app/features/content/shared/due-time-selector.component.ts b/frontend/app/features/content/shared/due-time-selector.component.ts index 80bb89d6b..c91a08a41 100644 --- a/frontend/app/features/content/shared/due-time-selector.component.ts +++ b/frontend/app/features/content/shared/due-time-selector.component.ts @@ -16,25 +16,26 @@ import { DialogModel } from '@app/shared'; templateUrl: './due-time-selector.component.html' }) export class DueTimeSelectorComponent { + private dueTimeResult: Subject; + public dueTimeDialog = new DialogModel(); public dueTime: string | null = ''; - public dueTimeFunction: Subject; public dueTimeAction: string | null = ''; public dueTimeMode = 'Immediately'; public selectDueTime(action: string): Observable { this.dueTimeAction = action; - this.dueTimeFunction = new Subject(); + this.dueTimeResult = new Subject(); this.dueTimeDialog.show(); - return this.dueTimeFunction; + return this.dueTimeResult; } public confirmStatusChange() { const result = this.dueTimeMode === 'Immediately' ? null : this.dueTime; - this.dueTimeFunction.next(result); - this.dueTimeFunction.complete(); + this.dueTimeResult.next(result); + this.dueTimeResult.complete(); this.cancelStatusChange(); } @@ -42,7 +43,7 @@ export class DueTimeSelectorComponent { public cancelStatusChange() { this.dueTimeMode = 'Immediately'; this.dueTimeDialog.hide(); - this.dueTimeFunction = null!; + this.dueTimeResult = null!; this.dueTime = null; } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/array-editor.component.html b/frontend/app/features/content/shared/forms/array-editor.component.html similarity index 100% rename from frontend/app/features/content/shared/array-editor.component.html rename to frontend/app/features/content/shared/forms/array-editor.component.html diff --git a/frontend/app/features/content/shared/array-editor.component.scss b/frontend/app/features/content/shared/forms/array-editor.component.scss similarity index 82% rename from frontend/app/features/content/shared/array-editor.component.scss rename to frontend/app/features/content/shared/forms/array-editor.component.scss index b08fbfc17..7eae62567 100644 --- a/frontend/app/features/content/shared/array-editor.component.scss +++ b/frontend/app/features/content/shared/forms/array-editor.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .array-container { background: $color-border; margin-bottom: 1rem; diff --git a/frontend/app/features/content/shared/array-editor.component.ts b/frontend/app/features/content/shared/forms/array-editor.component.ts similarity index 100% rename from frontend/app/features/content/shared/array-editor.component.ts rename to frontend/app/features/content/shared/forms/array-editor.component.ts diff --git a/frontend/app/features/content/shared/array-item.component.html b/frontend/app/features/content/shared/forms/array-item.component.html similarity index 86% rename from frontend/app/features/content/shared/array-item.component.html rename to frontend/app/features/content/shared/forms/array-item.component.html index b04a064d6..f440c1e20 100644 --- a/frontend/app/features/content/shared/array-item.component.html +++ b/frontend/app/features/content/shared/forms/array-item.component.html @@ -11,16 +11,16 @@
- - - -
- -
diff --git a/frontend/app/features/content/shared/array-item.component.scss b/frontend/app/features/content/shared/forms/array-item.component.scss similarity index 94% rename from frontend/app/features/content/shared/array-item.component.scss rename to frontend/app/features/content/shared/forms/array-item.component.scss index f07387916..c77818339 100644 --- a/frontend/app/features/content/shared/array-item.component.scss +++ b/frontend/app/features/content/shared/forms/array-item.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - :host ::ng-deep { .ui-separator { border-color: $color-border !important; diff --git a/frontend/app/features/content/shared/array-item.component.ts b/frontend/app/features/content/shared/forms/array-item.component.ts similarity index 94% rename from frontend/app/features/content/shared/array-item.component.ts rename to frontend/app/features/content/shared/forms/array-item.component.ts index 88ba73fbd..1feaee056 100644 --- a/frontend/app/features/content/shared/array-item.component.ts +++ b/frontend/app/features/content/shared/forms/array-item.component.ts @@ -155,27 +155,19 @@ export class ArrayItemComponent implements OnChanges, OnDestroy { this.changeDetector.markForCheck(); } - public emitClone() { - this.clone.emit(); - } - - public emitRemove() { - this.remove.emit(); - } - - public emitMoveTop() { + public moveTop() { this.move.emit(0); } - public emitMoveUp() { + public moveUp() { this.move.emit(this.index - 1); } - public emitMoveDown() { + public moveDown() { this.move.emit(this.index + 1); } - public emitMoveBottom() { + public moveBottom() { this.move.emit(99999); } diff --git a/frontend/app/features/content/shared/assets-editor.component.html b/frontend/app/features/content/shared/forms/assets-editor.component.html similarity index 100% rename from frontend/app/features/content/shared/assets-editor.component.html rename to frontend/app/features/content/shared/forms/assets-editor.component.html diff --git a/frontend/app/features/content/shared/assets-editor.component.scss b/frontend/app/features/content/shared/forms/assets-editor.component.scss similarity index 96% rename from frontend/app/features/content/shared/assets-editor.component.scss rename to frontend/app/features/content/shared/forms/assets-editor.component.scss index 6734b4757..86fc2ead1 100644 --- a/frontend/app/features/content/shared/assets-editor.component.scss +++ b/frontend/app/features/content/shared/forms/assets-editor.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .disabled { pointer-events: none; } diff --git a/frontend/app/features/content/shared/assets-editor.component.ts b/frontend/app/features/content/shared/forms/assets-editor.component.ts similarity index 95% rename from frontend/app/features/content/shared/assets-editor.component.ts rename to frontend/app/features/content/shared/forms/assets-editor.component.ts index db7935fcf..b7e81eaf6 100644 --- a/frontend/app/features/content/shared/assets-editor.component.ts +++ b/frontend/app/features/content/shared/forms/assets-editor.component.ts @@ -45,11 +45,12 @@ interface State { selector: 'sqx-assets-editor', styleUrls: ['./assets-editor.component.scss'], templateUrl: './assets-editor.component.html', - providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR], + providers: [ + SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR + ], changeDetection: ChangeDetectionStrategy.OnPush }) -// tslint:disable-next-line: readonly-array -export class AssetsEditorComponent extends StatefulControlComponent implements OnInit { +export class AssetsEditorComponent extends StatefulControlComponent> implements OnInit { @Input() public isCompact = false; @@ -70,7 +71,7 @@ export class AssetsEditorComponent extends StatefulControlComponent x.id))) { + if (!Types.equals(obj, this.snapshot.assets.map(x => x.id))) { const assetIds: string[] = obj; this.assetsService.getAssets(this.appsState.appName, 0, 0, undefined, undefined, obj) diff --git a/frontend/app/features/content/shared/field-editor.component.html b/frontend/app/features/content/shared/forms/field-editor.component.html similarity index 100% rename from frontend/app/features/content/shared/field-editor.component.html rename to frontend/app/features/content/shared/forms/field-editor.component.html diff --git a/frontend/app/features/content/shared/field-editor.component.scss b/frontend/app/features/content/shared/forms/field-editor.component.scss similarity index 92% rename from frontend/app/features/content/shared/field-editor.component.scss rename to frontend/app/features/content/shared/forms/field-editor.component.scss index f363be204..907653b31 100644 --- a/frontend/app/features/content/shared/field-editor.component.scss +++ b/frontend/app/features/content/shared/forms/field-editor.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - .field { &-required { color: $color-theme-error; diff --git a/frontend/app/features/content/shared/field-editor.component.ts b/frontend/app/features/content/shared/forms/field-editor.component.ts similarity index 100% rename from frontend/app/features/content/shared/field-editor.component.ts rename to frontend/app/features/content/shared/forms/field-editor.component.ts diff --git a/frontend/app/features/content/shared/stock-photo-editor.component.html b/frontend/app/features/content/shared/forms/stock-photo-editor.component.html similarity index 100% rename from frontend/app/features/content/shared/stock-photo-editor.component.html rename to frontend/app/features/content/shared/forms/stock-photo-editor.component.html diff --git a/frontend/app/features/content/shared/stock-photo-editor.component.scss b/frontend/app/features/content/shared/forms/stock-photo-editor.component.scss similarity index 97% rename from frontend/app/features/content/shared/stock-photo-editor.component.scss rename to frontend/app/features/content/shared/forms/stock-photo-editor.component.scss index 2cdc7af75..dd568d8e6 100644 --- a/frontend/app/features/content/shared/stock-photo-editor.component.scss +++ b/frontend/app/features/content/shared/forms/stock-photo-editor.component.scss @@ -1,6 +1,3 @@ -@import '_vars'; -@import '_mixins'; - $color-user-background: rgba(0, 0, 0, .5); $color-background: #000; diff --git a/frontend/app/features/content/shared/stock-photo-editor.component.ts b/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts similarity index 97% rename from frontend/app/features/content/shared/stock-photo-editor.component.ts rename to frontend/app/features/content/shared/forms/stock-photo-editor.component.ts index 8a7fe090d..53eaf9a42 100644 --- a/frontend/app/features/content/shared/stock-photo-editor.component.ts +++ b/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts @@ -30,7 +30,9 @@ export const SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR: any = { selector: 'sqx-stock-photo-editor', styleUrls: ['./stock-photo-editor.component.scss'], templateUrl: './stock-photo-editor.component.html', - providers: [SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR], + providers: [ + SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR + ], changeDetection: ChangeDetectionStrategy.OnPush }) export class StockPhotoEditorComponent extends StatefulControlComponent implements OnInit { diff --git a/frontend/app/features/content/shared/content-list-cell.directive.ts b/frontend/app/features/content/shared/list/content-list-cell.directive.ts similarity index 100% rename from frontend/app/features/content/shared/content-list-cell.directive.ts rename to frontend/app/features/content/shared/list/content-list-cell.directive.ts diff --git a/frontend/app/features/content/shared/list/content-list-field.component.html b/frontend/app/features/content/shared/list/content-list-field.component.html new file mode 100644 index 000000000..917135d06 --- /dev/null +++ b/frontend/app/features/content/shared/list/content-list-field.component.html @@ -0,0 +1,62 @@ + + + {{content.id}} + + + {{content.created | sqxFromNow}} + + + + + + {{content.createdBy | sqxUserNameRef}} + + + {{content.lastModified | sqxFromNow}} + + + + + + {{content.lastModifiedBy | sqxUserNameRef}} + + + + + + + {{content.status}} + + + + + {{content.scheduleJob.status}} at {{content.scheduleJob.dueTime | sqxShortDate}} + + + + + + + + {{content.version.value}} + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-list-field.component.scss b/frontend/app/features/content/shared/list/content-list-field.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/content/shared/list/content-list-field.component.ts b/frontend/app/features/content/shared/list/content-list-field.component.ts new file mode 100644 index 000000000..637342aca --- /dev/null +++ b/frontend/app/features/content/shared/list/content-list-field.component.ts @@ -0,0 +1,76 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { + ContentDto, + getContentValue, + LanguageDto, + MetaFields, + RootFieldDto, + TableField, + Types +} from '@app/shared'; + +@Component({ + selector: 'sqx-content-list-field', + styleUrls: ['./content-list-field.component.scss'], + templateUrl: './content-list-field.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentListFieldComponent implements OnChanges { + @Input() + public field: TableField; + + @Input() + public content: ContentDto; + + @Input() + public patchAllowed: boolean; + + @Input() + public patchForm: FormGroup; + + @Input() + public language: LanguageDto; + + public value: any; + + public ngOnChanges() { + this.reset(); + } + + public reset() { + if (Types.is(this.field, RootFieldDto)) { + const { value, formatted } = getContentValue(this.content, this.language, this.field); + + if (this.patchForm) { + const formControl = this.patchForm.controls[this.field.name]; + + if (formControl) { + formControl.setValue(value); + } + } + + this.value = formatted; + } + } + + public get metaFields() { + return MetaFields; + } + + public get isInlineEditable() { + return Types.is(this.field, RootFieldDto) ? this.field.isInlineEditable : false; + } + + public get fieldName() { + return Types.is(this.field, RootFieldDto) ? this.field.name : this.field; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-list-header.component.html b/frontend/app/features/content/shared/list/content-list-header.component.html new file mode 100644 index 000000000..707012dc1 --- /dev/null +++ b/frontend/app/features/content/shared/list/content-list-header.component.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-list-header.component.scss b/frontend/app/features/content/shared/list/content-list-header.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/content/shared/list/content-list-header.component.ts b/frontend/app/features/content/shared/list/content-list-header.component.ts new file mode 100644 index 000000000..89fe2d91e --- /dev/null +++ b/frontend/app/features/content/shared/list/content-list-header.component.ts @@ -0,0 +1,63 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +import { + LanguageDto, + MetaFields, + Query, + RootFieldDto, + TableField, + Types +} from '@app/shared'; + +@Component({ + selector: 'sqx-content-list-header', + styleUrls: ['./content-list-header.component.scss'], + templateUrl: './content-list-header.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentListHeaderComponent { + @Input() + public field: TableField; + + @Output() + public queryChange = new EventEmitter(); + + @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`; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-value-editor.component.html b/frontend/app/features/content/shared/list/content-value-editor.component.html new file mode 100644 index 000000000..b94b4ff30 --- /dev/null +++ b/frontend/app/features/content/shared/list/content-value-editor.component.html @@ -0,0 +1,48 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-value-editor.component.scss b/frontend/app/features/content/shared/list/content-value-editor.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/content/shared/list/content-value-editor.component.ts b/frontend/app/features/content/shared/list/content-value-editor.component.ts new file mode 100644 index 000000000..233a58994 --- /dev/null +++ b/frontend/app/features/content/shared/list/content-value-editor.component.ts @@ -0,0 +1,25 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { FieldDto } from '@app/shared'; + +@Component({ + selector: 'sqx-content-value-editor', + styleUrls: ['./content-value-editor.component.scss'], + templateUrl: './content-value-editor.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentValueEditorComponent { + @Input() + public field: FieldDto; + + @Input() + public form: FormGroup; +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-value.component.html b/frontend/app/features/content/shared/list/content-value.component.html new file mode 100644 index 000000000..7545608ed --- /dev/null +++ b/frontend/app/features/content/shared/list/content-value.component.html @@ -0,0 +1,6 @@ + + {{value}} + + + + \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-value.component.scss b/frontend/app/features/content/shared/list/content-value.component.scss new file mode 100644 index 000000000..39752b104 --- /dev/null +++ b/frontend/app/features/content/shared/list/content-value.component.scss @@ -0,0 +1,14 @@ +:ng-deep { + .html-value { + img { + margin-top: -25px; + max-height: 50px; + min-height: 50px; + position: absolute; + } + } +} + +.html-value { + position: relative; +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/list/content-value.component.ts b/frontend/app/features/content/shared/list/content-value.component.ts new file mode 100644 index 000000000..51d5b18bb --- /dev/null +++ b/frontend/app/features/content/shared/list/content-value.component.ts @@ -0,0 +1,25 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import { HtmlValue, Types } from '@app/shared'; + +@Component({ + selector: 'sqx-content-value', + styleUrls: ['./content-value.component.scss'], + templateUrl: './content-value.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentValueComponent { + @Input() + public value: any; + + public get isPlain() { + return !Types.is(this.value, HtmlValue); + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content.component.html b/frontend/app/features/content/shared/list/content.component.html similarity index 88% rename from frontend/app/features/content/shared/content.component.html rename to frontend/app/features/content/shared/list/content.component.html index a60669440..cc3604ca9 100644 --- a/frontend/app/features/content/shared/content.component.html +++ b/frontend/app/features/content/shared/list/content.component.html @@ -2,7 +2,7 @@ + (ngModelChange)="selectedChange.emit($event)" />
@@ -25,7 +25,7 @@