Browse Source

Asset dialog.

pull/352/head
Sebastian Stehle 7 years ago
parent
commit
8bed01fb1d
  1. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs
  3. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  4. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  5. 4
      src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs
  6. 34
      src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs
  7. 6
      src/Squidex.Domain.Apps.Entities/Assets/Commands/AnnotateAsset.cs
  8. 14
      src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs
  9. 20
      src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs
  10. 35
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  11. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs
  12. 8
      src/Squidex.Domain.Apps.Events/Assets/AssetAnnotated.cs
  13. 2
      src/Squidex.Domain.Apps.Events/Assets/AssetCreated.cs
  14. 2
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  15. 17
      src/Squidex/Areas/Api/Controllers/Assets/Models/AnnotateAssetDto.cs
  16. 6
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs
  17. 4
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  18. 2
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  19. 6
      src/Squidex/app/features/apps/pages/apps-page.component.html
  20. 2
      src/Squidex/app/features/apps/pages/news-dialog.component.html
  21. 6
      src/Squidex/app/features/apps/pages/news-dialog.component.ts
  22. 2
      src/Squidex/app/features/apps/pages/onboarding-dialog.component.html
  23. 6
      src/Squidex/app/features/apps/pages/onboarding-dialog.component.ts
  24. 2
      src/Squidex/app/features/assets/pages/assets-page.component.html
  25. 16
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  26. 8
      src/Squidex/app/features/content/shared/array-editor.component.html
  27. 16
      src/Squidex/app/features/content/shared/array-item.component.html
  28. 34
      src/Squidex/app/features/content/shared/array-item.component.ts
  29. 14
      src/Squidex/app/features/content/shared/assets-editor.component.html
  30. 14
      src/Squidex/app/features/content/shared/content-item.component.html
  31. 36
      src/Squidex/app/features/content/shared/content-item.component.ts
  32. 8
      src/Squidex/app/features/content/shared/contents-selector.component.html
  33. 10
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  34. 2
      src/Squidex/app/features/content/shared/due-time-selector.component.html
  35. 6
      src/Squidex/app/features/content/shared/references-editor.component.html
  36. 8
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  37. 12
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  38. 2
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  39. 4
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html
  40. 10
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts
  41. 4
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  42. 4
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.html
  43. 8
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts
  44. 12
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  45. 4
      src/Squidex/app/features/schemas/pages/schema/schema-preview-urls-form.component.html
  46. 8
      src/Squidex/app/features/schemas/pages/schema/schema-preview-urls-form.component.ts
  47. 4
      src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html
  48. 8
      src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts
  49. 4
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  50. 14
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  51. 6
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  52. 2
      src/Squidex/app/features/settings/pages/clients/client.component.html
  53. 15
      src/Squidex/app/framework/angular/forms/tag-editor.component.ts
  54. 4
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.html
  55. 4
      src/Squidex/app/framework/angular/modals/modal-dialog.component.html
  56. 6
      src/Squidex/app/framework/angular/modals/modal-dialog.component.ts
  57. 4
      src/Squidex/app/framework/angular/pager.component.html
  58. 8
      src/Squidex/app/framework/angular/pager.component.ts
  59. 6
      src/Squidex/app/framework/angular/sorted.directive.ts
  60. 14
      src/Squidex/app/framework/state.ts
  61. 4
      src/Squidex/app/shared/components/app-form.component.html
  62. 8
      src/Squidex/app/shared/components/app-form.component.ts
  63. 40
      src/Squidex/app/shared/components/asset-dialog.component.html
  64. 2
      src/Squidex/app/shared/components/asset-dialog.component.scss
  65. 78
      src/Squidex/app/shared/components/asset-dialog.component.ts
  66. 56
      src/Squidex/app/shared/components/asset.component.html
  67. 120
      src/Squidex/app/shared/components/asset.component.ts
  68. 10
      src/Squidex/app/shared/components/assets-list.component.html
  69. 6
      src/Squidex/app/shared/components/assets-list.component.ts
  70. 10
      src/Squidex/app/shared/components/assets-selector.component.html
  71. 10
      src/Squidex/app/shared/components/assets-selector.component.ts
  72. 2
      src/Squidex/app/shared/components/comment.component.html
  73. 8
      src/Squidex/app/shared/components/comment.component.ts
  74. 4
      src/Squidex/app/shared/components/comments.component.html
  75. 2
      src/Squidex/app/shared/components/markdown-editor.component.html
  76. 2
      src/Squidex/app/shared/components/rich-editor.component.html
  77. 2
      src/Squidex/app/shared/components/rich-editor.component.ts
  78. 2
      src/Squidex/app/shared/components/schema-category.component.html
  79. 10
      src/Squidex/app/shared/components/schema-category.component.ts
  80. 2
      src/Squidex/app/shared/components/search-form.component.html
  81. 8
      src/Squidex/app/shared/components/search-form.component.ts
  82. 1
      src/Squidex/app/shared/declarations.ts
  83. 3
      src/Squidex/app/shared/module.ts
  84. 68
      src/Squidex/app/shared/services/assets.service.spec.ts
  85. 38
      src/Squidex/app/shared/services/assets.service.ts
  86. 44
      src/Squidex/app/shared/state/assets.forms.ts
  87. 8
      src/Squidex/app/shared/state/assets.state.spec.ts
  88. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  89. 2
      src/Squidex/appsettings.json
  90. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs
  91. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs
  92. 35
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs
  93. 29
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs
  94. 18
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  95. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  96. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs
  97. 9
      tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs
  98. 2
      tools/Migrate_01/Migrations/CreateAssetSlugs.cs
  99. 13
      tools/Migrate_01/OldEvents/AssetRenamed.cs
  100. 27
      tools/Migrate_01/OldEvents/AssetTagged.cs

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
Created,
Deleted,
Renamed,
Annotated,
Updated
}
}

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs

@ -23,6 +23,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
string FileName { get; }
string FileNameSlug { get; }
string Slug { get; }
}
}

2
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs

@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonIgnoreIfDefault]
[BsonElement]
public string FileNameSlug { get; set; }
public string Slug { get; set; }
[BsonRequired]
[BsonElement]

4
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
.Ascending(x => x.Tags)
.Descending(x => x.LastModified)),
new CreateIndexModel<MongoAssetEntity>(
Index.Ascending(x => x.FileNameSlug))
Index.Ascending(x => x.Slug))
},
ct);
}
@ -107,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
using (Profiler.TraceMethod<MongoAssetRepository>())
{
var assetEntity =
await Collection.Find(x => x.FileNameSlug == slug)
await Collection.Find(x => x.Slug == slug)
.FirstOrDefaultAsync();
return assetEntity;

4
src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs

@ -49,8 +49,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
case AssetCreated _:
result.Type = EnrichedAssetEventType.Created;
break;
case AssetRenamed _:
result.Type = EnrichedAssetEventType.Renamed;
case AssetAnnotated _:
result.Type = EnrichedAssetEventType.Annotated;
break;
case AssetUpdated _:
result.Type = EnrichedAssetEventType.Updated;

34
src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs

@ -62,15 +62,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
return new AssetSavedResult(Version, Snapshot.FileVersion);
});
case TagAsset tagAsset:
return UpdateAsync(tagAsset, async c =>
{
GuardAsset.CanTag(c);
c.Tags = await NormalizeTagsAsync(Snapshot.AppId.Id, c.Tags);
Tag(c);
});
case DeleteAsset deleteAsset:
return UpdateAsync(deleteAsset, async c =>
{
@ -80,12 +71,17 @@ namespace Squidex.Domain.Apps.Entities.Assets
Delete(c);
});
case RenameAsset renameAsset:
return UpdateAsync(renameAsset, c =>
case AnnotateAsset annotateAsset:
return UpdateAsync(annotateAsset, async c =>
{
GuardAsset.CanRename(c, Snapshot.FileName);
GuardAsset.CanAnnotate(c, Snapshot.FileName, Snapshot.Slug);
if (c.Tags != null)
{
c.Tags = await NormalizeTagsAsync(Snapshot.AppId.Id, c.Tags);
}
Rename(c);
Annotate(c);
});
default:
throw new NotSupportedException();
@ -109,7 +105,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
MimeType = command.File.MimeType,
PixelWidth = command.ImageInfo?.PixelWidth,
PixelHeight = command.ImageInfo?.PixelHeight,
IsImage = command.ImageInfo != null
IsImage = command.ImageInfo != null,
Slug = command.File.FileName.ToAssetSlug()
});
RaiseEvent(@event);
@ -137,14 +134,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = Snapshot.TotalSize }));
}
public void Rename(RenameAsset command)
{
RaiseEvent(SimpleMapper.Map(command, new AssetRenamed()));
}
public void Tag(TagAsset command)
public void Annotate(AnnotateAsset command)
{
RaiseEvent(SimpleMapper.Map(command, new AssetTagged()));
RaiseEvent(SimpleMapper.Map(command, new AssetAnnotated()));
}
private void RaiseEvent(AppEvent @event)

6
src/Squidex.Domain.Apps.Entities/Assets/Commands/TagAsset.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/AnnotateAsset.cs

@ -9,8 +9,12 @@ using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Assets.Commands
{
public sealed class TagAsset : AssetCommand
public sealed class AnnotateAsset : AssetCommand
{
public string FileName { get; set; }
public string Slug { get; set; }
public HashSet<string> Tags { get; set; }
}
}

14
src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs

@ -1,14 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Assets.Commands
{
public sealed class RenameAsset : AssetCommand
{
public string FileName { get; set; }
}
}

20
src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs

@ -12,21 +12,28 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
public static class GuardAsset
{
public static void CanRename(RenameAsset command, string oldName)
public static void CanAnnotate(AnnotateAsset command, string oldFileName, string oldSlug)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot rename asset.", e =>
{
if (string.IsNullOrWhiteSpace(command.FileName))
if (string.IsNullOrWhiteSpace(command.FileName) &&
string.IsNullOrWhiteSpace(command.Slug) &&
command.Tags == null)
{
e(Not.Defined("Name"), nameof(command.FileName));
e("Either file name, slug or tags must be defined.", nameof(command.FileName), nameof(command.Slug), nameof(command.Tags));
}
if (string.Equals(command.FileName, oldName))
if (!string.IsNullOrWhiteSpace(command.FileName) && string.Equals(command.FileName, oldFileName))
{
e(Not.New("Asset", "name"), nameof(command.FileName));
}
if (!string.IsNullOrWhiteSpace(command.Slug) && string.Equals(command.Slug, oldSlug))
{
e(Not.New("Asset", "slug"), nameof(command.Slug));
}
});
}
@ -35,11 +42,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
Guard.NotNull(command, nameof(command));
}
public static void CanTag(TagAsset command)
{
Guard.NotNull(command, nameof(command));
}
public static void CanUpdate(UpdateAsset command)
{
Guard.NotNull(command, nameof(command));

35
src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
public string MimeType { get; set; }
[DataMember]
public string FileNameSlug { get; set; }
public string Slug { get; set; }
[DataMember]
public long FileVersion { get; set; }
@ -66,7 +66,15 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
SimpleMapper.Map(@event, this);
FileName = @event.FileName;
FileNameSlug = @event.FileName.ToAssetSlug();
if (string.IsNullOrWhiteSpace(@event.Slug))
{
Slug = @event.FileName.ToAssetSlug();
}
else
{
Slug = @event.Slug;
}
TotalSize += @event.FileSize;
}
@ -78,15 +86,22 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
TotalSize += @event.FileSize;
}
protected void On(AssetRenamed @event)
{
FileName = @event.FileName;
FileNameSlug = @event.FileName.ToAssetSlug();
}
protected void On(AssetTagged @event)
protected void On(AssetAnnotated @event)
{
Tags = @event.Tags;
if (!string.IsNullOrWhiteSpace(@event.FileName))
{
FileName = @event.FileName;
}
if (!string.IsNullOrWhiteSpace(@event.Slug))
{
Slug = @event.Slug;
}
if (@event.Tags != null)
{
Tags = @event.Tags;
}
}
protected void On(AssetDeleted @event)

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs

