Browse Source

Tags

pull/308/head
Sebastian 8 years ago
parent
commit
4a43948b6d
  1. 9
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  2. 9
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  3. 18
      src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs
  4. 4
      src/Squidex.Domain.Apps.Entities/Assets/Commands/TagAsset.cs
  5. 3
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs
  6. 7
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  7. 16
      src/Squidex.Domain.Apps.Entities/IEntityWithTags.cs
  8. 6
      src/Squidex.Domain.Apps.Entities/Tags/GrainTagService.cs
  9. 6
      src/Squidex.Domain.Apps.Entities/Tags/ITagGrain.cs
  10. 6
      src/Squidex.Domain.Apps.Entities/Tags/ITagService.cs
  11. 14
      src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs
  12. 3
      src/Squidex.Domain.Apps.Events/Assets/AssetTagged.cs
  13. 3
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  14. 3
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetUpdateDto.cs
  15. 8
      src/Squidex/app/framework/angular/forms/tag-editor.component.html
  16. 12
      src/Squidex/app/framework/angular/forms/tag-editor.component.scss
  17. 4
      src/Squidex/app/framework/angular/forms/tag-editor.component.ts
  18. 2
      src/Squidex/app/shared/components/asset.component.html
  19. 1
      src/Squidex/app/shared/components/assets-list.component.html
  20. 4
      src/Squidex/app/shared/components/assets-list.component.ts
  21. 4
      src/Squidex/app/shared/state/assets.state.spec.ts
  22. 38
      src/Squidex/app/shared/state/assets.state.ts
  23. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/OData/ODataQueryTests.cs
  24. 5
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs

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

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Core.ValidateContent;
@ -38,10 +39,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement]
public string FileName { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string[] Tags { get; set; }
[BsonRequired]
[BsonElement]
public long FileSize { get; set; }
@ -74,6 +71,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement]
public RefToken LastModifiedBy { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public HashSet<string> Tags { get; set; }
[BsonElement]
public bool IsDeleted { get; set; }

9
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.UriParser;
using MongoDB.Bson;
@ -93,12 +94,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
if (string.Equals(field, nameof(MongoAssetEntity.Tags), StringComparison.OrdinalIgnoreCase))
{
var tags = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, new[] { value.ToString() })).Result;
var tagIds = new HashSet<string>(new[] { value.ToString() });
var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, tagIds)).Result;
if (tags.Length == 1)
{
return tags[0] ?? value;
}
return tagNames?.FirstOrDefault() ?? value;
}
return value;

18
src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs

@ -75,25 +75,29 @@ namespace Squidex.Domain.Apps.Entities.Assets
private async Task DenormalizeTagsAsync(Guid appId, IEnumerable<IAssetEntity> assets)
{
var tags = assets.Where(x => x.Tags != null).SelectMany(x => x.Tags).Distinct().ToArray();
var tags = new HashSet<string>(assets.Where(x => x.Tags != null).SelectMany(x => x.Tags).Distinct());
var tagsById = await tagService.DenormalizeTagsAsync(appId, TagGroups.Assets, tags);
foreach (var asset in assets)
{
if (asset.Tags?.Length > 0)
if (asset.Tags?.Count > 0)
{
var tagNames = new List<string>();
var tagNames = asset.Tags.ToList();
foreach (var id in asset.Tags)
asset.Tags.Clear();
foreach (var id in tagNames)
{
if (tagsById.TryGetValue(id, out var name))
{
tagNames.Add(name);
asset.Tags.Add(name);
}
}
asset.Tags = tagNames.ToArray();
}
else
{
asset.Tags?.Clear();
}
}
}

4
src/Squidex.Domain.Apps.Entities/Assets/Commands/TagAsset.cs

@ -5,10 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Assets.Commands
{
public sealed class TagAsset : AssetCommand
{
public string[] Tags { get; set; }
public HashSet<string> Tags { get; set; }
}
}

3
src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs

