Browse Source

Languages and rules.

pull/363/head
Sebastian 7 years ago
parent
commit
4a7f976580
  1. 14
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs
  2. 2
      src/Squidex.Web/PermissionExtensions.cs
  3. 4
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  4. 42
      src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
  5. 44
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs
  6. 54
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs
  7. 14
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs
  8. 10
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs
  9. 3
      src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs
  10. 8
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  11. 2
      src/Squidex/app/declarations.d.ts
  12. 2
      src/Squidex/app/features/administration/pages/users/user-page.component.html
  13. 8
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  14. 4
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  15. 10
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  16. 8
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html
  17. 2
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
  18. 15
      src/Squidex/app/features/settings/pages/languages/language.component.html
  19. 16
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  20. 2
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  21. 4
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  22. 12
      src/Squidex/app/framework/angular/sorted.directive.ts
  23. 121
      src/Squidex/app/shared/services/app-languages.service.spec.ts
  24. 88
      src/Squidex/app/shared/services/app-languages.service.ts
  25. 4
      src/Squidex/app/shared/services/apps.service.ts
  26. 4
      src/Squidex/app/shared/services/assets.service.ts
  27. 3
      src/Squidex/app/shared/services/backups.service.ts
  28. 11
      src/Squidex/app/shared/services/contributors.service.spec.ts
  29. 30
      src/Squidex/app/shared/services/contributors.service.ts
  30. 3
      src/Squidex/app/shared/services/rules.service.ts
  31. 59
      src/Squidex/app/shared/state/languages.state.spec.ts
  32. 66
      src/Squidex/app/shared/state/languages.state.ts

14
src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId);
break;
case AssignContributor assignContributor:
await Index(GetUserId(context)).AddAppAsync(assignContributor.AppId);
await Index(GetUserId(assignContributor)).AddAppAsync(assignContributor.AppId);
break;
case RemoveContributor removeContributor:
await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId);
@ -57,19 +57,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
await next();
}
private static string GetUserId(RemoveContributor removeContributor)
private static string GetUserId(CreateApp createApp)
{
return removeContributor.ContributorId;
return createApp.Actor.Identifier;
}
private static string GetUserId(CreateApp createApp)
private static string GetUserId(AssignContributor assignContributor)
{
return createApp.Actor.Identifier;
return assignContributor.ContributorId;
}
private static string GetUserId(CommandContext context)
private static string GetUserId(RemoveContributor removeContributor)
{
return context.Result<EntityCreatedResult<string>>().IdOrValue;
return removeContributor.ContributorId;
}
private IAppsByUserIndex Index(string id)

2
src/Squidex.Web/PermissionExtensions.cs

@ -39,7 +39,7 @@ namespace Squidex.Web
public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null)
{
return httpContext.Permissions().Includes(permission) || permission?.Includes(permission) == true;
return httpContext.Permissions().Includes(permission) || permissions?.Includes(permission) == true;
}
public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*", PermissionSet permissions = null)

4
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -61,7 +61,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="app">The name of the app.</param>
/// <param name="request">Contributor object that needs to be added to the app.</param>
/// <returns>
/// 200 => User assigned to app.
/// 201 => User assigned to app.
/// 400 => User is already assigned to the app or not found.
/// 404 => App not found.
/// </returns>
@ -87,7 +87,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
response = ContributorsDto.FromApp(invited.App, appPlansProvider, this, true);
}
return Ok(response);
return CreatedAtAction(nameof(GetContributors), new { app }, response);
}
/// <summary>