@ -101,9 +101,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
AddField(new FieldType
{
Name = "fileNameSlug",
Name = "slug",
ResolvedType = AllTypes.NonNullString,
Resolver = Resolve(x => x.FileNameSlug),
Resolver = Resolve(x => x.Slug),
Description = "The file name as slug."
});

8
src/Squidex.Domain.Apps.Events/Assets/AssetTagged.cs → src/Squidex.Domain.Apps.Events/Assets/AssetAnnotated.cs

@ -10,9 +10,13 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Assets
{
[EventType(nameof(AssetTagged))]
public sealed class AssetTagged : AssetEvent
[EventType(nameof(AssetAnnotated))]
public sealed class AssetAnnotated : AssetEvent
{
public string FileName { get; set; }
public string Slug { get; set; }
public HashSet<string> Tags { get; set; }
}
}

2
src/Squidex.Domain.Apps.Events/Assets/AssetCreated.cs

@ -17,6 +17,8 @@ namespace Squidex.Domain.Apps.Events.Assets
public string MimeType { get; set; }
public string Slug { get; set; }
public long FileVersion { get; set; }
public long FileSize { get; set; }

2
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -237,7 +237,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
[AssetRequestSizeLimit]
[ApiPermission(Permissions.AppAssetsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutAsset(string app, Guid id, [FromBody] UpdateAssetDto request)
public async Task<IActionResult> PutAsset(string app, Guid id, [FromBody] AnnotateAssetDto request)
{
await CommandBus.PublishAsync(request.ToCommand(id));

17
src/Squidex/Areas/Api/Controllers/Assets/Models/UpdateAssetDto.cs → src/Squidex/Areas/Api/Controllers/Assets/Models/AnnotateAssetDto.cs

@ -8,16 +8,22 @@
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Assets.Models
{
public sealed class UpdateAssetDto
public sealed class AnnotateAssetDto
{
/// <summary>
/// The new name of the asset.
/// </summary>
public string FileName { get; set; }
/// <summary>
/// The new slug of the asset.
/// </summary>
public string Slug { get; set; }
/// <summary>
/// The new asset tags.
/// </summary>
@ -25,14 +31,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
public AssetCommand ToCommand(Guid id)
{
if (Tags != null)
{
return new TagAsset { AssetId = id, Tags = Tags };
}
else
{
return new RenameAsset { AssetId = id, FileName = FileName };
}
return SimpleMapper.Map(this, new AnnotateAsset { AssetId = id });
}
}
}

6
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs

@ -33,6 +33,12 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
[Required]
public string FileName { get; set; }
/// <summary>
/// The slug.
/// </summary>
[Required]
public string Slug { get; set; }
/// <summary>
/// The mime type.
/// </summary>

4
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -30,10 +30,10 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
public string FileName { get; set; }
/// <summary>
/// The file name as a slug.
/// The slug.
/// </summary>
[Required]
public string FileNameSlug { get; set; }
public string Slug { get; set; }
/// <summary>
/// The mime type.

2
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -59,7 +59,7 @@
</ng-container>
</sqx-panel>
<sqx-modal-dialog *sqxModalView="eventConsumerErrorDialog;onRoot:true" (closed)="eventConsumerErrorDialog.hide()">
<sqx-modal-dialog *sqxModalView="eventConsumerErrorDialog;onRoot:true" (close)="eventConsumerErrorDialog.hide()">
<ng-container title>
Error
</ng-container>

6
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -95,18 +95,18 @@
<ng-container *sqxModalView="addAppDialog;onRoot:true">
<sqx-app-form [template]="addAppTemplate"
(completed)="addAppDialog.hide()">
(complete)="addAppDialog.hide()">
</sqx-app-form>
</ng-container>
<ng-container *sqxModalView="onboardingDialog;onRoot:true;closeAuto:false">
<sqx-onboarding-dialog
(closed)="onboardingDialog.hide()">
(close)="onboardingDialog.hide()">
</sqx-onboarding-dialog>
</ng-container>
<ng-container *sqxModalView="newsDialog;onRoot:true;closeAuto:false">
<sqx-news-dialog [features]="newsFeatures"
(closed)="newsDialog.hide()">
(close)="newsDialog.hide()">
</sqx-news-dialog>
</ng-container>

2
src/Squidex/app/features/apps/pages/news-dialog.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog large="true" (closed)="closed.emit()">
<sqx-modal-dialog large="true" (close)="emitClose()">
<ng-container title>
New Features
</ng-container>

6
src/Squidex/app/features/apps/pages/news-dialog.component.ts

@ -19,9 +19,9 @@ export class NewsDialogComponent {
public features: FeatureDto[];
@Output()
public closed = new EventEmitter();
public close = new EventEmitter();
public close() {
this.closed.emit();
public emitClose() {
this.close.emit();
}
}

2
src/Squidex/app/features/apps/pages/onboarding-dialog.component.html

@ -1,6 +1,6 @@
<sqx-modal-dialog [showHeader]="false">
<ng-container content>
<a class="header-right modal-close" (click)="close()">Skip Tour</a>
<a class="header-right modal-close" (click)="emitClose()">Skip Tour</a>
<div class="onboarding-step" *ngIf="step === 0">
<img @fade class="header-left" src="/images/logo-white-small.png" />

6
src/Squidex/app/features/apps/pages/onboarding-dialog.component.ts

@ -21,10 +21,10 @@ export class OnboardingDialogComponent {
public step = 0;
@Output()
public closed = new EventEmitter();
public close = new EventEmitter();
public close() {
this.closed.emit();
public emitClose() {
this.close.emit();
}
public next() {

2
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -26,7 +26,7 @@
</div>
<div class="col-6">
<sqx-search-form formClass="form" placeholder="Search by asset name" fieldExample="fileSize"
(queryChanged)="search($event)"
(queryChange)="search($event)"
[query]="assetsState.assetsQuery | async"
[queries]="queries"
enableShortcut="true">

16
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -22,10 +22,10 @@
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Search for content" fieldExample="data/[MY_FIELD]/iv"
(queryChanged)="search($event)"
(queryChange)="search($event)"
[query]="contentsState.contentsQuery | async"
[queries]="schemaQueries"
(archivedChanged)="goArchive($event)"
(archivedChange)="goArchive($event)"
[archived]="contentsState.isArchive | async"
expandable="true"
enableArchive="true"
@ -106,12 +106,12 @@
[schema]="schema"
[selected]="isItemSelected(content)"
(selectedChange)="selectItem(content, $event)"
(unpublishing)="unpublish(content)"
(publishing)="publish(content)"
(archiving)="archive(content)"
(restoring)="restore(content)"
(deleting)="delete(content)"
(cloning)="clone(content)">
(unpublishe)="unpublish(content)"
(publishe)="publish(content)"
(archive)="archive(content)"
(restore)="restore(content)"
(delete)="delete(content)"
(clone)="clone(content)">
</tr>
<tr class="spacer"></tr>
</tbody>

8
src/Squidex/app/features/content/shared/array-editor.component.html

@ -1,6 +1,6 @@
<div class="array-container" *ngIf="arrayControl.controls.length > 0"
[sqxSortModel]="arrayControl.controls"
(sqxSorted)="sort($event)">
(sqxSort)="sort($event)">
<div class="item" *ngFor="let itemForm of arrayControl.controls; let i = index">
<sqx-array-item
[form]="form"
@ -13,9 +13,9 @@
[language]="language"
[languages]="languages"
(toggle)="hide($event)"
(moving)="move(itemForm, $event)"
(cloning)="addItem(itemForm)"
(removing)="removeItem(i)">
(move)="move(itemForm, $event)"
(clone)="addItem(itemForm)"
(remove)="removeItem(i)">
</sqx-array-item>
</div>
</div>

16
src/Squidex/app/features/content/shared/array-item.component.html

@ -5,32 +5,32 @@
<span class="header-text text-decent">Item #{{index + 1}}</span>
<button type="button" class="btn btn-text-secondary" [disabled]="isFirst" (click)="moveTop()">
<button type="button" class="btn btn-text-secondary" [disabled]="isFirst" (click)="emitMoveTop()">
<i class="icon-caret-top"></i>
</button>
<button type="button" class="btn btn-text-secondary" [disabled]="isFirst" (click)="moveUp()">
<button type="button" class="btn btn-text-secondary" [disabled]="isFirst" (click)="emitMoveUp()">
<i class="icon-caret-up"></i>
</button>
<button type="button" class="btn btn-text-secondary" [disabled]="isLast" (click)="moveDown()">
<button type="button" class="btn btn-text-secondary" [disabled]="isLast" (click)="emitMoveDown()">
<i class="icon-caret-down"></i>
</button>
<button type="button" class="btn btn-text-secondary" [disabled]="isLast" (click)="moveBottom()">
<button type="button" class="btn btn-text-secondary" [disabled]="isLast" (click)="emitMoveBottom()">
<i class="icon-caret-bottom"></i>
</button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="!isHidden" (click)="toggle.emit(false)" title="Open all items">
<button type="button" class="btn btn-text-secondary" [class.hidden]="!isHidden" (click)="emitToggle(false)" title="Open all items">
<i class="icon-plus-square"></i>
</button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="isHidden" (click)="toggle.emit(true)" title="Close all items">
<button type="button" class="btn btn-text-secondary" [class.hidden]="isHidden" (click)="emitToggle(true)" title="Close all items">
<i class="icon-minus-square"></i>
</button>
</span>
<span class="float-right">
<button type="button" class="btn btn-text-secondary" (click)="cloning.emit()">
<button type="button" class="btn btn-text-secondary" (click)="emitClone()">
<i class="icon-clone"></i>
</button>
<button type="button" class="btn btn-text-danger" (click)="removing.emit()">
<button type="button" class="btn btn-text-danger" (click)="emitRemove()">
<i class="icon-bin2"></i>
</button>

34
src/Squidex/app/features/content/shared/array-item.component.ts

@ -25,13 +25,13 @@ import {
})
export class ArrayItemComponent implements OnChanges {
@Output()
public removing = new EventEmitter();
public remove = new EventEmitter();
@Output()
public moving = new EventEmitter<number>();
public move = new EventEmitter<number>();
@Output()
public cloning = new EventEmitter();
public clone = new EventEmitter();
@Output()
public toggle = new EventEmitter<boolean>();
@ -77,19 +77,31 @@ export class ArrayItemComponent implements OnChanges {
}
}
public moveTop() {
this.moving.emit(0);
public emitToggle(value: boolean) {
this.toggle.emit(value);
}
public moveUp() {
this.moving.emit(this.index - 1);
public emitClone() {
this.clone.emit();
}
public moveDown() {
this.moving.emit(this.index + 1);
public emitRemove() {
this.remove.emit();
}
public moveBottom() {
this.moving.emit(99999);
public emitMoveTop() {
this.move.emit(0);
}
public emitMoveUp() {
this.move.emit(this.index - 1);
}
public emitMoveDown() {
this.move.emit(this.index + 1);
}
public emitMoveBottom() {
this.move.emit(99999);
}
}

14
src/Squidex/app/features/content/shared/assets-editor.component.html

@ -24,10 +24,10 @@
<ng-container *ngIf="!snapshot.isListView; else listTemplate">
<div class="row no-gutters">
<sqx-asset *ngFor="let file of snapshot.assetFiles" [initFile]="file"
(failed)="removeLoadingAsset(file)" (loaded)="addAsset(file, $event)">
(faile)="removeLoadingAsset(file)" (load)="addAsset(file, $event)">
</sqx-asset>
<sqx-asset *ngFor="let asset of snapshot.assets; trackBy: trackByAsset" [asset]="asset" [isCompact]="isCompact" removeMode="true"
(updated)="notifyOthers($event)" (removing)="removeLoadedAsset($event)">
(update)="notifyOthers($event)" (remove)="removeLoadedAsset($event)">
</sqx-asset>
</div>
</ng-container>
@ -35,17 +35,19 @@
<ng-template #listTemplate>
<div class="list-view">
<sqx-asset *ngFor="let file of snapshot.assetFiles" [initFile]="file"
[isListView]="true" (failed)="removeLoadingAsset(file)" (loaded)="addAsset(file, $event)">
[isListView]="true"
(loadError)="removeLoadingAsset(file)"
(load)="addAsset(file, $event)">
</sqx-asset>
<div
[sqxSortModel]="snapshot.assets.mutableValues"
(sqxSorted)="sortAssets($event)">
(sqxSort)="sortAssets($event)">
<div *ngFor="let asset of snapshot.assets; trackBy: trackByAsset">
<sqx-asset [asset]="asset" removeMode="true"
[isListView]="true"
[isCompact]="isCompact"
(updated)="notifyOthers($event)" (removing)="removeLoadedAsset($event)">
(update)="notifyOthers($event)" (remove)="removeLoadedAsset($event)">
</sqx-asset>
</div>
</div>
@ -56,6 +58,6 @@
<ng-container *sqxModalView="assetsDialog;onRoot:true;closeAuto:false">
<sqx-assets-selector
(selected)="selectAssets($event)">
(select)="selectAssets($event)">
</sqx-assets-selector>
</ng-container>

14
src/Squidex/app/features/content/shared/content-item.component.html

@ -54,26 +54,26 @@
<i class="icon-dots"></i>
</button>
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="publishing.emit()" *ngIf="content.status === 'Draft'">
<a class="dropdown-item" (click)="emitPublish()" *ngIf="content.status === 'Draft'">
Publish
</a>
<a class="dropdown-item" (click)="unpublishing.emit()" *ngIf="content.status === 'Published'">
<a class="dropdown-item" (click)="emitUnpublish()" *ngIf="content.status === 'Published'">
Unpublish
</a>
<a class="dropdown-item" (click)="archiving.emit()" *ngIf="content.status !== 'Archived'">
<a class="dropdown-item" (click)="emitArchive()" *ngIf="content.status !== 'Archived'">
Archive
</a>
<a class="dropdown-item" (click)="restoring.emit()" *ngIf="content.status === 'Archived'">
<a class="dropdown-item" (click)="emitRestore()" *ngIf="content.status === 'Archived'">
Restore
</a>
<a class="dropdown-item" (click)="cloning.emit(); dropdown.hide()" *ngIf="content.status !== 'Archived'">
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="content.status !== 'Archived'">
Clone
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="deleting.emit()"
(sqxConfirmClick)="emitDelete()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
@ -82,7 +82,7 @@
</div>
</td>
<td class="cell-actions" *ngIf="isReference" (click)="shouldStop($event)">
<button type="button" class="btn btn-text-secondary" (click)="deleting.emit()">
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()">
<i class="icon-close"></i>
</button>
</td>

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

@ -35,22 +35,22 @@ import {
})
export class ContentItemComponent implements OnChanges {
@Output()
public cloning = new EventEmitter();
public clone = new EventEmitter();
@Output()
public deleting = new EventEmitter();
public delete = new EventEmitter();
@Output()
public archiving = new EventEmitter();
public archive = new EventEmitter();
@Output()
public restoring = new EventEmitter();
public restore = new EventEmitter();
@Output()
public publishing = new EventEmitter();
public publish = new EventEmitter();
@Output()
public unpublishing = new EventEmitter();
public unpublish = new EventEmitter();
@Output()
public selectedChange = new EventEmitter();
@ -128,6 +128,30 @@ export class ContentItemComponent implements OnChanges {
this.updateValues();
}
public emitDelete() {
this.delete.emit();
}
public emitPublish() {
this.publish.emit();
}
public emitUnpublish() {
this.unpublish.emit();
}
public emitArchive() {
this.archive.emit();
}
public emitRestore() {
this.unpublish.emit();
}
public emitClone() {
this.clone.emit();
}
private updateValues() {
this.values = [];

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

@ -1,4 +1,4 @@
<sqx-modal-dialog (closed)="complete()" large="true" fullHeight="true" contentClass="grid">
<sqx-modal-dialog (close)="emitComplete()" large="true" fullHeight="true" contentClass="grid">
<ng-container title>
Select contents
</ng-container>
@ -13,7 +13,7 @@
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Search for content" fieldExample="data/[MY_FIELD]/iv"
[query]="contentsState.contentsQuery | async"
(queryChanged)="search($event)"
(queryChange)="search($event)"
expandable="true">
</sqx-search-form>
</div>
@ -68,7 +68,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="submit" class="float-right btn btn-success" (click)="select()" [disabled]="selectionCount === 0">Link selected contents ({{selectionCount}})</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-success" (click)="emitSelect()" [disabled]="selectionCount === 0">Link selected contents ({{selectionCount}})</button>
</ng-container>
</sqx-modal-dialog>

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

@ -35,7 +35,7 @@ export class ContentsSelectorComponent implements OnInit {
public schema: SchemaDetailsDto;
@Output()
public selected = new EventEmitter<ContentDto[]>();
public select = new EventEmitter<ContentDto[]>();
public searchModal = new ModalModel();
@ -75,12 +75,12 @@ export class ContentsSelectorComponent implements OnInit {
return this.selectedItems[content.id];
}
public complete() {
this.selected.emit([]);
public emitComplete() {
this.select.emit([]);
}
public select() {
this.selected.emit(Object.values(this.selectedItems));
public emitSelect() {
this.select.emit(Object.values(this.selectedItems));
}
public selectLanguage(language: LanguageDto) {

2
src/Squidex/app/features/content/shared/due-time-selector.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog *sqxModalView="dueTimeDialog;onRoot:true" (closed)="cancelStatusChange()">
<sqx-modal-dialog *sqxModalView="dueTimeDialog;onRoot:true" (close)="cancelStatusChange()">
<ng-container title>
{{dueTimeAction}} content item(s)
</ng-container>

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

@ -8,7 +8,7 @@
<table class="table table-items table-fixed" [class.disabled]="snapshot.isDisabled" *ngIf="snapshot.schema && snapshot.contentItems && snapshot.contentItems.length > 0"
[sqxSortModel]="snapshot.contentItems.mutableValues"
(sqxSorted)="sort($event)">
(sqxSort)="sort($event)">
<tbody *ngFor="let content of snapshot.contentItems">
<tr [sqxContent]="content"
[language]="language"
@ -16,7 +16,7 @@
[isReference]="true"
[isCompact]="isCompact"
[schema]="snapshot.schema"
(deleting)="remove(content)"></tr>
(delete)="remove(content)"></tr>
<tr class="spacer"></tr>
</tbody>
</table>
@ -32,6 +32,6 @@
[language]="language"
[languages]="languages"
[schema]="snapshot.schema"
(selected)="select($event)">
(select)="select($event)">
</sqx-contents-selector>
</ng-container>

8
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog large="true" fullHeight="true" (closed)="complete()" [showFooter]="step === 2 || step === 4">
<sqx-modal-dialog large="true" fullHeight="true" (close)="emitComplete()" [showFooter]="step === 2 || step === 4">
<ng-container title>
<ng-container *ngIf="mode === 'EditTrigger'">
Edit Trigger
@ -92,17 +92,17 @@
<ng-container footer>
<div>
<ng-container *ngIf="mode === 'Wizard' && step === 2">
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveTrigger()">Next</button>
</ng-container>
<ng-container *ngIf="mode !== 'Wizard' && step === 2">
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveTrigger()">Save</button>
</ng-container>
<ng-container *ngIf="step === 4">
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveAction()">Save</button>
</ng-container>
</div>

12
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts

@ -39,7 +39,7 @@ export class RuleWizardComponent implements OnInit {
public step = 1;
@Output()
public completed = new EventEmitter();
public complete = new EventEmitter();
@Input()
public ruleActions: { [name: string]: RuleElementDto };
@ -75,8 +75,8 @@ export class RuleWizardComponent implements OnInit {
}
}
public complete() {
this.completed.emit();
public emitComplete() {
this.complete.emit();
}
public selectTriggerType(type: string) {
@ -122,7 +122,7 @@ export class RuleWizardComponent implements OnInit {
this.rulesState.create(requestDto)
.subscribe(() => {
this.complete();
this.emitComplete();
this.actionForm.submitCompleted();
this.triggerForm.submitCompleted();
@ -135,7 +135,7 @@ export class RuleWizardComponent implements OnInit {
private updateTrigger() {
this.rulesState.updateTrigger(this.rule, this.trigger)
.subscribe(() => {
this.complete();
this.emitComplete();
this.triggerForm.submitCompleted();
}, error => {
@ -146,7 +146,7 @@ export class RuleWizardComponent implements OnInit {
private updateAction() {
this.rulesState.updateAction(this.rule, this.action)
.subscribe(() => {
this.complete();
this.emitComplete();
this.actionForm.submitCompleted();
}, error => {

2
src/Squidex/app/features/rules/pages/rules/rules-page.component.html

@ -70,7 +70,7 @@
[ruleActions]="ruleActions"
[ruleTriggers]="ruleTriggers"
[mode]="wizardMode"
(completed)="addRuleDialog.hide()">
(complete)="addRuleDialog.hide()">
</sqx-rule-wizard>
</ng-container>
</ng-container>

4
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog (closed)="complete()" large="true">
<sqx-modal-dialog (close)="emitComplete()" large="true">
<ng-container title>
<ng-container *ngIf="parent; else noParent">
Add Nested Field
@ -91,7 +91,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<div class="float-right" *ngIf="!isEditing">
<button type="button" class="btn btn-outline-success mr-1" (click)="addField(false, false)">Create and close</button>

10
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts

@ -38,7 +38,7 @@ export class FieldWizardComponent implements OnInit {
public parent: RootFieldDto;
@Output()
public completed = new EventEmitter();
public complete = new EventEmitter();
public fieldTypes = fieldTypes;
public field: FieldDto;
@ -62,8 +62,8 @@ export class FieldWizardComponent implements OnInit {
}
}
public complete() {
this.completed.emit();
public emitComplete() {
this.complete.emit();
}
public addField(addNew: boolean, edit: boolean) {
@ -85,7 +85,7 @@ export class FieldWizardComponent implements OnInit {
this.isEditing = true;
} else {
this.complete();
this.emitComplete();
}
}, error => {
this.addFieldForm.submitFailed(error);
@ -110,7 +110,7 @@ export class FieldWizardComponent implements OnInit {
if (addNew) {
this.isEditing = false;
} else {
this.complete();
this.emitComplete();
}
}, error => {
this.editForm.submitFailed(error);

4
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -107,7 +107,7 @@
<ng-container *ngIf="field['nested']; let nested">
<span class="nested-field-line-v"></span>
<div [sqxSortModel]="nested" (sqxSorted)="sortFields($event)">
<div [sqxSortModel]="nested" (sqxSort)="sortFields($event)">
<div class="nested-field" *ngFor="let nested of nested; trackBy: trackByField.bind(this)">
<span class="nested-field-line-h"></span>
@ -125,7 +125,7 @@
<ng-container *sqxModalView="addFieldDialog;onRoot:true;closeAuto:false;closeAlways:true">
<sqx-field-wizard [schema]="schema" [parent]="field"
(completed)="addFieldDialog.hide()">
(complete)="addFieldDialog.hide()">
</sqx-field-wizard>
</ng-container>
</ng-container>

4
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<sqx-modal-dialog (closed)="complete()">
<sqx-modal-dialog (close)="emitComplete()">
<ng-container title>
Edit Schema
</ng-container>
@ -29,7 +29,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary">Save</button>
</ng-container>
</sqx-modal-dialog>

8
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts

@ -21,7 +21,7 @@ import {
})
export class SchemaEditFormComponent implements OnInit {
@Output()
public completed = new EventEmitter();
public complete = new EventEmitter();
@Input()
public schema: SchemaDetailsDto;
@ -38,8 +38,8 @@ export class SchemaEditFormComponent implements OnInit {
this.editForm.load(this.schema.properties);
}
public complete() {
this.completed.emit();
public emitComplete() {
this.complete.emit();
}
public saveSchema() {
@ -48,7 +48,7 @@ export class SchemaEditFormComponent implements OnInit {
if (value) {
this.schemasState.update(this.schema, value)
.subscribe(() => {
this.complete();
this.emitComplete();
}, error => {
this.editForm.submitFailed(error);
});

12
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -69,7 +69,7 @@
<ng-container *ngIf="patternsState.patterns | async; let patterns">
<div class="schemas"
[sqxSortModel]="schema.fields"
(sqxSorted)="sortFields($event)">
(sqxSort)="sortFields($event)">
<div *ngFor="let field of schema.fields; trackBy: trackByField.bind(this)">
<sqx-field [field]="field" [schema]="schema" [patterns]="patterns"></sqx-field>
</div>
@ -94,30 +94,30 @@
<ng-container *sqxModalView="editSchemaDialog;onRoot:true">
<sqx-schema-edit-form [schema]="schema"
(completed)="editSchemaDialog.hide()">
(complete)="editSchemaDialog.hide()">
</sqx-schema-edit-form>
</ng-container>
<ng-container *sqxModalView="addFieldDialog;onRoot:true;closeAuto:false">
<sqx-field-wizard [schema]="schema"
(completed)="addFieldDialog.hide()">
(complete)="addFieldDialog.hide()">
</sqx-field-wizard>
</ng-container>
<ng-container *sqxModalView="configureScriptsDialog;onRoot:true">
<sqx-schema-scripts-form [schema]="schema"
(completed)="configureScriptsDialog.hide()">
(complete)="configureScriptsDialog.hide()">
</sqx-schema-scripts-form>
</ng-container>
<ng-container *sqxModalView="configurePreviewUrlsDialog;onRoot:true">
<sqx-schema-preview-urls-form [schema]="schema"
(completed)="configurePreviewUrlsDialog.hide()">
(complete)="configurePreviewUrlsDialog.hide()">
</sqx-schema-preview-urls-form>
</ng-container>
<ng-container *sqxModalView="exportSchemaDialog;onRoot:true">
<sqx-modal-dialog (closed)="exportSchemaDialog.hide()" large="true">
<sqx-modal-dialog (close)="exportSchemaDialog.hide()" large="true">
<ng-container title>
Export Schema
</ng-container>

4
src/Squidex/app/features/schemas/pages/schema/schema-preview-urls-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<sqx-modal-dialog (closed)="complete()" large="true">
<sqx-modal-dialog (close)="emitComplete()" large="true">
<ng-container title>
Preview Urls
</ng-container>
@ -58,7 +58,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="editForm.submitted | async">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()" [disabled]="editForm.submitted | async">Cancel</button>
<button type="submit" class="float-right btn btn-primary">Save</button>
</ng-container>
</sqx-modal-dialog>

8
src/Squidex/app/features/schemas/pages/schema/schema-preview-urls-form.component.ts

@ -22,7 +22,7 @@ import {
})
export class SchemaPreviewUrlsFormComponent implements OnInit {
@Output()
public completed = new EventEmitter();
public complete = new EventEmitter();
@Input()
public schema: SchemaDetailsDto;
@ -41,8 +41,8 @@ export class SchemaPreviewUrlsFormComponent implements OnInit {
this.editForm.load(this.schema.previewUrls);
}
public complete() {
this.completed.emit();
public emitComplete() {
this.complete.emit();
}
public cancelAdd() {
@ -65,7 +65,7 @@ export class SchemaPreviewUrlsFormComponent implements OnInit {
if (value) {
this.schemasState.configurePreviewUrls(this.schema, value)
.subscribe(() => {
this.complete();
this.emitComplete();
}, error => {
this.editForm.submitFailed(error);
});

4
src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<sqx-modal-dialog (closed)="complete()" large="true">
<sqx-modal-dialog (close)="emitComplete()" large="true">
<ng-container title>
Scripts
</ng-container>
@ -23,7 +23,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="editForm.submitted | async">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()" [disabled]="editForm.submitted | async">Cancel</button>
<button type="submit" class="float-right btn btn-primary">Save</button>
</ng-container>
</sqx-modal-dialog>

8
src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts

@ -21,7 +21,7 @@ import {
})
export class SchemaScriptsFormComponent implements OnInit {
@Output()
public completed = new EventEmitter();
public complete = new EventEmitter();
@Input()
public schema: SchemaDetailsDto;
@ -40,8 +40,8 @@ export class SchemaScriptsFormComponent implements OnInit {
this.editForm.load(this.schema.scripts);
}
public complete() {
this.completed.emit();
public emitComplete() {
this.complete.emit();
}
public selectField(field: string) {
@ -54,7 +54,7 @@ export class SchemaScriptsFormComponent implements OnInit {
if (value) {
this.schemasState.configureScripts(this.schema, value)
.subscribe(() => {
this.complete();
this.emitComplete();
}, error => {
this.editForm.submitFailed(error);
});

4
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="createForm.form" (ngSubmit)="createSchema()">
<sqx-modal-dialog (closed)="cancel()" [large]="false">
<sqx-modal-dialog (close)="emitCancel()" [large]="false">
<ng-container title>
<ng-container *ngIf="import; else noImport">
Clone Schema
@ -84,7 +84,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitCancel()">Cancel</button>
<button type="submit" class="float-right btn btn-success">Create</button>
</ng-container>
</sqx-modal-dialog>

14
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -23,10 +23,10 @@ import {
})
export class SchemaFormComponent implements OnInit {
@Output()
public created = new EventEmitter<SchemaDto>();
public complete = new EventEmitter<SchemaDto>();
@Output()
public cancelled = new EventEmitter();
public cancel = new EventEmitter();
@Input()
public import: any;
@ -55,12 +55,12 @@ export class SchemaFormComponent implements OnInit {
return false;
}
public complete(schema: SchemaDto) {
this.created.emit(schema);
public emitComplete(value: SchemaDto) {
this.complete.emit(value);
}
public cancel() {
this.cancelled.emit();
public emitCancel() {
this.cancel.emit();
}
public createSchema() {
@ -71,7 +71,7 @@ export class SchemaFormComponent implements OnInit {
this.schemasState.create(schemaDto)
.subscribe(dto => {
this.complete(dto);
this.emitComplete(dto);
}, error => {
this.createForm.submitFailed(error);
});

6
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -25,7 +25,7 @@
[name]="category"
[schemas]="schemasState.schemas | async"
[schemasFilter]="schemasFilter.valueChanges | async"
(removing)="removeCategory(category)">
(remove)="removeCategory(category)">
</sqx-schema-category>
<form [formGroup]="addCategoryForm.form" (ngSubmit)="addCategory()">
@ -36,8 +36,8 @@
<ng-container *sqxModalView="addSchemaDialog;onRoot:true">
<sqx-schema-form [import]="import"
(cancelled)="addSchemaDialog.hide()"
(created)="redirectSchema($event)">
(cancel)="addSchemaDialog.hide()"
(create)="redirectSchema($event)">
</sqx-schema-form>
</ng-container>

2
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -80,7 +80,7 @@
</div>
</div>
<sqx-modal-dialog *sqxModalView="connectDialog;onRoot:true" large="true" (closed)="connectDialog.hide()">
<sqx-modal-dialog *sqxModalView="connectDialog;onRoot:true" large="true" (close)="connectDialog.hide()">
<ng-container title>
Connect
</ng-container>

15
src/Squidex/app/framework/angular/forms/tag-editor.component.ts

@ -91,6 +91,12 @@ interface State {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TagEditorComponent extends StatefulControlComponent<State, any[]> implements AfterViewInit, OnInit {
@ViewChild('form')
public formElement: ElementRef<HTMLElement>;
@ViewChild('input')
public inputElement: ElementRef<HTMLInputElement>;
@Input()
public converter: Converter = new StringConverter();
@ -118,11 +124,10 @@ export class TagEditorComponent extends StatefulControlComponent<State, any[]> i
@Input()
public inputName = 'tag-editor';
@ViewChild('form')
public formElement: ElementRef<HTMLElement>;
@ViewChild('input')
public inputElement: ElementRef<HTMLInputElement>;
@Input()
public set disabled(value: boolean) {
this.setDisabledState(value);
}
public addInput = new FormControl();

4
src/Squidex/app/framework/angular/modals/dialog-renderer.component.html

@ -1,6 +1,6 @@
<ng-content></ng-content>
<sqx-modal-dialog *sqxModalView="dialogView;onRoot:true" showClose="false" (closed)="cancel()">
<sqx-modal-dialog *sqxModalView="dialogView;onRoot:true" showClose="false" (close)="cancel()">
<ng-container title>
{{snapshot.dialogRequest?.title}}
</ng-container>
@ -17,7 +17,7 @@
<div class="notification-container notification-container-bottom-right">
<div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of snapshot.notifications" (click)="close(notification)" @fade>
<button type="button" class="close" data-dismiss="alert" (closed)="close(notification)">&times;</button>
<button type="button" class="close" data-dismiss="alert" (close)="close(notification)">&times;</button>
<span [innerHTML]="notification.message"></span>
</div>

4
src/Squidex/app/framework/angular/modals/modal-dialog.component.html

@ -8,7 +8,7 @@
<ng-content select="[title]"></ng-content>
</h4>
<button type="button" class="close" (click)="closed.emit()">
<button type="button" class="close" (click)="close()">
<span aria-hidden="true">&times;</span>
</button>
</div>
@ -30,4 +30,4 @@
</div>
</div>
<sqx-shortcut keys="escape" (trigger)="closed.emit()"></sqx-shortcut>
<sqx-shortcut keys="escape" (trigger)="emitClose()"></sqx-shortcut>

6
src/Squidex/app/framework/angular/modals/modal-dialog.component.ts

@ -49,7 +49,7 @@ export class ModalDialogComponent extends StatefulComponent<State> implements Af
public contentClass = '';
@Output()
public closed = new EventEmitter();
public close = new EventEmitter();
@ViewChild('tabsElement')
public tabsElement: ElementRef<ParentNode>;
@ -70,4 +70,8 @@ export class ModalDialogComponent extends StatefulComponent<State> implements Af
this.next(() => ({ hasTabs, hasFooter }));
}
public emitClose() {
this.close.emit();
}
}

4
src/Squidex/app/framework/angular/pager.component.html

@ -2,10 +2,10 @@
<div class="float-right pagination">
<span class="pagination-text">{{pager.itemFirst}}-{{pager.itemLast}} of {{pager.numberOfItems}}</span>
<button type="button" class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoPrev" (click)="prevPage.emit()">
<button type="button" class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoPrev" (click)="emitPrev()">
<i class="icon-angle-left"></i>
</button>
<button type="button" class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoNext" (click)="nextPage.emit()">
<button type="button" class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoNext" (click)="emitNext()">
<i class="icon-angle-right"></i>
</button>
</div>

8
src/Squidex/app/framework/angular/pager.component.ts

@ -27,4 +27,12 @@ export class PagerComponent {
@Input()
public hideWhenButtonsDisabled = false;
public emitNext() {
this.nextPage.emit();
}
public emitPrev() {
this.prevPage.emit();
}
}

6
src/Squidex/app/framework/angular/sorted.directive.ts

@ -21,8 +21,8 @@ export class SortedDirective implements OnDestroy, OnInit {
@Input('sqxSortModel')
public sortModel: any[];
@Output('sqxSorted')
public sorted = new EventEmitter<any[]>();
@Output('sqxSort')
public sort = new EventEmitter<any[]>();
constructor(
private readonly elementRef: ElementRef
@ -49,7 +49,7 @@ export class SortedDirective implements OnDestroy, OnInit {
newModel.splice(event.oldIndex, 1);
newModel.splice(event.newIndex, 0, item);
this.sorted.emit(newModel);
this.sort.emit(newModel);
}
},

14
src/Squidex/app/framework/state.ts

@ -98,7 +98,7 @@ export class Form<T extends AbstractControl> {
}
export class Model {
protected clone(update: ((v: any) => object) | object): any {
protected clone(update: ((v: any) => object) | object, validOnly = false): any {
let values: object;
if (Types.isFunction(update)) {
values = update(<any>this);
@ -106,7 +106,17 @@ export class Model {
values = update;
}
const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this, values);
const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
for (let key in values) {
if (values.hasOwnProperty(key)) {
let value = values[key];
if (value || validOnly) {
clone[key] = value;
}
}
}
if (Types.isFunction(clone.onCloned)) {
clone.onCloned();

4
src/Squidex/app/shared/components/app-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="createForm.form" (ngSubmit)="createApp()">
<sqx-modal-dialog (closed)="complete()">
<sqx-modal-dialog (close)="emitComplete()">
<ng-container title>
<ng-container *ngIf="template; else noTemplate">
Create {{template}} Sample
@ -33,7 +33,7 @@
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()" [disabled]="createForm.submitted | async">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()" [disabled]="createForm.submitted | async">Cancel</button>
<button type="submit" class="float-right btn btn-success">Create</button>
</ng-container>
</sqx-modal-dialog>

8
src/Squidex/app/shared/components/app-form.component.ts

@ -23,7 +23,7 @@ import {
})
export class AppFormComponent {
@Output()
public completed = new EventEmitter();
public complete = new EventEmitter();
@Input()
public template = '';
@ -37,8 +37,8 @@ export class AppFormComponent {
) {
}
public complete() {
this.completed.emit();
public emitComplete() {
this.complete.emit();
}
public createApp() {
@ -49,7 +49,7 @@ export class AppFormComponent {
this.appsStore.create(request)
.subscribe(() => {
this.complete();
this.emitComplete();
}, error => {
this.createForm.submitFailed(error);
});

40
src/Squidex/app/shared/components/asset-dialog.component.html

@ -0,0 +1,40 @@
<form [formGroup]="annotateForm.form" (ngSubmit)="annotateAsset()">
<sqx-modal-dialog (close)="emitCancel()">
<ng-container title>
Update Asset
</ng-container>
<ng-container content>
<sqx-form-error [error]="annotateForm.error | async"></sqx-form-error>
<div class="form-group">
<label for="fileName">Name</label>
<sqx-control-errors for="fileName" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="fileName" formControlName="fileName" />
</div>
<div class="form-group">
<label for="slug">Slug</label>
<sqx-control-errors for="slug" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="slug" formControlName="slug" sqxTransformInput="Slugify" />
</div>
<div class="form-group">
<label for="tags">Tags</label>
<sqx-control-errors for="tags" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<sqx-tag-editor [suggestions]="allTags" [allowDuplicates]="false" [undefinedWhenEmpty]="false" formControlName="tags"></sqx-tag-editor>
</div>
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="emitCancel()">Cancel</button>
<button type="submit" class="float-right btn btn-primary">Save</button>
</ng-container>
</sqx-modal-dialog>
</form>

2
src/Squidex/app/shared/components/asset-dialog.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

78
src/Squidex/app/shared/components/asset-dialog.component.ts

@ -0,0 +1,78 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
AnnotateAssetForm,
AppsState,
AssetDto,
AssetsService,
AuthService,
StatefulComponent
} from '@app/shared/internal';
@Component({
selector: 'sqx-asset-dialog',
styleUrls: ['./asset-dialog.component.scss'],
templateUrl: './asset-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetDialogComponent extends StatefulComponent implements OnInit {
@Input()
public asset: AssetDto;
@Input()
public allTags: string[];
@Output()
public cancel = new EventEmitter();
@Output()
public complete = new EventEmitter<AssetDto>();
public annotateForm = new AnnotateAssetForm(this.formBuilder);
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly formBuilder: FormBuilder
) {
super(changeDetector, {
isRenaming: false,
isTagging: false,
progress: 0
});
}
public ngOnInit() {
this.annotateForm.load(this.asset);
}
public emitCancel() {
this.cancel.emit();
}
public emitComplete(asset: AssetDto) {
this.complete.emit(asset);
}
public annotateAsset() {
const value = this.annotateForm.submit(this.asset);
if (value) {
this.assetsService.putAsset(this.appsState.appName, this.asset.id, value, this.asset.version)
.subscribe(dto => {
this.emitComplete(this.asset.annnotate(value, this.authState.user!.token, dto.version));
}, error => {
this.annotateForm.submitFailed(error);
});
}
}
}

56
src/Squidex/app/shared/components/asset.component.html

@ -1,5 +1,5 @@
<ng-container *ngIf="!isListView; else listTemplate">
<div class="card" [class.selectable]="isSelectable" [class.border-primary]="isSelected" (click)="selected.emit(asset)" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="card" [class.selectable]="isSelectable" [class.border-primary]="isSelected" (click)="emitSelect()" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="card-body">
<div class="file-preview" *ngIf="asset && snapshot.progress === 0" @fade>
<span class="file-type" *ngIf="asset.fileType">
@ -17,14 +17,16 @@
<div class="overlay-background"></div>
<div class="overlay-menu">
<a class="file-download" [href]="asset | sqxAssetUrl" sqxExternalLink="noicon" (click)="$event.stopPropagation()">
<a class="file-edit ml-2" (click)="edit()" *ngIf="!isDisabled">
<i class="icon-pencil"></i>
</a>
<a class="file-download ml-2" [href]="asset | sqxAssetUrl" sqxExternalLink="noicon" (click)="$event.stopPropagation()">
<i class="icon-download"></i>
</a>
<a class="file-delete ml-2" (click)="deleting.emit(asset)" *ngIf="!isDisabled && !removeMode">
<a class="file-delete ml-2" (click)="emitDelete()" *ngIf="!isDisabled && !removeMode">
<i class="icon-delete"></i>
</a>
<a class="file-delete ml-2" (click)="removing.emit(asset)" *ngIf="removeMode">
<a class="file-delete ml-2" (click)="emitRemove()" *ngIf="removeMode">
<i class="icon-close"></i>
</a>
</div>
@ -53,28 +55,15 @@
<div class="drop-overlay-text">Drop to update</div>
</div>
</div>
<div class="card-footer">
<div class="card-footer" (dblclick)="edit()">
<ng-container *ngIf="asset">
<div>
<div *ngIf="!snapshot.isRenaming" class="file-name editable" (dblclick)="renameStart()">
<div class="file-name editable" (click)="edit()">
{{asset.fileName}}
</div>
<div *ngIf="snapshot.isRenaming">
<form [formGroup]="renameForm.form" (ngSubmit)="renameAsset()">
<sqx-control-errors for="name" [submitted]="renameForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control form-underlined editable" id="assetName" formControlName="name" autocomplete="off" spellcheck="false" sqxFocusOnInit (blur)="renameCancel()" />
</form>
</div>
</div>
<div class="file-tags tags">
<sqx-tag-editor
[suggestions]="allTags"
[acceptEnter]="true"
[allowDuplicates]="false"
[undefinedWhenEmpty]="false"
[formControl]="tagInput" class="blank" placeholder="+Tag">
</sqx-tag-editor>
<sqx-tag-editor [disabled]="true" [ngModel]="asset.tags" class="blank"></sqx-tag-editor>
</div>
<div class="file-info">
<ng-container *ngIf="asset.pixelWidth">{{asset.pixelWidth}}x{{asset.pixelHeight}}px, </ng-container> {{asset.fileSize | sqxFileSize}}
@ -85,7 +74,7 @@
</ng-container>
<ng-template #listTemplate>
<div class="table-items-row" [class.selectable]="isSelectable" (click)="selected.emit(asset)" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="table-items-row" [class.selectable]="isSelectable" (click)="emitSelect()" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="left-border" [class.hidden]="!isSelectable" [class.selected]="isSelected" ></div>
<div *ngIf="asset && asset.canPreview && snapshot.progress === 0" class="image drag-handle" [class.image-left]="!isSelectable" @fade>
@ -98,16 +87,9 @@
<table class="table-fixed" *ngIf="asset && snapshot.progress === 0" @fade>
<tr>
<td class="col-name">
<div *ngIf="!snapshot.isRenaming" class="file-name editable" (dblclick)="renameStart()">
<div class="file-name editable" (click)="edit()">
{{asset.fileName}}
</div>
<div *ngIf="snapshot.isRenaming">
<form [formGroup]="renameForm.form" (ngSubmit)="renameAsset()">
<sqx-control-errors for="name" [submitted]="renameForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control editable form-underlined" id="assetName" formControlName="name" autocomplete="off" spellcheck="false" sqxFocusOnInit (blur)="renameCancel()" />
</form>
</div>
</td>
<td class="col-info" *ngIf="!isCompact">
<ng-container *ngIf="asset.pixelWidth">{{asset.pixelWidth}}x{{asset.pixelHeight}}px, </ng-container> {{asset.fileSize | sqxFileSize}}
@ -121,10 +103,10 @@
</a>
</td>
<td class="col-actions text-right" *ngIf="!isDisabled || removeMode">
<button type="button" class="btn btn-text-danger" (click)="deleting.emit(asset)" *ngIf="!isDisabled && !removeMode">
<button type="button" class="btn btn-text-danger" (click)="emitDelete()" *ngIf="!isDisabled && !removeMode">
<i class="icon-bin2"></i>
</button>
<button type="button" class="btn btn-text-secondary" (click)="removing.emit(asset)" *ngIf="removeMode">
<button type="button" class="btn btn-text-secondary" (click)="emitRemove()" *ngIf="removeMode">
<i class="icon-close"></i>
</button>
</td>
@ -140,4 +122,12 @@
<div class="drop-overlay-text">Drop to update</div>
</div>
</div>
</ng-template>
</ng-template>
<ng-container *ngIf="asset">
<sqx-asset-dialog *sqxModalView="editDialog;onRoot:true"
[asset]="asset" [allTags]="allTags"
(cancel)="cancelEdit()"
(complete)="updateAsset($event, true)">
</sqx-asset-dialog>
</ng-container>

120
src/Squidex/app/shared/components/asset.component.ts

@ -5,9 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnInit, Output } from '@angular/core';
import {
AppsState,
@ -15,20 +13,15 @@ import {
AssetsService,
AuthService,
DateTime,
DialogModel,
DialogService,
fadeAnimation,
RenameAssetDto,
RenameAssetForm,
StatefulComponent,
TagAssetDto,
Types,
Versioned
} from '@app/shared/internal';
interface State {
isTagging: boolean;
isRenaming: boolean;
progress: number;
}
@ -41,7 +34,7 @@ interface State {
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetComponent extends StatefulComponent<State> implements OnChanges, OnInit {
export class AssetComponent extends StatefulComponent<State> implements OnInit {
@Input()
public initFile: File;
@ -70,37 +63,32 @@ export class AssetComponent extends StatefulComponent<State> implements OnChange
public allTags: string[];
@Output()
public loaded = new EventEmitter<AssetDto>();
public load = new EventEmitter<AssetDto>();
@Output()
public removing = new EventEmitter<AssetDto>();
public loadError = new EventEmitter();
@Output()
public updated = new EventEmitter<AssetDto>();
public remove = new EventEmitter<AssetDto>();
@Output()
public deleting = new EventEmitter<AssetDto>();
public update = new EventEmitter<AssetDto>();
@Output()
public selected = new EventEmitter<AssetDto>();
public delete = new EventEmitter<AssetDto>();
@Output()
public failed = new EventEmitter();
public renameForm = new RenameAssetForm(this.formBuilder);
public select = new EventEmitter<AssetDto>();
public tagInput = new FormControl();
public editDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder
private readonly dialogs: DialogService
) {
super(changeDetector, {
isRenaming: false,
isTagging: false,
progress: 0
});
}
@ -114,30 +102,16 @@ export class AssetComponent extends StatefulComponent<State> implements OnChange
this.assetsService.uploadFile(this.appsState.appName, initFile, this.authState.user!.token, DateTime.now())
.subscribe(dto => {
if (Types.is(dto, AssetDto)) {
this.emitLoaded(dto);
this.emitLoad(dto);
} else {
this.setProgress(dto);
}
}, error => {
this.dialogs.notifyError(error);
this.emitFailed(error);
this.emitLoadError(error);
});
}
this.own(
this.tagInput.valueChanges.pipe(
distinctUntilChanged(),
debounceTime(2000)
).subscribe(tags => {
this.tagAsset(tags);
}));
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['asset'] && this.asset) {
this.tagInput.setValue(this.asset.tags, { emitEvent: false });
}
}
public updateFile(files: FileList) {
@ -159,77 +133,53 @@ export class AssetComponent extends StatefulComponent<State> implements OnChange
}
}
public renameAsset() {
const value = this.renameForm.submit(this.asset);
if (value) {
const requestDto = new RenameAssetDto(value.name);
this.assetsService.putAsset(this.appsState.appName, this.asset.id, requestDto, this.asset.version)
.subscribe(dto => {
this.updateAsset(this.asset.rename(requestDto.fileName, this.authState.user!.token, dto.version), true);
}, error => {
this.dialogs.notifyError(error);
this.renameForm.submitFailed(error);
});
public edit() {
if (!this.isDisabled) {
this.editDialog.show();
}
}
public tagAsset(tags: string[]) {
if (tags) {
const requestDto = new TagAssetDto(tags);
this.assetsService.putAsset(this.appsState.appName, this.asset.id, requestDto, this.asset.version)
.subscribe(dto => {
this.updateAsset(this.asset.tag(tags, this.authState.user!.token, dto.version), true);
}, error => {
this.dialogs.notifyError(error);
});
}
public cancelEdit() {
this.editDialog.hide();
}
public renameStart() {
if (!this.isDisabled) {
this.renameForm.load(this.asset);
this.next(s => ({ ...s, isRenaming: true }));
}
public emitSelect() {
this.select.emit(this.asset);
}
public renameCancel() {
this.renameForm.submitCompleted();
public emitDelete() {
this.delete.emit(this.asset);
}
this.next(s => ({ ...s, isRenaming: false }));
public emitLoad(asset: AssetDto) {
this.load.emit(asset);
}
private emitFailed(error: any) {
this.failed.emit(error);
public emitLoadError(error: any) {
this.loadError.emit(error);
}
private emitLoaded(asset: AssetDto) {
this.loaded.emit(asset);
public emitUpdate() {
this.update.emit(this.asset);
}
private emitUpdated(asset: AssetDto) {
this.updated.emit(asset);
public emitRemove() {
this.remove.emit(this.asset);
}
private setProgress(progress: number) {
this.next(s => ({ ...s, progress }));
}
private updateAsset(asset: AssetDto, emitEvent: boolean) {
public updateAsset(asset: AssetDto, emitEvent: boolean) {
this.asset = asset;
this.tagInput.setValue(asset.tags, { emitEvent: false });
if (emitEvent) {
this.emitUpdated(asset);
this.emitUpdate();
}
this.renameCancel();
this.next(s => ({ ...s, progress: 0 }));
this.cancelEdit();
}
}

10
src/Squidex/app/shared/components/assets-list.component.html

@ -17,8 +17,8 @@
<div class="row assets" [class.unrow]="isListView" *ngIf="state.tagsNames | async; let tags" (paste)="addFiles($event)">
<sqx-asset *ngFor="let file of newFiles" [initFile]="file"
[isListView]="isListView"
(failed)="remove(file)"
(loaded)="add(file, $event)">
(loadError)="remove(file)"
(load)="add(file, $event)">
</sqx-asset>
<ng-container *ngIf="state.assets | async; let assets">
@ -28,9 +28,9 @@
[isSelectable]="selectedIds"
[isSelected]="isSelected(asset)"
[allTags]="tags"
(updated)="update($event)"
(selected)="select($event)"
(deleting)="delete($event)">
(update)="update($event)"
(select)="emitSelect($event)"
(delete)="delete($event)">
</sqx-asset>
</ng-container>
</div>

6
src/Squidex/app/shared/components/assets-list.component.ts

@ -36,7 +36,7 @@ export class AssetsListComponent {
public selectedIds: object;
@Output()
public selected = new EventEmitter<AssetDto>();
public select = new EventEmitter<AssetDto>();
public add(file: File, asset: AssetDto) {
this.newFiles = this.newFiles.remove(file);
@ -64,8 +64,8 @@ export class AssetsListComponent {
this.state.update(asset);
}
public select(asset: AssetDto) {
this.selected.emit(asset);
public emitSelect(asset: AssetDto) {
this.select.emit(asset);
}
public isSelected(asset: AssetDto) {

10
src/Squidex/app/shared/components/assets-selector.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog (closed)="complete()" large="true" fullHeight="true">
<sqx-modal-dialog (close)="emitComplete()" large="true" fullHeight="true">
<ng-container title>
Select assets
</ng-container>
@ -22,7 +22,7 @@
</div>
<div class="col-6">
<sqx-search-form formClass="form" placeholder="Search by asset name" fieldExample="fileSize"
(queryChanged)="search($event)"
(queryChange)="search($event)"
[query]="assetsState.assetsQuery | async"
enableShortcut="true">
</sqx-search-form>
@ -45,14 +45,14 @@
<ng-container content>
<sqx-assets-list
[isListView]="snapshot.isListView"
(selected)="selectAsset($event)"
(select)="selectAsset($event)"
[selectedIds]="snapshot.selectedAssets"
[state]="assetsState" isDisabled="true">
</sqx-assets-list>
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="submit" class="float-right btn btn-success" (click)="select()" [disabled]="snapshot.selectionCount === 0">Link selected assets ({{snapshot.selectionCount}})</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-success" (click)="emitSelect()" [disabled]="snapshot.selectionCount === 0">Link selected assets ({{snapshot.selectionCount}})</button>
</ng-container>
</sqx-modal-dialog>

10
src/Squidex/app/shared/components/assets-selector.component.ts

@ -34,7 +34,7 @@ interface State {
})
export class AssetsSelectorComponent extends StatefulComponent<State> implements OnInit {
@Output()
public selected = new EventEmitter<AssetDto[]>();
public select = new EventEmitter<AssetDto[]>();
constructor(changeDector: ChangeDetectorRef,
public readonly assetsState: AssetsDialogState,
@ -59,12 +59,12 @@ export class AssetsSelectorComponent extends StatefulComponent<State> implements
this.assetsState.search(query).pipe(onErrorResumeNext()).subscribe();
}
public complete() {
this.selected.emit([]);
public emitComplete() {
this.select.emit([]);
}
public select() {
this.selected.emit(Object.values(this.snapshot.selectedAssets));
public emitSelect() {
this.select.emit(Object.values(this.snapshot.selectedAssets));
}
public selectTags(tags: string[]) {

2
src/Squidex/app/shared/components/comment.component.html

@ -7,7 +7,7 @@
<div class="user-row">
<div class="user-ref">{{comment.user | sqxUserNameRef}}</div>
<button *ngIf="comment.user === userId" type="button" class="btn btn-sm btn-text-danger item-remove" (click)="deleting.emit()!">
<button *ngIf="comment.user === userId" type="button" class="btn btn-sm btn-text-danger item-remove" (click)="emitDelete()!">
<i class="icon-bin2"></i>
</button>
</div>

8
src/Squidex/app/shared/components/comment.component.ts

@ -26,13 +26,17 @@ export class CommentComponent {
public userId: string;
@Output()
public deleting = new EventEmitter();
public delete = new EventEmitter();
@Output()
public updated = new EventEmitter<string>();
public update = new EventEmitter<string>();
constructor(
private readonly formBuilder: FormBuilder
) {
}
public emitDelete() {
this.delete.emit();
}
}

4
src/Squidex/app/shared/components/comments.component.html

@ -8,8 +8,8 @@
<sqx-comment *ngFor="let comment of state.comments | async; trackBy: trackByComment"
[comment]="comment"
[userId]="userId"
(updated)="update(comment, $event)"
(deleting)="delete(comment)">
(update)="update(comment, $event)"
(delete)="delete(comment)">
</sqx-comment>
</div>

2
src/Squidex/app/shared/components/markdown-editor.component.html

@ -6,6 +6,6 @@
<ng-container *sqxModalView="assetsDialog;onRoot:true;closeAuto:false">
<sqx-assets-selector
(selected)="insertAssets($event)">
(select)="insertAssets($event)">
</sqx-assets-selector>
</ng-container>

2
src/Squidex/app/shared/components/rich-editor.component.html

@ -4,6 +4,6 @@
<ng-container *sqxModalView="assetsDialog;onRoot:true;closeAuto:false">
<sqx-assets-selector
(selected)="insertAssets($event)">
(select)="insertAssets($event)">
</sqx-assets-selector>
</ng-container>

2
src/Squidex/app/shared/components/rich-editor.component.ts

@ -52,7 +52,7 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
public editor: ElementRef;
@Output()
public assetPluginClicked = new EventEmitter<any>();
public assetPluginClick = new EventEmitter<any>();
public assetsDialog = new DialogModel();

2
src/Squidex/app/shared/components/schema-category.component.html

@ -8,7 +8,7 @@
<h3>{{snapshot.displayName}} ({{snapshot.schemasFiltered.length}})</h3>
<button type="button" class="btn btn-sm btn-text-secondary float-right" *ngIf="snapshot.schemasForCategory.length === 0 && !isReadonly" (click)="removing.emit()">
<button type="button" class="btn btn-sm btn-text-secondary float-right" *ngIf="snapshot.schemasForCategory.length === 0 && !isReadonly" (click)="emitRemove()">
<i class="icon-bin2"></i>
</button>
</div>

10
src/Squidex/app/shared/components/schema-category.component.ts

@ -38,9 +38,6 @@ interface State {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SchemaCategoryComponent extends StatefulComponent<State> implements OnInit, OnChanges {
@Output()
public removing = new EventEmitter();
@Input()
public name: string;
@ -56,6 +53,9 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
@Input()
public schemas: ImmutableArray<SchemaDto>;
@Output()
public remove = new EventEmitter();
public allowDrop = (schema: any) => {
return (Types.is(schema, SchemaDto) || Types.is(schema, SchemaDetailsDto)) && !this.isSameCategory(schema);
}
@ -130,6 +130,10 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
this.schemasState.changeCategory(schema, this.name).pipe(onErrorResumeNext()).subscribe();
}
public emitRemove() {
this.remove.emit();
}
public trackBySchema(index: number, schema: SchemaDto) {
return schema.id;
}

2
src/Squidex/app/shared/components/search-form.component.html

@ -77,7 +77,7 @@
<ng-container *sqxModalView="saveQueryDialog;onRoot:true">
<form [formGroup]="saveQueryForm.form" (ngSubmit)="saveQueryComplete()">
<sqx-modal-dialog (closed)="saveQueryDialog.hide()">
<sqx-modal-dialog (close)="saveQueryDialog.hide()">
<ng-container title>
Name your query
</ng-container>

8
src/Squidex/app/shared/components/search-form.component.ts

@ -40,13 +40,13 @@ export class SearchFormComponent implements OnChanges, OnInit {
public query = '';
@Output()
public queryChanged = new EventEmitter<string>();
public queryChange = new EventEmitter<string>();
@Input()
public archived = false;
@Output()
public archivedChanged = new EventEmitter<boolean>();
public archivedChange = new EventEmitter<boolean>();
@Input()
public schemaName = '';
@ -113,7 +113,7 @@ export class SearchFormComponent implements OnChanges, OnInit {
public search() {
this.invalidate(this.contentsFilter.value);
this.queryChanged.emit(this.contentsFilter.value);
this.queryChange.emit(this.contentsFilter.value);
}
private invalidate(query: string) {
@ -186,7 +186,7 @@ export class SearchFormComponent implements OnChanges, OnInit {
}
if (query !== this.query) {
this.queryChanged.emit(query);
this.queryChange.emit(query);
}
this.contentsFilter.setValue(query);

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

@ -6,6 +6,7 @@
*/
export * from './components/app-form.component';
export * from './components/asset-dialog.component';
export * from './components/asset.component';
export * from './components/assets-list.component';
export * from './components/assets-selector.component';

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

@ -23,6 +23,7 @@ import {
AppsService,
AppsState,
AssetComponent,
AssetDialogComponent,
AssetPreviewUrlPipe,
AssetsDialogState,
AssetsListComponent,
@ -102,6 +103,7 @@ import {
declarations: [
AppFormComponent,
AssetComponent,
AssetDialogComponent,
AssetPreviewUrlPipe,
AssetUrlPipe,
AssetsListComponent,
@ -131,6 +133,7 @@ import {
exports: [
AppFormComponent,
AssetComponent,
AssetDialogComponent,
AssetPreviewUrlPipe,
AssetUrlPipe,
AssetsListComponent,

68
src/Squidex/app/shared/services/assets.service.spec.ts

@ -10,6 +10,7 @@ import { inject, TestBed } from '@angular/core/testing';
import {
AnalyticsService,
AnnotateAssetDto,
ApiUrlConfig,
AssetDto,
AssetReplacedDto,
@ -17,8 +18,6 @@ import {
AssetsService,
DateTime,
ErrorDto,
RenameAssetDto,
TagAssetDto,
Version,
Versioned
} from './../';
@ -31,21 +30,15 @@ describe('AssetDto', () => {
const version = new Version('1');
const newVersion = new Version('2');
it('should update name property and user info when renaming', () => {
const asset_1 = new AssetDto('1', creator, creator, creation, creation, 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, [], 'url', version);
const asset_2 = asset_1.rename('new-name.png', modifier, newVersion, modified);
it('should update tag property and user info when annnoting', () => {
const update = new AnnotateAssetDto('NewName.png', null, null);
expect(asset_2.fileName).toEqual('new-name.png');
expect(asset_2.lastModified).toEqual(modified);
expect(asset_2.lastModifiedBy).toEqual(modifier);
expect(asset_2.version).toEqual(newVersion);
});
const asset_1 = new AssetDto('1', creator, creator, creation, creation, 'Name.png', 'png', 1, 1, 'image/png', false, 1, 1, 'name.png', [], 'url', version);
const asset_2 = asset_1.annnotate(update, modifier, newVersion, modified);
it('should update tag property and user info when tagged', () => {
const asset_1 = new AssetDto('1', creator, creator, creation, creation, 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, [], 'url', version);
const asset_2 = asset_1.tag(['tag1', 'tag2'], modifier, newVersion, modified);
expect(asset_2.tags).toEqual(['tag1', 'tag2']);
expect(asset_2.fileName).toEqual('NewName.png');
expect(asset_2.tags).toEqual([]);
expect(asset_2.slug).toEqual(asset_1.slug);
expect(asset_2.lastModified).toEqual(modified);
expect(asset_2.lastModifiedBy).toEqual(modifier);
expect(asset_2.version).toEqual(newVersion);
@ -54,7 +47,7 @@ describe('AssetDto', () => {
it('should update file properties when uploading', () => {
const update = new AssetReplacedDto(2, 2, 'image/jpeg', true, 2, 2);
const asset_1 = new AssetDto('1', creator, creator, creation, creation, 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, [], 'url', version);
const asset_1 = new AssetDto('1', creator, creator, creation, creation, 'Name.png', 'png', 1, 1, 'image/png', false, 1, 1, 'name.png', [], 'url', version);
const asset_2 = asset_1.update(update, modifier, newVersion, modified);
expect(asset_2.fileSize).toEqual(2);
@ -139,7 +132,7 @@ describe('AssetsService', () => {
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileName: 'My Asset1.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2000,
@ -147,6 +140,7 @@ describe('AssetsService', () => {
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
slug: 'my-asset1.png',
tags: undefined,
version: 11
},
@ -156,7 +150,7 @@ describe('AssetsService', () => {
createdBy: 'Created2',
lastModified: '2017-10-12T10:10',
lastModifiedBy: 'LastModifiedBy2',
fileName: 'my-asset2.png',
fileName: 'My Asset2.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2000,
@ -164,6 +158,7 @@ describe('AssetsService', () => {
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
slug: 'my-asset2.png',
tags: ['tag1', 'tag2'],
version: 22
}
@ -176,7 +171,7 @@ describe('AssetsService', () => {
'id1', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
'My Asset1.png',
'png',
1024,
2000,
@ -184,13 +179,14 @@ describe('AssetsService', () => {
true,
1024,
2048,
'my-asset1.png',
[],
'http://service/p/api/assets/id1',
new Version('11')),
new AssetDto('id2', 'Created2', 'LastModifiedBy2',
DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'),
'my-asset2.png',
'My Asset2.png',
'png',
1024,
2000,
@ -198,6 +194,7 @@ describe('AssetsService', () => {
true,
1024,
2048,
'my-asset2.png',
['tag1', 'tag2'],
'http://service/p/api/assets/id2',
new Version('22'))
@ -224,7 +221,7 @@ describe('AssetsService', () => {
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileName: 'My Asset1.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2000,
@ -232,6 +229,7 @@ describe('AssetsService', () => {
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
slug: 'my-asset1.png',
tags: ['tag1', 'tag2']
}, {
headers: {
@ -244,7 +242,7 @@ describe('AssetsService', () => {
'id1', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
'My Asset1.png',
'png',
1024,
2000,
@ -252,6 +250,7 @@ describe('AssetsService', () => {
true,
1024,
2048,
'my-asset1.png',
['tag1', 'tag2'],
'http://service/p/api/assets/id1',
new Version('2')));
@ -312,7 +311,7 @@ describe('AssetsService', () => {
req.flush({
id: 'id1',
fileName: 'my-asset1.png',
fileName: 'My Asset1.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2,
@ -320,6 +319,7 @@ describe('AssetsService', () => {
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
slug: 'my-asset1.png',
tags: ['tag1', 'tag2']
}, {
headers: {
@ -334,13 +334,14 @@ describe('AssetsService', () => {
user,
now,
now,
'my-asset1.png',
'My Asset1.png',
'png',
1024, 2,
'image/png',
true,
1024,
2048,
'my-asset1.png',
['tag1', 'tag2'],
'http://service/p/api/assets/id1',
new Version('2')));
@ -424,25 +425,10 @@ describe('AssetsService', () => {
expect(error!).toEqual(new ErrorDto(413, 'Asset is too big.'));
}));
it('should make put request to update asset',
inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => {
const dto = new RenameAssetDto('My-Asset.pdf');
assetsService.putAsset('my-app', '123', dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets/123');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({});
}));
it('should make put request to update asset',
it('should make put request to annotate asset',
inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => {
const dto = new TagAssetDto(['tag1', 'tag2']);
const dto = new AnnotateAssetDto('My Asset.pdf', 'my-asset.pdf', ['tag1', 'tag2']);
assetsService.putAsset('my-app', '123', dto, version).subscribe();

38
src/Squidex/app/shared/services/assets.service.ts

@ -51,6 +51,7 @@ export class AssetDto extends Model {
public readonly isImage: boolean,
public readonly pixelWidth: number | null,
public readonly pixelHeight: number | null,
public readonly slug: string,
public readonly tags: string[],
public readonly url: string,
public readonly version: Version
@ -58,8 +59,8 @@ export class AssetDto extends Model {
super();
}
public with(value: Partial<AssetDto>): AssetDto {
return this.clone(value);
public with(value: Partial<AssetDto>, validOnly = false): AssetDto {
return this.clone(value, validOnly);
}
public update(update: AssetReplacedDto, user: string, version: Version, now?: DateTime): AssetDto {
@ -71,35 +72,21 @@ export class AssetDto extends Model {
});
}
public tag(tags: string[], user: string, version: Version, now?: DateTime): AssetDto {
public annnotate(update: AnnotateAssetDto, user: string, version: Version, now?: DateTime): AssetDto {
return this.with({
tags,
...<any>update,
lastModified: now || DateTime.now(),
lastModifiedBy: user,
version
});
}
public rename(fileName: string, user: string, version: Version, now?: DateTime): AssetDto {
return this.with({
fileName,
lastModified: now || DateTime.now(),
lastModifiedBy: user,
version
});
}
}
export class RenameAssetDto {
constructor(
public readonly fileName: string
) {
}, true);
}
}
export class TagAssetDto {
export class AnnotateAssetDto {
constructor(
public readonly tags: string[]
public readonly fileName: string | null,
public readonly slug: string | null,
public readonly tags: string[] | null
) {
}
}
@ -189,6 +176,7 @@ export class AssetsService {
item.isImage,
item.pixelWidth,
item.pixelHeight,
item.slug,
item.tags || [],
assetUrl,
new Version(item.version.toString()));
@ -231,6 +219,7 @@ export class AssetsService {
response.isImage,
response.pixelWidth,
response.pixelHeight,
response.slug,
response.tags || [],
assetUrl,
new Version(event.headers.get('etag')!));
@ -278,6 +267,7 @@ export class AssetsService {
body.isImage,
body.pixelWidth,
body.pixelHeight,
body.slug,
body.tags || [],
assetUrl,
response.version);
@ -340,7 +330,7 @@ export class AssetsService {
pretifyError('Failed to delete asset. Please reload.'));
}
public putAsset(appName: string, id: string, dto: RenameAssetDto | TagAssetDto, version: Version): Observable<Versioned<any>> {
public putAsset(appName: string, id: string, dto: AnnotateAssetDto, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return HTTP.putVersioned(this.http, url, dto, version).pipe(

44
src/Squidex/app/shared/state/assets.forms.ts

@ -7,14 +7,24 @@
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Form } from '@app/framework';
import { Form, Types } from '@app/framework';
import { AssetDto } from './../services/assets.service';
export class RenameAssetForm extends Form<FormGroup> {
export class AnnotateAssetForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
name: ['',
fileName: ['',
[
Validators.required
]
],
slug: ['',
[
Validators.required
]
],
tags: ['',
[
Validators.required
]
@ -27,8 +37,25 @@ export class RenameAssetForm extends Form<FormGroup> {
if (asset) {
let index = asset.fileName.lastIndexOf('.');
if (index > 0) {
result.name += asset.fileName.substr(index);
result.fileName += asset.fileName.substr(index);
}
if (result.fileName === asset.fileName) {
delete result.fileName;
}
if (result.slug === asset.slug) {
delete result.slug;
}
if (Types.jsJsonEquals(result.tags, asset.tags)) {
delete result.tags;
}
if (Object.keys(result).length === 0) {
return null;
}
}
@ -36,13 +63,14 @@ export class RenameAssetForm extends Form<FormGroup> {
}
public load(asset: AssetDto) {
let name = asset.fileName;
let fileName = asset.fileName;
let index = fileName.lastIndexOf('.');
let index = name.lastIndexOf('.');
if (index > 0) {
name = name.substr(0, index);
fileName = fileName.substr(0, index);
}
super.load({ name });
super.load({ fileName, slug: asset.slug, tags: asset.tags });
}
}

8
src/Squidex/app/shared/state/assets.state.spec.ts

@ -30,8 +30,8 @@ describe('AssetsState', () => {
const newVersion = new Version('2');
const oldAssets = [
new AssetDto('id1', creator, creator, creation, creation, 'name1', 'type1', 1, 1, 'mime1', false, null, null, ['tag1', 'shared'], 'url1', version),
new AssetDto('id2', creator, creator, creation, creation, 'name2', 'type2', 2, 2, 'mime2', false, null, null, ['tag2', 'shared'], 'url2', version)
new AssetDto('id1', creator, creator, creation, creation, 'name1', 'type1', 1, 1, 'mime1', false, null, null, 'slug1', ['tag1', 'shared'], 'url1', version),
new AssetDto('id2', creator, creator, creation, creation, 'name2', 'type2', 2, 2, 'mime2', false, null, null, 'slug2', ['tag2', 'shared'], 'url2', version)
];
let dialogs: IMock<DialogService>;
@ -81,7 +81,7 @@ describe('AssetsState', () => {
});
it('should add asset to snapshot when created', () => {
const newAsset = new AssetDto('id3', creator, creator, creation, creation, 'name3', 'type3', 3, 3, 'mime3', true, 0, 0, [], 'url3', version);
const newAsset = new AssetDto('id3', creator, creator, creation, creation, 'name3', 'type3', 3, 3, 'mime3', true, 0, 0, 'slug2', [], 'url3', version);
assetsState.add(newAsset);
@ -90,7 +90,7 @@ describe('AssetsState', () => {
});
it('should update properties when updated', () => {
const newAsset = new AssetDto('id1', modifier, modifier, modified, modified, 'name3', 'type3', 3, 3, 'mime3', true, 0, 0, ['new'], 'url3', version);
const newAsset = new AssetDto('id1', modifier, modifier, modified, modified, 'name3', 'type3', 3, 3, 'mime3', true, 0, 0, 'slug3', ['new'], 'url3', version);
assetsState.update(newAsset);

2
src/Squidex/app/shell/pages/internal/apps-menu.component.html

@ -57,6 +57,6 @@
<ng-container *sqxModalView="addAppDialog;onRoot:true">
<sqx-app-form
(completed)="addAppDialog.hide()">
(complete)="addAppDialog.hide()">
</sqx-app-form>
</ng-container>

2
src/Squidex/appsettings.json

@ -189,7 +189,7 @@
*
* Supported: MongoDB, Development
*/
"clustering": "MongoDb",
"clustering": "Development",
/*
* The port is used to share messages between all cluster members. Must be accessible within your cluster or network.
*/

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs

@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
public string FileName { get; set; }
public string FileNameSlug { get; set; }
public string Slug { get; set; }
public long FileSize { get; set; }

2
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
new object[] { new AssetCreated(), EnrichedAssetEventType.Created },
new object[] { new AssetUpdated(), EnrichedAssetEventType.Updated },
new object[] { new AssetRenamed(), EnrichedAssetEventType.Renamed },
new object[] { new AssetAnnotated(), EnrichedAssetEventType.Annotated },
new object[] { new AssetDeleted(), EnrichedAssetEventType.Deleted }
};

35
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs

@ -76,7 +76,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
MimeType = file.MimeType,
PixelWidth = image.PixelWidth,
PixelHeight = image.PixelHeight,
Tags = new HashSet<string>()
Tags = new HashSet<string>(),
Slug = file.FileName.ToAssetSlug()
})
);
}
@ -109,9 +110,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
}
[Fact]
public async Task Rename_should_create_events()
public async Task AnnotateName_should_create_events()
{
var command = new RenameAsset { FileName = "My New Image.png" };
var command = new AnnotateAsset { FileName = "My New Image.png" };
await ExecuteCreateAsync();
@ -120,18 +121,36 @@ namespace Squidex.Domain.Apps.Entities.Assets
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal("My New Image.png", sut.Snapshot.FileName);
Assert.Equal("my-new-image.png", sut.Snapshot.FileNameSlug);
LastEvents
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetRenamed { FileName = "My New Image.png" })
CreateAssetEvent(new AssetAnnotated { FileName = "My New Image.png" })
);
}
[Fact]
public async Task Tag_should_create_events()
public async Task AnnotateSlug_should_create_events()
{
var command = new TagAsset();
var command = new AnnotateAsset { Slug = "my-new-image.png" };
await ExecuteCreateAsync();
var result = await sut.ExecuteAsync(CreateAssetCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal("my-new-image.png", sut.Snapshot.Slug);
LastEvents
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetAnnotated { Slug = "my-new-image.png" })
);
}
[Fact]
public async Task AnnotateTag_should_create_events()
{
var command = new AnnotateAsset { Tags = new HashSet<string>() };
await ExecuteCreateAsync();
@ -141,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
LastEvents
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetTagged { Tags = new HashSet<string>() })
CreateAssetEvent(new AssetAnnotated { Tags = new HashSet<string>() })
);
}

29
tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs

@ -15,29 +15,38 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
public class GuardAssetTests
{
[Fact]
public void CanRename_should_throw_exception_if_name_not_defined()
public void CanAnnotate_should_throw_exception_if_nothing_defined()
{
var command = new RenameAsset();
var command = new AnnotateAsset();
ValidationAssert.Throws(() => GuardAsset.CanRename(command, "asset-name"),
new ValidationError("Name is required.", "FileName"));
ValidationAssert.Throws(() => GuardAsset.CanAnnotate(command, "asset-name", "asset-slug"),
new ValidationError("Either file name, slug or tags must be defined.", "FileName", "Slug", "Tags"));
}
[Fact]
public void CanRename_should_throw_exception_if_name_are_the_same()
public void CanAnnotate_should_throw_exception_if_names_are_the_same()
{
var command = new RenameAsset { FileName = "asset-name" };
var command = new AnnotateAsset { FileName = "asset-name" };
ValidationAssert.Throws(() => GuardAsset.CanRename(command, "asset-name"),
ValidationAssert.Throws(() => GuardAsset.CanAnnotate(command, "asset-name", "asset-slug"),
new ValidationError("Asset has already this name.", "FileName"));
}
[Fact]
public void CanRename_should_not_throw_exception_if_name_are_different()
public void CanAnnotate_should_throw_exception_if_slugs_are_the_same()
{
var command = new RenameAsset { FileName = "new-name" };
var command = new AnnotateAsset { Slug = "asset-slug" };
GuardAsset.CanRename(command, "asset-name");
ValidationAssert.Throws(() => GuardAsset.CanAnnotate(command, "asset-name", "asset-slug"),
new ValidationError("Asset has already this slug.", "Slug"));
}
[Fact]
public void CanAnnotate_should_not_throw_exception_if_names_are_different()
{
var command = new AnnotateAsset { FileName = "new-name", Slug = "new-slug" };
GuardAsset.CanAnnotate(command, "asset-name", "asset-slug");
}
[Fact]

18
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -51,12 +51,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sourceUrl
mimeType
fileName
fileNameSlug
fileSize
fileVersion
isImage
pixelWidth
pixelHeight
slug
}
}";
@ -86,12 +86,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sourceUrl = $"assets/source/{asset.Id}",
mimeType = "image/png",
fileName = "MyFile.png",
fileNameSlug = "myfile.png",
fileSize = 1024,
fileVersion = 123,
isImage = true,
pixelWidth = 800,
pixelHeight = 600
pixelHeight = 600,
slug = "myfile.png"
}
}
}
@ -119,12 +119,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sourceUrl
mimeType
fileName
fileNameSlug
fileSize
fileVersion
isImage
pixelWidth
pixelHeight
slug
}
}
}";
@ -158,12 +158,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sourceUrl = $"assets/source/{asset.Id}",
mimeType = "image/png",
fileName = "MyFile.png",
fileNameSlug = "myfile.png",
fileSize = 1024,
fileVersion = 123,
isImage = true,
pixelWidth = 800,
pixelHeight = 600
pixelHeight = 600,
slug = "myfile.png"
}
}
}
@ -193,12 +193,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sourceUrl
mimeType
fileName
fileNameSlug
fileSize
fileVersion
isImage
pixelWidth
pixelHeight
slug
}}
}}";
@ -224,12 +224,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sourceUrl = $"assets/source/{asset.Id}",
mimeType = "image/png",
fileName = "MyFile.png",
fileNameSlug = "myfile.png",
fileSize = 1024,
fileVersion = 123,
isImage = true,
pixelWidth = 800,
pixelHeight = 600
pixelHeight = 600,
slug = "myfile.png"
}
}
};

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -170,7 +170,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
LastModified = now,
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"),
FileName = "MyFile.png",
FileNameSlug = "myfile.png",
Slug = "myfile.png",
FileSize = 1024,
FileVersion = 123,
MimeType = "image/png",

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs

@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.TestData
public string FileName { get; set; }
public string FileNameSlug { get; set; }
public string Slug { get; set; }
public long FileSize { get; set; }

9
tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs

@ -355,6 +355,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i);
}
[Fact]
public void Should_parse_filter_with_full_text_numbers()
{
var i = Q("$search=\"33k\"");
var o = C("FullText: '33k'");
Assert.Equal(o, i);
}
[Fact]
public void Should_parse_filter_with_full_text()
{

2
tools/Migrate_01/Migrations/CreateAssetSlugs.cs

@ -27,7 +27,7 @@ namespace Migrate_01.Migrations
{
return stateForAssets.ReadAllAsync(async (state, version) =>
{
state.FileNameSlug = state.FileName.ToAssetSlug();
state.Slug = state.FileName.ToAssetSlug();
await stateForAssets.WriteAsync(state.Id, state, version, version);
});

13
src/Squidex.Domain.Apps.Events/Assets/AssetRenamed.cs → tools/Migrate_01/OldEvents/AssetRenamed.cs

@ -5,13 +5,22 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Assets
namespace Migrate_01.OldEvents
{
[EventType(nameof(AssetRenamed))]
public sealed class AssetRenamed : AssetEvent
[Obsolete]
public sealed class AssetRenamed : AssetEvent, IMigrated<IEvent>
{
public string FileName { get; set; }
public IEvent Migrate()
{
return new AssetAnnotated { FileName = FileName };
}
}
}

27
tools/Migrate_01/OldEvents/AssetTagged.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Migrate_01.OldEvents
{
[EventType(nameof(AssetTagged))]
[Obsolete]
public sealed class AssetTagged : AssetEvent, IMigrated<IEvent>
{
public HashSet<string> Tags { get; set; }
public IEvent Migrate()
{
return new AssetAnnotated { Tags = Tags };
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save