@ -16,14 +16,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
IEntityWithCreatedBy,
IEntityWithLastModifiedBy,
IEntityWithVersion,
IEntityWithTags,
IAssetInfo
{
NamedId<Guid> AppId { get; }
string MimeType { get; }
string[] Tags { get; set; }
long FileVersion { get; }
}
}

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

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Events;
@ -28,9 +29,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
[JsonProperty]
public string MimeType { get; set; }
[JsonProperty]
public string[] Tags { get; set; }
[JsonProperty]
public long FileVersion { get; set; }
@ -52,6 +50,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
[JsonProperty]
public bool IsDeleted { get; set; }
[JsonProperty]
public HashSet<string> Tags { get; set; }
Guid IAssetInfo.AssetId
{
get { return Id; }

16
src/Squidex.Domain.Apps.Entities/IEntityWithTags.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities
{
public interface IEntityWithTags
{
HashSet<string> Tags { get; }
}
}

6
src/Squidex.Domain.Apps.Entities/Tags/GrainTagService.cs

@ -24,17 +24,17 @@ namespace Squidex.Domain.Apps.Entities.Tags
this.grainFactory = grainFactory;
}
public Task<string[]> NormalizeTagsAsync(Guid appId, string category, string[] names, string[] ids)
public Task<HashSet<string>> NormalizeTagsAsync(Guid appId, string category, HashSet<string> names, HashSet<string> ids)
{
return GetGrain(appId, category).NormalizeTagsAsync(names, ids);
}
public Task<string[]> GetTagIdsAsync(Guid appId, string category, string[] names)
public Task<HashSet<string>> GetTagIdsAsync(Guid appId, string category, HashSet<string> names)
{
return GetGrain(appId, category).GetTagIdsAsync(names);
}
public Task<Dictionary<string, string>> DenormalizeTagsAsync(Guid appId, string category, string[] ids)
public Task<Dictionary<string, string>> DenormalizeTagsAsync(Guid appId, string category, HashSet<string> ids)
{
return GetGrain(appId, category).DenormalizeTagsAsync(ids);
}

6
src/Squidex.Domain.Apps.Entities/Tags/ITagGrain.cs

@ -13,11 +13,11 @@ namespace Squidex.Domain.Apps.Entities.Tags
{
public interface ITagGrain : IGrainWithStringKey
{
Task<string[]> NormalizeTagsAsync(string[] names, string[] ids);
Task<HashSet<string>> NormalizeTagsAsync(HashSet<string> names, HashSet<string> ids);
Task<string[]> GetTagIdsAsync(string[] names);
Task<HashSet<string>> GetTagIdsAsync(HashSet<string> names);
Task<Dictionary<string, string>> DenormalizeTagsAsync(string[] ids);
Task<Dictionary<string, string>> DenormalizeTagsAsync(HashSet<string> ids);
Task<Dictionary<string, int>> GetTagsAsync();
}

6
src/Squidex.Domain.Apps.Entities/Tags/ITagService.cs

@ -13,11 +13,11 @@ namespace Squidex.Domain.Apps.Entities.Tags
{
public interface ITagService
{
Task<string[]> NormalizeTagsAsync(Guid appId, string category, string[] names, string[] ids);
Task<HashSet<string>> NormalizeTagsAsync(Guid appId, string category, HashSet<string> names, HashSet<string> ids);
Task<string[]> GetTagIdsAsync(Guid appId, string category, string[] names);
Task<HashSet<string>> GetTagIdsAsync(Guid appId, string category, HashSet<string> names);
Task<Dictionary<string, string>> DenormalizeTagsAsync(Guid appId, string category, string[] ids);
Task<Dictionary<string, string>> DenormalizeTagsAsync(Guid appId, string category, HashSet<string> ids);
Task<Dictionary<string, int>> GetTagsAsync(Guid appId, string category);
}

14
src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs

@ -51,9 +51,9 @@ namespace Squidex.Domain.Apps.Entities.Tags
return persistence.ReadAsync();
}
public async Task<string[]> NormalizeTagsAsync(string[] names, string[] ids)
public async Task<HashSet<string>> NormalizeTagsAsync(HashSet<string> names, HashSet<string> ids)
{
var result = new List<string>();
var result = new HashSet<string>();
if (names != null)
{
@ -108,22 +108,22 @@ namespace Squidex.Domain.Apps.Entities.Tags
await persistence.WriteSnapshotAsync(state);
return result.ToArray();
return result;
}
public Task<string[]> GetTagIdsAsync(string[] names)
public Task<HashSet<string>> GetTagIdsAsync(HashSet<string> names)
{
var result = new List<string>();
var result = new HashSet<string>();
foreach (var name in names)
{
result.Add(state.Tags.FirstOrDefault(x => x.Value.Name == name).Key);
}
return Task.FromResult(result.ToArray());
return Task.FromResult(result);
}
public Task<Dictionary<string, string>> DenormalizeTagsAsync(string[] ids)
public Task<Dictionary<string, string>> DenormalizeTagsAsync(HashSet<string> ids)
{
var result = new Dictionary<string, string>();

3
src/Squidex.Domain.Apps.Events/Assets/AssetTagged.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Assets
@ -12,6 +13,6 @@ namespace Squidex.Domain.Apps.Events.Assets
[EventType(nameof(AssetTagged))]
public sealed class AssetTagged : AssetEvent
{
public string[] Tags { get; set; }
public HashSet<string> Tags { get; set; }
}
}

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

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Domain.Apps.Entities.Assets;
@ -42,7 +43,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// <summary>
/// The asset tags.
/// </summary>
public string[] Tags { get; set; }
public HashSet<string> Tags { get; set; }
/// <summary>
/// The size of the file in bytes.

3
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetUpdateDto.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Entities.Assets.Commands;
@ -23,7 +24,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// The new asset tags.
/// </summary>
[Required]
public string[] Tags { get; set; }
public HashSet<string> Tags { get; set; }
public AssetCommand ToCommand(Guid id)
{

8
src/Squidex/app/framework/angular/forms/tag-editor.component.html

@ -1,10 +1,6 @@
<div class="form-control {{class}}" (click)="input.focus()" [class.focus]="hasFocus">
<span class="items">
<span class="item-container" *ngFor="let item of items; let i = index" [class.disabled]="addInput.disabled">
<span class="item">
{{item}} <i class="icon-close" (click)="remove(i)"></i>
</span>
</span>
<span class="item" *ngFor="let item of items; let i = index" [class.disabled]="addInput.disabled">
{{item}} <i class="icon-close" (click)="remove(i)"></i>
</span>
<input type="text" class="blank" [attr.name]="inputName" (keydown)="onKeyDown($event)" #input

12
src/Squidex/app/framework/angular/forms/tag-editor.component.scss

@ -32,10 +32,6 @@
font-size: .6rem;
}
.items {
margin-left: -2px;
}
.item {
& {
@include border-radius(10px);
@ -50,11 +46,19 @@
font-size: .8rem;
font-weight: normal;
line-height: 20px;
margin: 2px 2px 2px 0;
vertical-align: middle;
}
&,
&-container {
display: inline-block;
}
&-container {
height: 24px;
padding: 2px;
padding-left: 0;
}
&.disabled {

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

@ -132,9 +132,9 @@ export class TagEditorComponent implements ControlValueAccessor {
}
private resetForm() {
this.adjustSize();
this.addInput.reset();
this.adjustSize();
}
public markTouched() {

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

@ -54,7 +54,7 @@
</div>
</div>
<div>
<sqx-tag-editor [formControl]="tagInput" class="blank"></sqx-tag-editor>
<sqx-tag-editor [useDefaultValue]="false" [formControl]="tagInput" 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}}

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

@ -25,6 +25,7 @@
[isDisabled]="isDisabled"
[isSelectable]="selectedIds"
[isSelected]="isSelected(asset)"
(updated)="update($event)"
(selected)="select($event)"
(deleting)="delete($event)">
</sqx-asset>

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

@ -58,6 +58,10 @@ export class AssetsListComponent {
this.state.goPrev().pipe(onErrorResumeNext()).subscribe();
}
public update(asset: AssetDto) {
this.state.update(asset);
}
public trackByAsset(index: number, asset: AssetDto) {
return asset.id;
}

4
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, [], 'url1', version),
new AssetDto('id2', creator, creator, creation, creation, 'name2', 'type2', 2, 2, 'mime2', false, null, null, [], 'url2', version)
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)
];
let dialogs: IMock<DialogService>;