42
src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
@ -39,12 +40,12 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns>
[HttpGet]
[Route("apps/{app}/languages/")]
[ProducesResponseType(typeof(AppLanguageDto[]), 200)]
[ProducesResponseType(typeof(AppLanguagesDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public IActionResult GetLanguages(string app)
{
var response = AppLanguageDto.FromApp(App);
var response = AppLanguagesDto.FromApp(App, this);
Response.Headers[HeaderNames.ETag] = App.Version.ToString();
@ -63,7 +64,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns>
[HttpPost]
[Route("apps/{app}/languages/")]
[ProducesResponseType(typeof(AppLanguageDto), 201)]
[ProducesResponseType(typeof(AppLanguagesDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppLanguagesCreate)]
[ApiCosts(1)]
@ -71,9 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
{
var command = request.ToCommand();
await CommandBus.PublishAsync(command);
var response = AppLanguageDto.FromCommand(command);
var response = await InvokeCommandAsync(app, command);
return CreatedAtAction(nameof(GetLanguages), new { app }, response);
}
@ -85,19 +84,23 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="language">The language to update.</param>
/// <param name="request">The language object.</param>
/// <returns>
/// 204 => Language updated.
/// 200 => Language updated.
/// 400 => Language request not valid.
/// 404 => Language or app not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/languages/{language}/")]
[ProducesResponseType(typeof(AppLanguagesDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppLanguagesUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> Update(string app, string language, [FromBody] UpdateLanguageDto request)
public async Task<IActionResult> PutLanguage(string app, string language, [FromBody] UpdateLanguageDto request)
{
await CommandBus.PublishAsync(request.ToCommand(ParseLanguage(language)));
var command = request.ToCommand(ParseLanguage(language));
var response = await InvokeCommandAsync(app, command);
return NoContent();
return Ok(response);
}
/// <summary>
@ -106,18 +109,31 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="app">The name of the app.</param>
/// <param name="language">The language to delete from the app.</param>
/// <returns>
/// 204 => Language deleted.
/// 200 => Language deleted.
/// 404 => Language or app not found.
/// </returns>
[HttpDelete]
[Route("apps/{app}/languages/{language}/")]
[ProducesResponseType(typeof(AppLanguagesDto), 200)]
[ApiPermission(Permissions.AppLanguagesDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteLanguage(string app, string language)
{
await CommandBus.PublishAsync(new RemoveLanguage { Language = ParseLanguage(language) });
var command = new RemoveLanguage { Language = ParseLanguage(language) };
var response = await InvokeCommandAsync(app, command);
return Ok(response);
}
private async Task<AppLanguagesDto> InvokeCommandAsync(string app, ICommand command)
{
var context = await CommandBus.PublishAsync(command);
var result = context.Result<IAppEntity>();
var response = AppLanguagesDto.FromApp(result, this);
return NoContent();
return response;
}
private static Language ParseLanguage(string language)

44
src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs

@ -5,18 +5,18 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class AppLanguageDto
public sealed class AppLanguageDto : Resource
{
/// <summary>
/// The iso code of the language.
@ -46,25 +46,37 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// </summary>
public bool IsOptional { get; set; }
public static AppLanguageDto FromCommand(AddLanguage command)
public static AppLanguageDto FromLanguage(LanguageConfig language, IAppEntity app, ApiController controller)
{
return SimpleMapper.Map(command.Language, new AppLanguageDto { Fallback = Array.Empty<Language>() });
}
var result = SimpleMapper.Map(language.Language,
new AppLanguageDto
{
IsMaster = language == app.LanguagesConfig.Master,
IsOptional = language.IsOptional,
Fallback = language.LanguageFallbacks.ToArray()
});
public static AppLanguageDto[] FromApp(IAppEntity app)
{
return app.LanguagesConfig.OfType<LanguageConfig>().Select(x => FromLanguage(x, app)).OrderByDescending(x => x.IsMaster).ThenBy(x => x.Iso2Code).ToArray();
return CreateLinks(result, controller, app);
}
private static AppLanguageDto FromLanguage(LanguageConfig x, IAppEntity app)
private static AppLanguageDto CreateLinks(AppLanguageDto result, ApiController controller, IAppEntity app)
{
return SimpleMapper.Map(x.Language,
new AppLanguageDto
var values = new { app = app.Name, language = result.Iso2Code };
if (!result.IsMaster)
{
if (controller.HasPermission(Permissions.AppLanguagesUpdate, app.Name))
{
IsMaster = x == app.LanguagesConfig.Master,
IsOptional = x.IsOptional,
Fallback = x.LanguageFallbacks.ToArray()
});
result.AddPutLink("update", controller.Url<AppLanguagesController>(x => nameof(x.PutLanguage), values));
}
if (controller.HasPermission(Permissions.AppLanguagesDelete, app.Name) && app.LanguagesConfig.Count > 1)
{
result.AddDeleteLink("delete", controller.Url<AppLanguagesController>(x => nameof(x.DeleteLanguage), values));
}
}
return result;
}
}
}

54
src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs

@ -0,0 +1,54 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class AppLanguagesDto : Resource
{
/// <summary>
/// The languages.
/// </summary>
[Required]
public AppLanguageDto[] Items { get; set; }
public static AppLanguagesDto FromApp(IAppEntity app, ApiController controller)
{
var result = new AppLanguagesDto
{
Items = app.LanguagesConfig.OfType<LanguageConfig>()
.Select(x => AppLanguageDto.FromLanguage(x, app, controller))
.OrderByDescending(x => x.IsMaster)
.ThenBy(x => x.Iso2Code)
.ToArray()
};
return CreateLinks(result, controller, app.Name);
}
private static AppLanguagesDto CreateLinks(AppLanguagesDto result, ApiController controller, string app)
{
var values = new { app };
result.AddSelfLink(controller.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values));
if (controller.HasPermission(Permissions.AppLanguagesCreate, app))
{
result.AddPostLink("create", controller.Url<AppLanguagesController>(x => nameof(x.PostLanguage), values));
}
return result;
}
}
}

14
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs

@ -7,6 +7,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
@ -33,6 +34,19 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
private static ContributorDto CreateLinks(ContributorDto result, ApiController controller, string app)
{
if (!controller.IsUser(result.ContributorId))
{
if (controller.HasPermission(Permissions.AppContributorsAssign, app))
{
result.AddPostLink("update", controller.Url<AppContributorsController>(x => nameof(x.PostContributor), new { app }));
}
if (controller.HasPermission(Permissions.AppContributorsRevoke, app))
{
result.AddDeleteLink("delete", controller.Url<AppContributorsController>(x => nameof(x.DeleteContributor), new { app, id = result.ContributorId }));
}
}
return result;
}
}

10
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs

@ -10,6 +10,7 @@ using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
@ -57,6 +58,15 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
private static ContributorsDto CreateLinks(ContributorsDto result, ApiController controller, string app)
{
var values = new { app };
result.AddSelfLink(controller.Url<AppContributorsController>(x => nameof(x.GetContributors), values));
if (controller.HasPermission(Permissions.AppContributorsAssign, app))
{
result.AddPostLink("create", controller.Url<AppContributorsController>(x => nameof(x.PostContributor), values));
}
return result;
}
}

3
src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
@ -26,7 +27,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// <summary>
/// Optional fallback languages.
/// </summary>
public Language[] Fallback { get; set; }
public List<Language> Fallback { get; set; }
public UpdateLanguage ToCommand(Language language)
{

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

@ -179,7 +179,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
var command = new CreateAsset { File = assetFile };
var response = await InvokeCommand(app, command);
var response = await InvokeCommandAsync(app, command);
return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response);
}
@ -210,7 +210,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
var command = new UpdateAsset { File = assetFile, AssetId = id };
var response = await InvokeCommand(app, command);
var response = await InvokeCommandAsync(app, command);
return Ok(response);
}
@ -237,7 +237,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
{
var command = request.ToCommand(id);
var response = await InvokeCommand(app, command);
var response = await InvokeCommandAsync(app, command);
return Ok(response);
}
@ -262,7 +262,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
return NoContent();
}
private async Task<AssetDto> InvokeCommand(string app, AssetCommand command)
private async Task<AssetDto> InvokeCommandAsync(string app, ICommand command)
{
var context = await CommandBus.PublishAsync(command);

2
src/Squidex/app/declarations.d.ts

@ -12,6 +12,8 @@ declare module 'progressbar.js';
declare module 'sortablejs' {
export class Ref {
public destroy(): any;
public option(property: string, value: any): any;
}
export function create(element: any, options: any): Ref;

2
src/Squidex/app/features/administration/pages/users/user-page.component.html

@ -16,7 +16,7 @@
<ng-container menu>
<ng-container *ngIf="usersState.selectedUser | async; let user; else noUserMenu">
<ng-container *ngIf="canUpdate">
<ng-container *ngIf="isEditable">
<button type="submit" class="btn btn-primary" title="CTRL + S">
Save
</button>

8
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -24,7 +24,7 @@ import {
templateUrl: './user-page.component.html'
})
export class UserPageComponent extends ResourceOwner implements OnInit {
public canUpdate = false;
public isEditable = false;
public user?: UserDto;
public userForm = new UserForm(this.formBuilder);
@ -47,9 +47,9 @@ export class UserPageComponent extends ResourceOwner implements OnInit {
if (selectedUser) {
this.userForm.load(selectedUser);
this.canUpdate = hasLink(this.user, 'update');
this.isEditable = hasLink(this.user, 'update');
if (!this.canUpdate) {
if (!this.isEditable) {
this.userForm.form.disable();
}
}
@ -57,7 +57,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit {
}
public save() {
if (!this.canUpdate) {
if (!this.isEditable) {
return;
}

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

@ -106,12 +106,12 @@
<ng-container *ngIf="mode !== 'Wizard' && step === 2">
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveTrigger()" *ngIf="canUpdate">Save</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveTrigger()" *ngIf="isEditable">Save</button>
</ng-container>
<ng-container *ngIf="step === 4">
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveAction()" *ngIf="canUpdate">Save</button>
<button type="submit" class="float-right btn btn-primary" (click)="saveAction()" *ngIf="isEditable">Save</button>
</ng-container>
</div>
</ng-container>

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

@ -36,7 +36,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
public triggerType: string;
public trigger: any = {};
public canUpdate: boolean;
public isEditable: boolean;
public step = 1;
@ -64,7 +64,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
}
public ngOnInit() {
this.canUpdate = !this.rule || hasLink(this.rule, 'update');
this.isEditable = !this.rule || hasLink(this.rule, 'update');
if (this.mode === MODE_EDIT_ACTION) {
this.step = 4;
@ -80,7 +80,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
}
public ngAfterViewInit() {
if (!this.canUpdate) {
if (!this.isEditable) {
this.actionForm.form.disable();
this.triggerForm.form.disable();
@ -145,7 +145,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
}
private updateTrigger() {
if (!this.canUpdate) {
if (!this.isEditable) {
return;
}
@ -160,7 +160,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
}
private updateAction() {
if (!this.canUpdate) {
if (!this.isEditable) {
return;
}

8
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html

@ -22,13 +22,13 @@
</td>
<td class="text-center">
<input type="text" class="form-control code" placeholder="Optional condition as javascript expression"
[disabled]="!canUpdate"
[disabled]="!isEditable"
[ngModelOptions]="{ updateOn: 'blur' }"
[ngModel]="triggerSchema.condition"
(ngModelChange)="updateCondition(triggerSchema.schema, $event)" />
</td>
<td class="text-center">
<button type="button" class="btn btn-text-secondary" (click)="removeSchema(triggerSchema)" [disabled]="!canUpdate">
<button type="button" class="btn btn-text-secondary" (click)="removeSchema(triggerSchema)" [disabled]="!isEditable">
<i class="icon-close"></i>
</button>
</td>
@ -38,12 +38,12 @@
<div class="section" *ngIf="schemasToAdd.length > 0">
<form class="form-inline" (ngSubmit)="addSchema()">
<div class="form-group mr-1">
<select class="form-control schemas-control" [disabled]="!canUpdate" [(ngModel)]="schemaToAdd" name="schema">
<select class="form-control schemas-control" [disabled]="!isEditable" [(ngModel)]="schemaToAdd" name="schema">
<option *ngFor="let schema of schemasToAdd; trackBy: trackBySchema" [ngValue]="schema">{{schema.displayName}}</option>
</select>
</div>
<button type="submit" class="btn btn-success" [disabled]="!canUpdate">Add Schema</button>
<button type="submit" class="btn btn-success" [disabled]="!isEditable">Add Schema</button>
</form>
</div>

2
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts

@ -30,7 +30,7 @@ export class ContentChangedTriggerComponent implements OnInit {
public schemas: ImmutableArray<SchemaDto>;
@Input()
public canUpdate: boolean;
public isEditable: boolean;
@Input()
public trigger: any;

15
src/Squidex/app/features/settings/pages/languages/language.component.html

@ -13,7 +13,8 @@
<i class="icon-settings"></i>
</button>
<button type="button" class="btn btn-text-danger" [class.invisible]="language.isMaster"
<button type="button" class="btn btn-text-danger"
[disabled]="language | sqxHasNoLink:'delete'"
(sqxConfirmClick)="remove()"
confirmTitle="Remove language"
confirmText="Do you really want to remove the language?">
@ -29,25 +30,25 @@
<div class="table-items-row-details-tabs clearfix">
<div class="float-right">
<button type="reset" class="btn btn-text" (click)="toggleEditing()">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
<button type="submit" class="btn btn-primary" *ngIf="isEditable">Save</button>
</div>
</div>
<div class="table-items-row-details-tab">
<div class="form-group row">
<div class="form-group row" *ngIf="isEditable || fallbackLanguages.length > 0">
<label class="col-3 col-form-label fallback-label">Fallback</label>
<div class="col-9">
<div class="fallback-languages" [sqxSortModel]="fallbackLanguages.mutableValues" *ngIf="fallbackLanguages.length > 0">
<div class="fallback-languages" [sqxSortModel]="fallbackLanguages.mutableValues" [disabled]="!isEditable" *ngIf="fallbackLanguages.length > 0">
<div class="fallback-language" *ngFor="let language of fallbackLanguages">
<div class="row no-gutters">
<div class="col-auto">
<div class="col-auto" *ngIf="isEditable">
<i class="icon-drag2 drag-handle mr-1"></i>
</div>
<div class="col">
{{language.englishName}}
</div>
<div class="col-auto">
<div class="col-auto" *ngIf="isEditable">
<button type="button" class="btn btn-text-secondary btn-sm" (click)="removeFallbackLanguage(language)">
<i class="icon-close"></i>
</button>
@ -56,7 +57,7 @@
</div>
</div>
<form class="form fallback-form" (ngSubmit)="addFallbackLanguage()" *ngIf="otherLanguage">
<form class="form fallback-form" (ngSubmit)="addFallbackLanguage()" *ngIf="otherLanguage && isEditable">
<div class="row no-gutters">
<div class="col">
<select class="form-control fallback-select" [(ngModel)]="otherLanguage" name="otherLanguage">

16
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -12,6 +12,7 @@ import {
AppLanguageDto,
EditLanguageForm,
fadeAnimation,
hasAnyLink,
ImmutableArray,
LanguagesState
} from '@app/shared';
@ -37,6 +38,7 @@ export class LanguageComponent implements OnChanges {
public otherLanguage: AppLanguageDto;
public isEditing = false;
public isEditable = false;
public editForm = new EditLanguageForm(this.formBuilder);
@ -59,10 +61,14 @@ export class LanguageComponent implements OnChanges {
}
public save() {
if (!this.isEditable) {
return;
}
const value = this.editForm.submit();
if (value) {
const request = { ...value, fallbackLanguages: this.fallbackLanguages.map(x => x.iso2Code).values };
const request = { ...value, fallback: this.fallbackLanguages.map(x => x.iso2Code).values };
this.languagesState.update(this.language, request)
.subscribe(() => {
@ -90,9 +96,15 @@ export class LanguageComponent implements OnChanges {
}
private resetForm() {
this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.isEditable = hasAnyLink(this.language, 'update');
this.editForm.load(this.language);
if (!this.isEditable) {
this.editForm.form.disable();
}
this.otherLanguage = this.fallbackLanguagesNew.at(0);
}
public trackByLanguage(index: number, language: AppLanguageDto) {

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

@ -22,7 +22,7 @@
</sqx-language>
<ng-container *ngIf="languagesState.newLanguages | async; let newLanguages">
<div class="table-items-footer">
<div class="table-items-footer" *ngIf="languagesState.links | async | sqxHasLink:'create'">
<form [formGroup]="addLanguageForm.form" (ngSubmit)="addLanguage()">
<div class="row no-gutters">
<div class="col">

4
src/Squidex/app/features/settings/pages/languages/languages-page.component.ts

@ -61,8 +61,8 @@ export class LanguagesPageComponent extends ResourceOwner implements OnInit {
}
}
public trackByLanguage(language: { language: AppLanguageDto }) {
return language.language;
public trackByLanguage(index: number, language: { language: AppLanguageDto }) {
return language.language.iso2Code;
}
}

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

@ -14,6 +14,7 @@ import * as Sortable from 'sortablejs';
})
export class SortedDirective implements OnDestroy, OnInit {
private sortable: Sortable.Ref;
private isDisabled: boolean;
@Input()
public dragHandle = '.drag-handle';
@ -24,6 +25,15 @@ export class SortedDirective implements OnDestroy, OnInit {
@Output('sqxSort')
public sort = new EventEmitter<any[]>();
@Input('disabled')
public setDisabled(value: boolean) {
this.isDisabled = value;
if (this.sortable) {
this.sortable.option('disabled', value);
}
}
constructor(
private readonly elementRef: ElementRef
) {
@ -53,6 +63,8 @@ export class SortedDirective implements OnDestroy, OnInit {
}
},
isDisabled: this.isDisabled,
handle: this.dragHandle
});
}

121
src/Squidex/app/shared/services/app-languages.service.spec.ts

@ -13,8 +13,11 @@ import {
ApiUrlConfig,
AppLanguageDto,
AppLanguagesDto,
AppLanguagesPayload,
AppLanguagesService,
Version
Resource,
Version,
withLinks
} from '@app/shared/internal';
describe('AppLanguagesService', () => {
@ -51,33 +54,13 @@ describe('AppLanguagesService', () => {
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([
{
iso2Code: 'en',
englishName: 'English',
isMaster: true,
isOptional: true,
fallback: ['de', 'en']
},
{
iso2Code: 'it',
isMaster: false,
isOptional: false,
englishName: 'Italian'
}
], {
req.flush(languagesResponse('en', 'de', 'it'), {
headers: {
etag: '2'
}
});
expect(languages!).toEqual({
payload: [
new AppLanguageDto('en', 'English', true, true, ['de', 'en']),
new AppLanguageDto('it', 'Italian', false, false, [])
],
version: new Version('2')
});
expect(languages!).toEqual({payload: createLanguages('en', 'de', 'it'), version: new Version('2') });
}));
it('should make post request to add language',
@ -85,10 +68,10 @@ describe('AppLanguagesService', () => {
const dto = { language: 'de' };
let language: AppLanguageDto;
let languages: AppLanguagesDto;
appLanguagesService.postLanguage('my-app', dto, version).subscribe(result => {
language = result.payload;
languages = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages');
@ -96,14 +79,13 @@ describe('AppLanguagesService', () => {
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({
iso2Code: 'de',
isMaster: false,
isOptional: false,
englishName: 'German'
req.flush(languagesResponse('en', 'de', 'it'), {
headers: {
etag: '2'
}
});
expect(language!).toEqual(new AppLanguageDto('de', 'German', false, false, []));
expect(languages!).toEqual({payload: createLanguages('en', 'de', 'it'), version: new Version('2') });
}));
it('should make put request to make master language',
@ -111,26 +93,93 @@ describe('AppLanguagesService', () => {
const dto = { isMaster: true };
appLanguagesService.putLanguage('my-app', 'de', dto, version).subscribe();
const resource: Resource = {
_links: {
update: { method: 'PUT', href: 'api/apps/my-app/languages/de' }
}
};
let languages: AppLanguagesDto;
appLanguagesService.putLanguage('my-app', resource, dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages/de');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({});
req.flush(languagesResponse('en', 'de', 'it'), {
headers: {
etag: '2'
}
});
expect(languages!).toEqual({payload: createLanguages('en', 'de', 'it'), version: new Version('2') });
}));
it('should make delete request to remove language',
inject([AppLanguagesService, HttpTestingController], (appLanguagesService: AppLanguagesService, httpMock: HttpTestingController) => {
appLanguagesService.deleteLanguage('my-app', 'de', version).subscribe();
const resource: Resource = {
_links: {
update: { method: 'PUT', href: 'api/apps/my-app/languages/de' }
}
};
let languages: AppLanguagesDto;
appLanguagesService.deleteLanguage('my-app', resource, version).subscribe(result => {
languages = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages/de');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({});
req.flush(languagesResponse('en', 'de', 'it'), {
headers: {
etag: '2'
}
});
expect(languages!).toEqual({payload: createLanguages('en', 'de', 'it'), version: new Version('2') });
}));
});
function languagesResponse(...codes: string[]) {
return {
items: codes.map((code, i) => ({
code: code,
englishName: code,
isMaster: i === 0,
isOptional: i % 2 === 1,
fallback: codes.filter(x => x !== code),
_links: {
update: { method: 'PUT', href: `/languages/${code}` }
}
})),
_links: {
create: { method: 'POST', href: '/languages' }
}
};
}
});
export function createLanguages(...codes: string[]): AppLanguagesPayload {
return {
items: codes.map((code, i) =>
withLinks(
new AppLanguageDto(code, code,
i === 0,
i % 2 === 1,
codes.filter(x => x !== code)),
{
_links: {
update: { method: 'PUT', href: `/languages/${code}` }
}
})),
_links: {
create: { method: 'POST', href: '/languages' }
}
};
}

88
src/Squidex/app/shared/services/app-languages.service.ts

@ -15,17 +15,20 @@ import {
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Resource,
ResourceLinks,
Version,
Versioned
Versioned,
withLinks
} from '@app/framework';
import { LanguageDto } from './languages.service';
export type AppLanguagesDto = Versioned<AppLanguagesPayload>;
export type AppLanguagesPayload = { items: AppLanguageDto[] } & Resource;
export type AppLanguagesDto = Versioned<AppLanguageDto[]>;
export class AppLanguageDto {
public readonly _links: ResourceLinks = {};
export class AppLanguageDto extends Model<AppLanguageDto> {
constructor(
public readonly iso2Code: string,
public readonly englishName: string,
@ -33,16 +36,6 @@ export class AppLanguageDto extends Model<AppLanguageDto> {
public readonly isOptional: boolean,
public readonly fallback: string[]
) {
super();
}
public static fromLanguage(language: LanguageDto, isMaster = false, isOptional = false, fallback: string[] = []) {
return new AppLanguageDto(
language.iso2Code,
language.englishName,
isMaster,
isOptional,
fallback);
}
}
@ -68,36 +61,19 @@ export class AppLanguagesService {
public getLanguages(appName: string): Observable<AppLanguagesDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
const items: any[] = body;
const languages = items.map(item =>
new AppLanguageDto(
item.iso2Code,
item.englishName,
item.isMaster,
item.isOptional,
item.fallback || []));
return languages;
return parseLanguages(body);
}),
pretifyError('Failed to load languages. Please reload.'));
}
public postLanguage(appName: string, dto: AddAppLanguageDto, version: Version): Observable<Versioned<AppLanguageDto>> {
public postLanguage(appName: string, dto: AddAppLanguageDto, version: Version): Observable<AppLanguagesDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(({ body }) => {
const language = new AppLanguageDto(
body.iso2Code,
body.englishName,
body.isMaster,
body.isOptional,
body.fallback || []);
return language;
return parseLanguages(body);
}),
tap(() => {
this.analytics.trackEvent('Language', 'Added', appName);
@ -105,23 +81,49 @@ export class AppLanguagesService {
pretifyError('Failed to add language. Please reload.'));
}
public putLanguage(appName: string, languageCode: string, dto: UpdateAppLanguageDto, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
public putLanguage(appName: string, resource: Resource, dto: UpdateAppLanguageDto, version: Version): Observable<AppLanguagesDto> {
const link = resource._links['update'];
return HTTP.putVersioned(this.http, url, dto, version).pipe(
const url = this.apiUrl.buildUrl(link.href);
return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe(
mapVersioned(({ body }) => {
return parseLanguages(body);
}),
tap(() => {
this.analytics.trackEvent('Language', 'Updated', appName);
}),
pretifyError('Failed to change language. Please reload.'));
}
public deleteLanguage(appName: string, languageCode: string, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
public deleteLanguage(appName: string, resource: Resource, version: Version): Observable<AppLanguagesDto> {
const link = resource._links['update'];
return HTTP.deleteVersioned(this.http, url, version).pipe(
const url = this.apiUrl.buildUrl(link.href);
return HTTP.requestVersioned(this.http, link.method, url, version).pipe(
mapVersioned(({ body }) => {
return parseLanguages(body);
}),
tap(() => {
this.analytics.trackEvent('Language', 'Deleted', appName);
}),
pretifyError('Failed to add language. Please reload.'));
}
}
function parseLanguages(body: any) {
const items: any[] = body.items;
const languages = items.map(item =>
withLinks(
new AppLanguageDto(
item.iso2Code,
item.englishName,
item.isMaster,
item.isOptional,
item.fallback || []),
item));
return withLinks({ items: languages, _links: {} }, body);
}

4
src/Squidex/app/shared/services/apps.service.ts

@ -14,13 +14,12 @@ import {
AnalyticsService,
ApiUrlConfig,
DateTime,
Model,
pretifyError,
ResourceLinks,
withLinks
} from '@app/framework';
export class AppDto extends Model<AppDto> {
export class AppDto {
public readonly _links: ResourceLinks = {};
constructor(
@ -32,7 +31,6 @@ export class AppDto extends Model<AppDto> {
public readonly planName?: string,
public readonly planUpgrade?: string
) {
super();
}
}

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

@ -17,7 +17,6 @@ import {
ErrorDto,
HTTP,
Metadata,
Model,
pretifyError,
Resource,
ResourceLinks,
@ -32,7 +31,7 @@ export class AssetsDto extends ResultSet<AssetDto> {
public readonly _links: ResourceLinks = {};
}
export class AssetDto extends Model<AssetDto> {
export class AssetDto {
public get canPreview() {
return this.isImage || (this.mimeType === 'image/svg+xml' && this.fileSize < 100 * 1024);
}
@ -60,7 +59,6 @@ export class AssetDto extends Model<AssetDto> {
public readonly tags: string[],
public readonly version: Version
) {
super();
}
}

3
src/Squidex/app/shared/services/backups.service.ts

@ -27,7 +27,7 @@ export class BackupsDto extends ResultSet<BackupDto> {
public readonly _links: ResourceLinks = {};
}
export class BackupDto extends Model<BackupDto> {
export class BackupDto {
public readonly _links: ResourceLinks = {};
constructor(
@ -38,7 +38,6 @@ export class BackupDto extends Model<BackupDto> {
public readonly handledAssets: number,
public readonly status: string
) {
super();
}
}

11
src/Squidex/app/shared/services/contributors.service.spec.ts

@ -139,11 +139,14 @@ describe('ContributorsService', () => {
export function createContributors(...ids: number[]): ContributorsPayload {
return {
contributors: ids.map(id =>
withLinks(new ContributorDto(`id${id}`, id % 2 === 0 ? 'Owner' : 'Developer'), {
_links: {
update: { method: 'PUT', href: `/contributors/id${id}` }
withLinks(
new ContributorDto(`id${id}`, id % 2 === 0 ? 'Owner' : 'Developer'),
{
_links: {
update: { method: 'PUT', href: `/contributors/id${id}` }
}
}
})),
)),
maxContributors: ids.length * 13,
_links: {
create: { method: 'POST', href: '/contributors' }

30
src/Squidex/app/shared/services/contributors.service.ts

@ -15,8 +15,6 @@ import {
ApiUrlConfig,
HTTP,
mapVersioned,
Metadata,
Model,
pretifyError,
Resource,
ResourceLinks,
@ -27,20 +25,17 @@ import {
export type ContributorsDto = Versioned<ContributorsPayload>;
export type ContributorsPayload = {
readonly _links?: ResourceLinks,
readonly _meta?: Metadata
readonly contributors: ContributorDto[],
readonly maxContributors: number
};
} & Resource;
export class ContributorDto extends Model<AssignContributorDto> {
export class ContributorDto {
public readonly _links: ResourceLinks = {};
constructor(
public readonly contributorId: string,
public readonly role: string
) {
super();
}
}
@ -63,9 +58,7 @@ export class ContributorsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.getVersioned<any>(this.http, url).pipe(
mapVersioned(payload => {
const body = payload.body;
mapVersioned(({ body }) => {
return parseContributors(body);
}),
pretifyError('Failed to load contributors. Please reload.'));
@ -75,9 +68,7 @@ export class ContributorsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(payload => {
const body = payload.body;
mapVersioned(({ body }) => {
return parseContributors(body);
}),
tap(() => {
@ -107,13 +98,12 @@ export class ContributorsService {
function parseContributors(body: any) {
const items: any[] = body.contributors;
const contributors =
items.map(item =>
withLinks(
new ContributorDto(
item.contributorId,
item.role),
item));
const contributors = items.map(item =>
withLinks(
new ContributorDto(
item.contributorId,
item.role),
item));
return withLinks({ contributors, maxContributors: body.maxContributors, _links: {} }, body);
}

3
src/Squidex/app/shared/services/rules.service.ts

@ -79,7 +79,7 @@ export class RulesDto extends ResultSet<RuleDto> {
public readonly _links: ResourceLinks = {};
}
export class RuleDto extends Model<RuleDto> {
export class RuleDto {
public readonly _links: ResourceLinks = {};
constructor(
@ -95,7 +95,6 @@ export class RuleDto extends Model<RuleDto> {
public readonly action: any,
public readonly actionType: string
) {
super();
}
}

59
src/Squidex/app/shared/state/languages.state.spec.ts

@ -9,7 +9,6 @@ import { of } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppLanguageDto,
AppLanguagesService,
DialogService,
ImmutableArray,
@ -19,6 +18,8 @@ import {
versioned
} from '@app/shared/internal';
import { createLanguages } from '../services/app-languages.service.spec';
import { TestValues } from './_test-helpers';
describe('LanguagesState', () => {
@ -34,10 +35,7 @@ describe('LanguagesState', () => {
const languageIT = new LanguageDto('it', 'Italian');
const languageES = new LanguageDto('es', 'Spanish');
const oldLanguages = [
AppLanguageDto.fromLanguage(languageEN, true),
AppLanguageDto.fromLanguage(languageDE, false, true, [languageEN.iso2Code])
];
const oldLanguages = createLanguages('en', 'de');
let dialogs: IMock<DialogService>;
let languagesService: IMock<AppLanguagesService>;
@ -72,11 +70,11 @@ describe('LanguagesState', () => {
expect(languagesState.snapshot.languages.values).toEqual([
{
language: oldLanguages[0],
language: oldLanguages.items[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([oldLanguages[1]])
}, {
language: oldLanguages[1],
language: oldLanguages.items[1],
fallbackLanguages: ImmutableArray.of([oldLanguages[0]]),
fallbackLanguagesNew: ImmutableArray.empty()
}
@ -103,10 +101,10 @@ describe('LanguagesState', () => {
});
it('should add language to snapshot when assigned', () => {
const newLanguage = new AppLanguageDto(languageIT.iso2Code, languageIT.englishName, false, false, []);
const updated = createLanguages('de');
languagesService.setup(x => x.postLanguage(app, It.isAny(), version))
.returns(() => of(versioned(newVersion, newLanguage))).verifiable();
.returns(() => of(versioned(newVersion, updated))).verifiable();
languagesState.add(languageIT).subscribe();
@ -114,50 +112,39 @@ describe('LanguagesState', () => {
{
language: oldLanguages[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([oldLanguages[1], newLanguage])
}, {
language: oldLanguages[1],
fallbackLanguages: ImmutableArray.of([oldLanguages[0]]),
fallbackLanguagesNew: ImmutableArray.of([newLanguage])
}, {
language: newLanguage,
fallbackLanguages: ImmutableArray.of(),
fallbackLanguagesNew: ImmutableArray.of([oldLanguages[0], oldLanguages[1]])
fallbackLanguagesNew: ImmutableArray.empty()
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageDE, languageIT, languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
});
it('should update language in snapshot when updated', () => {
const updated = createLanguages('de');
const request = { isMaster: true, isOptional: false, fallback: [] };
languagesService.setup(x => x.putLanguage(app, oldLanguages[1].iso2Code, request, version))
.returns(() => of(versioned(newVersion))).verifiable();
.returns(() => of(versioned(newVersion, updated))).verifiable();
languagesState.update(oldLanguages[1], request).subscribe();
const newLanguage1 = AppLanguageDto.fromLanguage(languageDE, true);
const newLanguage2 = AppLanguageDto.fromLanguage(languageEN);
expect(languagesState.snapshot.languages.values).toEqual([
{
language: newLanguage1,
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([newLanguage2])
}, {
language: newLanguage2,
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([newLanguage1])
}
{
language: oldLanguages[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.empty()
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageIT, languageES]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageDE, languageIT, languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
});
it('should remove language from snapshot when deleted', () => {
const updated = createLanguages('de');
languagesService.setup(x => x.deleteLanguage(app, oldLanguages[1].iso2Code, version))
.returns(() => of(versioned(newVersion))).verifiable();
.returns(() => of(versioned(newVersion, updated))).verifiable();
languagesState.remove(oldLanguages[1]).subscribe();

66
src/Squidex/app/shared/state/languages.state.ts

@ -12,7 +12,7 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
mapVersioned,
ResourceLinks,
shareMapSubscribed,
shareSubscribed,
State,
@ -21,6 +21,7 @@ import {
import {
AppLanguageDto,
AppLanguagesPayload,
AppLanguagesService,
UpdateAppLanguageDto
} from './../services/app-languages.service';
@ -57,6 +58,9 @@ interface Snapshot {
// Indicates if the languages are loaded.
isLoaded?: boolean;
// The links.
links: ResourceLinks;
}
type AppLanguagesList = ImmutableArray<AppLanguageDto>;
@ -77,6 +81,10 @@ export class LanguagesState extends State<Snapshot> {
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
public links =
this.changes.pipe(map(x => x.links),
distinctUntilChanged());
constructor(
private readonly appLanguagesService: AppLanguagesService,
private readonly appsState: AppsState,
@ -88,7 +96,8 @@ export class LanguagesState extends State<Snapshot> {
allLanguages: ImmutableArray.empty(),
allLanguagesNew: ImmutableArray.empty(),
languages: ImmutableArray.empty(),
version: new Version('')
version: new Version(''),
links: {}
});
}
@ -110,67 +119,49 @@ export class LanguagesState extends State<Snapshot> {
const sorted = ImmutableArray.of(allLanguages).sortByStringAsc(x => x.englishName);
this.replaceLanguages(ImmutableArray.of(languages.payload), languages.version, sorted);
this.replaceLanguages(languages.payload, languages.version, sorted);
}),
shareSubscribed(this.dialogs));
}
public add(language: LanguageDto): Observable<AppLanguageDto> {
public add(language: LanguageDto): Observable<any> {
return this.appLanguagesService.postLanguage(this.appName, { language: language.iso2Code }, this.version).pipe(
tap(({ version, payload }) => {
const languages = this.snapshot.plainLanguages.push(payload).sortByStringAsc(x => x.englishName);
this.replaceLanguages(languages, version);
this.replaceLanguages(payload, version);
}),
shareMapSubscribed(this.dialogs, x => x.payload));
}
public remove(language: AppLanguageDto): Observable<any> {
return this.appLanguagesService.deleteLanguage(this.appName, language.iso2Code, this.version).pipe(
tap(({ version }) => {
const languages = this.snapshot.plainLanguages.filter(x => x.iso2Code !== language.iso2Code);
this.replaceLanguages(languages, version);
return this.appLanguagesService.deleteLanguage(this.appName, language, this.version).pipe(
tap(({ version, payload }) => {
this.replaceLanguages(payload, version);
}),
shareSubscribed(this.dialogs));
}
public update(language: AppLanguageDto, request: UpdateAppLanguageDto): Observable<AppLanguageDto> {
return this.appLanguagesService.putLanguage(this.appName, language.iso2Code, request, this.version).pipe(
mapVersioned(() => update(language, request)),
public update(language: AppLanguageDto, request: UpdateAppLanguageDto): Observable<any> {
return this.appLanguagesService.putLanguage(this.appName, language, request, this.version).pipe(
tap(({ version, payload }) => {
const languages = this.snapshot.plainLanguages.map(x => {
if (x.iso2Code === language.iso2Code) {
return payload;
} else if (x.isMaster && request.isMaster) {
return update(x, { isMaster: false });
} else {
return x;
}
});
this.replaceLanguages(languages, version);
this.replaceLanguages(payload, version);
}),
shareMapSubscribed(this.dialogs, x => x.payload));
}
private replaceLanguages(languages: AppLanguagesList, version: Version, allLanguages?: LanguageList) {
private replaceLanguages(payload: AppLanguagesPayload, version: Version, allLanguages?: LanguageList) {
this.next(s => {
allLanguages = allLanguages || s.allLanguages;
const languages = ImmutableArray.of(payload.items);
return {
...s,
languages: languages.sort((a, b) => {
if (a.isMaster === b.isMaster) {
return a.englishName.localeCompare(b.englishName);
} else {
return (a.isMaster ? 0 : 1) - (b.isMaster ? 0 : 1);
}
}).map(x => this.createLanguage(x, languages)),
plainLanguages: languages,
languages: languages.map(x => this.createLanguage(x, languages)),
plainLanguages: payload,
allLanguages: allLanguages,
allLanguagesNew: allLanguages.filter(x => !languages.find(l => l.iso2Code === x.iso2Code)),
isLoaded: true,
links: payload._links,
version: version
};
});
@ -200,7 +191,4 @@ export class LanguagesState extends State<Snapshot> {
.sortByStringAsc(x => x.englishName)
};
}
}
const update = (language: AppLanguageDto, request: UpdateAppLanguageDto) =>
language.with(request);
}
Loading…
Cancel
Save