38
src/Squidex/app/shared/state/assets.state.ts

@ -105,7 +105,17 @@ export class AssetsState extends State<Snapshot> {
const assets = s.assets.filter(x => x.id !== asset.id);
const assetsPager = s.assetsPager.decrementCount();
return { ...s, assets, assetsPager };
const tags = { ...s.tags };
for (let tag of asset.tags) {
if (tags[tag] === 1) {
delete tags[tag];
} else {
tags[tag]--;
}
}
return { ...s, assets, assetsPager, tags };
});
}),
notify(this.dialogs));
@ -113,9 +123,33 @@ export class AssetsState extends State<Snapshot> {
public update(asset: AssetDto) {
this.next(s => {
const previous = s.assets.find(x => x.id === asset.id);
const tags = { ...s.tags };
if (previous) {
for (let tag of previous.tags) {
if (tags[tag] === 1) {
delete tags[tag];
} else {
tags[tag]--;
}
}
}
if (asset) {
for (let tag of asset.tags) {
if (tags[tag]) {
tags[tag]++;
} else {
tags[tag] = 1;
}
}
}
const assets = s.assets.replaceBy('id', asset);
return { ...s, assets };
return { ...s, assets, tags };
});
}

5
tests/Squidex.Domain.Apps.Entities.Tests/Assets/OData/ODataQueryTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using FakeItEasy;
using Microsoft.OData.Edm;
using MongoDB.Bson.Serialization;
@ -36,8 +37,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.OData
public ODataQueryTests()
{
A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<string[]>.That.Contains("tag1")))
.Returns(new[] { "normalized1" });
A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.Contains("tag1")))
.Returns(new HashSet<string>(new[] { "normalized1" }));
valueConverter = FindExtensions.CreateValueConverter(appId, tagService);
}

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

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using NodaTime;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
@ -28,14 +29,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.TestData
public RefToken LastModifiedBy { get; set; }
public HashSet<string> Tags { get; set; }
public long Version { get; set; }
public string MimeType { get; set; }
public string FileName { get; set; }
public string[] Tags { get; set; }
public long FileSize { get; set; }
public long FileVersion { get; set; }

Loading…
Cancel
Save