Browse Source

Languages state.

pull/282/head
Sebastian Stehle 8 years ago
parent
commit
22864eb04d
  1. 21
      src/Squidex/app/features/administration/state/users.state.spec.ts
  2. 21
      src/Squidex/app/features/administration/state/users.state.ts
  3. 36
      src/Squidex/app/features/settings/pages/languages/language.component.html
  4. 12
      src/Squidex/app/features/settings/pages/languages/language.component.scss
  5. 134
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  6. 34
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  7. 6
      src/Squidex/app/features/settings/pages/languages/languages-page.component.scss
  8. 115
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  9. 4
      src/Squidex/app/features/settings/pages/patterns/pattern.component.ts
  10. 4
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.html
  11. 2
      src/Squidex/app/framework/state.ts
  12. 2
      src/Squidex/app/shared/components/asset.component.ts
  13. 1
      src/Squidex/app/shared/internal.ts
  14. 2
      src/Squidex/app/shared/module.ts
  15. 67
      src/Squidex/app/shared/services/app-languages.service.spec.ts
  16. 35
      src/Squidex/app/shared/services/app-languages.service.ts
  17. 24
      src/Squidex/app/shared/state/contributors.state.spec.ts
  18. 35
      src/Squidex/app/shared/state/contributors.state.ts
  19. 163
      src/Squidex/app/shared/state/languages.state.spec.ts
  20. 223
      src/Squidex/app/shared/state/languages.state.ts

21
src/Squidex/app/features/administration/state/users.state.spec.ts

@ -51,7 +51,10 @@ describe('UsersState', () => {
}); });
it('should load users', () => { it('should load users', () => {
expect(usersState.snapshot.users.values).toEqual(oldUsers.map(x => u(x))); expect(usersState.snapshot.users.values).toEqual([
{ isCurrentUser: false, user: oldUsers[0] },
{ isCurrentUser: true, user: oldUsers[1] }
]);
expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200); expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200);
expect(usersState.snapshot.isLoaded).toBeTruthy(); expect(usersState.snapshot.isLoaded).toBeTruthy();
@ -77,7 +80,7 @@ describe('UsersState', () => {
usersState.load().subscribe(); usersState.load().subscribe();
expect(usersState.snapshot.selectedUser).toEqual(u(newUsers[0])); expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: newUsers[0] });
}); });
it('should return user on select and not load when already loaded', () => { it('should return user on select and not load when already loaded', () => {
@ -88,7 +91,7 @@ describe('UsersState', () => {
}); });
expect(selectedUser!).toEqual(oldUsers[0]); expect(selectedUser!).toEqual(oldUsers[0]);
expect(usersState.snapshot.selectedUser).toEqual(u(oldUsers[0])); expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: oldUsers[0] });
usersService.verify(x => x.getUser(It.isAnyString()), Times.never()); usersService.verify(x => x.getUser(It.isAnyString()), Times.never());
}); });
@ -104,7 +107,7 @@ describe('UsersState', () => {
}); });
expect(selectedUser!).toEqual(newUser); expect(selectedUser!).toEqual(newUser);
expect(usersState.snapshot.selectedUser).toEqual(u(newUser)); expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: newUser });
usersService.verify(x => x.getUser('id3'), Times.once()); usersService.verify(x => x.getUser('id3'), Times.once());
}); });
@ -186,7 +189,11 @@ describe('UsersState', () => {
usersState.create(request).subscribe(); usersState.create(request).subscribe();
expect(usersState.snapshot.users.values).toEqual([u(newUser), ...oldUsers.map(x => u(x))]); expect(usersState.snapshot.users.values).toEqual([
{ isCurrentUser: false, user: newUser },
{ isCurrentUser: false, user: oldUsers[0] },
{ isCurrentUser: true, user: oldUsers[1] }
]);
expect(usersState.snapshot.usersPager.numberOfItems).toBe(201); expect(usersState.snapshot.usersPager.numberOfItems).toBe(201);
}); });
@ -211,8 +218,4 @@ describe('UsersState', () => {
usersService.verify(x => x.getUsers(10, 0, 'my-query'), Times.once()); usersService.verify(x => x.getUsers(10, 0, 'my-query'), Times.once());
}); });
function u(user: UserDto) {
return { user, isCurrentUser: user.id === 'id2' };
}
}); });

21
src/Squidex/app/features/administration/state/users.state.ts

@ -209,19 +209,28 @@ export class UsersState extends State<Snapshot> {
return this.load(); return this.load();
} }
private replaceUser(userDto: UserDto) { private replaceUser(user: UserDto) {
return this.next(s => { return this.next(s => {
const user = this.createUser(userDto); const users = s.users.map(u => u.user.id === user.id ? this.createUser(user, u) : u);
const users = s.users.map(u => u.user.id === userDto.id ? user : u);
const selectedUser = s.selectedUser && s.selectedUser.user.id === userDto.id ? user : s.selectedUser; const selectedUser = s.selectedUser && s.selectedUser.user.id === user.id ? users.find(x => x.user.id === user.id) : s.selectedUser;
return { ...s, users, selectedUser }; return { ...s, users, selectedUser };
}); });
} }
private createUser(user: UserDto): SnapshotUser { private get userId() {
return user ? { user, isCurrentUser: user.id === this.authState.user!.id } : null!; return this.authState.user!.id;
}
private createUser(user: UserDto, current?: SnapshotUser): SnapshotUser {
if (!user) {
return null!;
} else if (current && current.user === user) {
return current;
} else {
return { user, isCurrentUser: user.id === this.userId };
}
} }
} }

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

@ -7,14 +7,14 @@
<div class="col col-6"> <div class="col col-6">
<span class="language-name table-cell" [class.language-optional]="language.isOptional" [class.language-master]="language.isMaster">{{language.englishName}}</span> <span class="language-name table-cell" [class.language-optional]="language.isOptional" [class.language-master]="language.isMaster">{{language.englishName}}</span>
</div> </div>
<div class="col col-4"> <div class="col col-options">
<div class="float-right"> <div class="float-right">
<button type="button" class="btn btn-secondary table-items-edit-button" [class.active]="isEditing" (click)="toggleEditing()" *ngIf="!language.isMaster || allLanguages.length > 1"> <button type="button" class="btn btn-secondary table-items-edit-button" [class.active]="isEditing" (click)="toggleEditing()" *ngIf="!language.isMaster">
<i class="icon-settings"></i> <i class="icon-settings"></i>
</button> </button>
<button type="button" class="btn btn-link btn-danger" [class.invisible]="language.isMaster" <button type="button" class="btn btn-link btn-danger" [class.invisible]="language.isMaster"
(sqxConfirmClick)="removing.emit(language)" (sqxConfirmClick)="remove()"
confirmTitle="Remove language" confirmTitle="Remove language"
confirmText="Do you really want to remove the language?"> confirmText="Do you really want to remove the language?">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
@ -25,37 +25,45 @@
</div> </div>
<div class="table-items-row-details" *ngIf="isEditing"> <div class="table-items-row-details" *ngIf="isEditing">
<form [formGroup]="editForm" (ngSubmit)="save()"> <form [formGroup]="editForm.form" (ngSubmit)="save()">
<div class="table-items-row-details-tabs clearfix"> <div class="table-items-row-details-tabs clearfix">
<div class="float-right"> <div class="float-right">
<button type="reset" class="btn btn-link" (click)="cancel()">Cancel</button> <button type="reset" class="btn btn-link" (click)="toggleEditing()">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button> <button type="submit" class="btn btn-primary">Save</button>
</div> </div>
</div> </div>
<div class="table-items-row-details-tab"> <div class="table-items-row-details-tab">
<div class="form-group row" *ngIf="allLanguages.length > 1"> <div class="form-group row">
<label class="col col-3 col-form-label fallback-label">Fallback</label> <label class="col col-3 col-form-label fallback-label">Fallback</label>
<div class="col col-9"> <div class="col col-9">
<div class="fallback-languages" dnd-sortable-container [sortableData]="fallbackLanguages" *ngIf="fallbackLanguages.length > 0"> <div class="fallback-languages" dnd-sortable-container [sortableData]="fallbackLanguages.mutableValues" *ngIf="fallbackLanguages.length > 0">
<div class="fallback-language" *ngFor="let language of fallbackLanguages; let i = index" dnd-sortable [sortableIndex]="i"> <div class="fallback-language" *ngFor="let language of fallbackLanguages; let i = index" dnd-sortable [sortableIndex]="i">
<div class="row no-gutter">
<div class="col">
{{language.englishName}} {{language.englishName}}
</div>
<button type="button" class="btn btn-link btn-secondary btn-sm float-right" (click)="removeFallbackLanguage(language)"> <div class="col col-auto">
<button type="button" class="btn btn-link btn-secondary btn-sm" (click)="removeFallbackLanguage(language)">
<i class="icon-close"></i> <i class="icon-close"></i>
</button> </button>
</div> </div>
</div> </div>
</div>
</div>
<form class="form-inline fallback-form" (ngSubmit)="addLanguage()" *ngIf="otherLanguages.length > 0"> <form class="form fallback-form" (ngSubmit)="addFallbackLanguage()" *ngIf="otherLanguage">
<div class="form-group mr-1"> <div class="row no-gutters">
<div class="col">
<select class="form-control fallback-select" [(ngModel)]="otherLanguage" name="otherLanguage"> <select class="form-control fallback-select" [(ngModel)]="otherLanguage" name="otherLanguage">
<option *ngFor="let otherLanguage of otherLanguages" [ngValue]="otherLanguage">{{otherLanguage.englishName}}</option> <option *ngFor="let otherLanguage of fallbackLanguagesNew; trackBy: trackByLanguage" [ngValue]="otherLanguage">{{otherLanguage.englishName}}</option>
</select> </select>
</div> </div>
<div class="col col-auto pl-1">
<button type="submit" class="btn btn-success">Add Language</button> <button type="submit" class="btn btn-success">Add Language</button>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
@ -78,7 +86,7 @@
<div class="form-group row" *ngIf="!language.isMaster"> <div class="form-group row" *ngIf="!language.isMaster">
<div class="col offset-3 col-9" *ngIf="!isMaster"> <div class="col offset-3 col-9" *ngIf="!isMaster">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" is="isOptional" formControlName="isOptional" /> <input class="form-check-input" type="checkbox" id="isOptional" formControlName="isOptional" />
<label class="form-check-label" for="isOptional"> <label class="form-check-label" for="isOptional">
Is Optional Is Optional
</label> </label>

12
src/Squidex/app/features/settings/pages/languages/language.component.scss

@ -13,14 +13,9 @@ $field-header: #e7ebef;
} }
} }
.btn-danger {
width: 3.2rem;
}
.fallback { .fallback {
&-languages { &-languages {
@include border-radius; @include border-radius;
min-height: 2rem;
background: $color-border; background: $color-border;
border: 0; border: 0;
padding: .5rem; padding: .5rem;
@ -33,6 +28,7 @@ $field-header: #e7ebef;
background: $color-dark-foreground; background: $color-dark-foreground;
border: 0; border: 0;
margin-bottom: .5rem; margin-bottom: .5rem;
line-height: 2rem;
} }
&:last-child { &:last-child {
@ -47,8 +43,10 @@ $field-header: #e7ebef;
&-form { &-form {
margin-top: .5rem; margin-top: .5rem;
} }
}
&-select { .table-items-row-details {
width: 14rem; &::before {
right: 4.6rem;
} }
} }

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

@ -5,11 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { Subscription } from 'rxjs';
import { AppLanguageDto, fadeAnimation } from '@app/shared'; import {
AppLanguageDto,
EditLanguageForm,
fadeAnimation,
ImmutableArray,
LanguagesState,
UpdateAppLanguageDto
} from '@app/shared';
@Component({ @Component({
selector: 'sqx-language', selector: 'sqx-language',
@ -19,127 +25,79 @@ import { AppLanguageDto, fadeAnimation } from '@app/shared';
fadeAnimation fadeAnimation
] ]
}) })
export class LanguageComponent implements OnInit, OnChanges, OnDestroy { export class LanguageComponent implements OnChanges {
private isMasterSubscription: Subscription;
@Input() @Input()
public language: AppLanguageDto; public language: AppLanguageDto;
@Input() @Input()
public allLanguages: AppLanguageDto[]; public fallbackLanguages: ImmutableArray<AppLanguageDto>;
@Output()
public removing = new EventEmitter<AppLanguageDto>();
@Output() @Input()
public saving = new EventEmitter<AppLanguageDto>(); public fallbackLanguagesNew: ImmutableArray<AppLanguageDto>;
public otherLanguages: AppLanguageDto[];
public otherLanguage: AppLanguageDto; public otherLanguage: AppLanguageDto;
public fallbackLanguages: AppLanguageDto[] = [];
public isEditing = false; public isEditing = false;
public isMaster = false;
public editFormSubmitted = false; public editForm = new EditLanguageForm(this.formBuilder);
public editForm =
this.formBuilder.group({
isMaster: false,
isOptional: false
});
constructor( constructor(
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder,
private readonly languagesState: LanguagesState
) { ) {
} }
public ngOnDestroy() {
this.isMasterSubscription.unsubscribe();
}
public ngOnInit() {
this.isMasterSubscription =
this.editForm.controls['isMaster'].valueChanges
.subscribe(v => {
this.isMaster = v;
this.editForm.controls['isOptional'].setValue(false);
});
this.resetEditForm();
}
public ngOnChanges() { public ngOnChanges() {
this.resetEditForm(); this.resetForm();
}
public cancel() {
this.resetEditForm();
}
public addLanguage() {
this.addFallbackLanguage(this.otherLanguage);
} }
public toggleEditing() { public toggleEditing() {
this.isEditing = !this.isEditing; this.isEditing = !this.isEditing;
} }
public removeFallbackLanguage(language: AppLanguageDto) { public remove() {
this.fallbackLanguages.splice(this.fallbackLanguages.indexOf(language), 1); this.languagesState.remove(this.language).onErrorResumeNext().subscribe();
this.otherLanguages = [...this.otherLanguages, language];
this.otherLanguage = this.otherLanguages[0];
} }
public addFallbackLanguage(language: AppLanguageDto) { public save() {
this.fallbackLanguages.push(language); const value = this.editForm.submit();
this.otherLanguages = this.otherLanguages.filter(l => l.iso2Code !== language.iso2Code); if (value) {
this.otherLanguage = this.otherLanguages[0]; const request = new UpdateAppLanguageDto(value.isMaster, value.isOptional, this.fallbackLanguages.map(x => x.iso2Code).values);
}
public save() { this.languagesState.update(this.language, request)
this.editFormSubmitted = true; .subscribe(() => {
this.editForm.submitCompleted();
if (this.editForm.valid) {
const newLanguage = this.toggleEditing();
new AppLanguageDto( }, error => {
this.language.iso2Code, this.editForm.submitFailed(error);
this.language.englishName, });
this.editForm.controls['isMaster'].value,
this.editForm.controls['isOptional'].value,
this.fallbackLanguages.map(l => l.iso2Code));
this.emitSaving(newLanguage);
} }
} }
private emitSaving(language: AppLanguageDto) { public removeFallbackLanguage(language: AppLanguageDto) {
this.saving.emit(language); this.fallbackLanguages = this.fallbackLanguages.remove(language);
} this.fallbackLanguagesNew = this.fallbackLanguagesNew.push(language).sortByStringAsc(x => x.iso2Code);
private resetEditForm() { this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.editFormSubmitted = false; }
this.editForm.reset(this.language);
this.isEditing = false; public addFallbackLanguage() {
this.fallbackLanguages = this.fallbackLanguages.push(this.otherLanguage);
this.fallbackLanguagesNew = this.fallbackLanguagesNew.remove(this.otherLanguage);
if (this.language && this.allLanguages) { this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.otherLanguages =
this.allLanguages.filter(l =>
this.language.iso2Code !== l.iso2Code &&
this.language.fallback.indexOf(l.iso2Code) < 0);
this.otherLanguage = this.otherLanguages[0];
} }
if (this.language) { private resetForm() {
this.isMaster = this.language.isMaster; this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.fallbackLanguages = this.editForm.load(this.language);
this.allLanguages.filter(l =>
this.language.fallback.indexOf(l.iso2Code) >= 0);
} }
public trackByLanguage(index: number, language: AppLanguageDto) {
return language.iso2Code;
} }
} }

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

@ -2,29 +2,43 @@
<sqx-panel desiredWidth="42rem"> <sqx-panel desiredWidth="42rem">
<ng-container title> <ng-container title>
Contributors Languages
</ng-container>
<ng-container menu>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh languages (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<div *ngFor="let language of appLanguages?.languages"> <ng-container *ngIf="languagesState.isLoaded | async">
<sqx-language [language]="language" [allLanguages]="appLanguages.languages" <ng-container *ngIf="languagesState.languages | async; let languages">
(saving)="updateLanguage($event)" <sqx-language *ngFor="let languageInfo of languages; trackBy: trackByLanguage"
(removing)="removeLanguage($event)"></sqx-language> [language]="languageInfo.language"
</div> [fallbackLanguages]="languageInfo.fallbackLanguages"
[fallbackLanguagesNew]="languageInfo.fallbackLanguagesNew">
</sqx-language>
</ng-container>
<div class="table-items-footer" *ngIf="appLanguages"> <ng-container *ngIf="languagesState.newLanguages | async; let newLanguages">
<form [formGroup]="addLanguageForm" (ngSubmit)="addLanguage()"> <div class="table-items-footer">
<form [formGroup]="addLanguageForm.form" (ngSubmit)="addLanguage()">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col"> <div class="col">
<select class="form-control language-select" formControlName="language"> <select class="form-control" formControlName="language">
<option *ngFor="let language of newLanguages" [ngValue]="language">{{language.englishName}}</option> <option *ngFor="let language of newLanguages" [ngValue]="language">{{language.englishName}}</option>
</select> </select>
</div> </div>
<div class="col col-auto pl-1"> <div class="col col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="!addLanguageForm.valid">Add Language</button> <button type="submit" class="btn btn-success">Add Language</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</ng-container> </ng-container>
</ng-container>
</ng-container>
</sqx-panel> </sqx-panel>

6
src/Squidex/app/features/settings/pages/languages/languages-page.component.scss

@ -1,8 +1,2 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
.table-item-row-details {
&::before {
right: 5.2rem;
}
}

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

@ -5,19 +5,15 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { Subscription } from 'rxjs';
import { import {
AddAppLanguageDto, AddLanguageForm,
AppLanguageDto, AppLanguageDto,
AppLanguagesDto,
AppLanguagesService,
AppsState, AppsState,
DialogService, LanguagesState
ImmutableArray,
LanguageDto,
LanguagesService
} from '@app/shared'; } from '@app/shared';
@Component({ @Component({
@ -25,104 +21,53 @@ import {
styleUrls: ['./languages-page.component.scss'], styleUrls: ['./languages-page.component.scss'],
templateUrl: './languages-page.component.html' templateUrl: './languages-page.component.html'
}) })
export class LanguagesPageComponent implements OnInit { export class LanguagesPageComponent implements OnDestroy, OnInit {
public allLanguages: LanguageDto[] = []; private newLanguagesSubscription: Subscription;
public newLanguages: LanguageDto[] = [];
public appLanguages: AppLanguagesDto;
public addLanguageForm = public addLanguageForm = new AddLanguageForm(this.formBuilder);
this.formBuilder.group({
language: [null,
[
Validators.required
]
]
});
constructor( constructor(
public readonly appsState: AppsState, public readonly appsState: AppsState,
private readonly appLanguagesService: AppLanguagesService, public readonly languagesState: LanguagesState,
private readonly dialogs: DialogService, private readonly formBuilder: FormBuilder
private readonly formBuilder: FormBuilder,
private readonly languagesService: LanguagesService
) { ) {
} }
public ngOnInit() { public ngOnDestroy() {
this.loadAllLanguages(); this.newLanguagesSubscription.unsubscribe();
this.load();
} }
public load() { public ngOnInit() {
this.appLanguagesService.getLanguages(this.appsState.appName) this.newLanguagesSubscription =
.subscribe(dto => { this.languagesState.newLanguages
this.updateLanguages(dto); .subscribe(languages => {
}, error => { if (languages.length > 0) {
this.dialogs.notifyError(error); this.addLanguageForm.load({ language: languages.at(0) });
});
} }
public removeLanguage(language: AppLanguageDto) {
this.appLanguagesService.deleteLanguage(this.appsState.appName, language.iso2Code, this.appLanguages.version)
.subscribe(dto => {
this.updateLanguages(this.appLanguages.removeLanguage(language, dto.version));
}, error => {
this.dialogs.notifyError(error);
}); });
}
public addLanguage() {
const requestDto = new AddAppLanguageDto(this.addLanguageForm.controls['language'].value.iso2Code);
this.appLanguagesService.postLanguages(this.appsState.appName, requestDto, this.appLanguages.version) this.languagesState.load().onErrorResumeNext().subscribe();
.subscribe(dto => {
this.updateLanguages(this.appLanguages.addLanguage(dto.payload, dto.version));
}, error => {
this.dialogs.notifyError(error);
});
} }
public updateLanguage(language: AppLanguageDto) { public reload() {
this.appLanguagesService.putLanguage(this.appsState.appName, language.iso2Code, language, this.appLanguages.version) this.languagesState.load(true).onErrorResumeNext().subscribe();
.subscribe(dto => {
this.updateLanguages(this.appLanguages.updateLanguage(language, dto.version));
}, error => {
this.dialogs.notifyError(error);
});
} }
private loadAllLanguages() { public addLanguage() {
this.languagesService.getLanguages() const value = this.addLanguageForm.submit();
.subscribe(languages => {
this.allLanguages = ImmutableArray.of(languages).sortByStringAsc(l => l.englishName).values;
this.updateNewLanguages(); if (value) {
this.languagesState.add(value.language)
.subscribe(() => {
this.addLanguageForm.submitCompleted();
}, error => { }, error => {
this.dialogs.notifyError(error); this.addLanguageForm.submitFailed(error);
}); });
} }
private updateLanguages(appLanguages: AppLanguagesDto, masterId?: string) {
this.addLanguageForm.reset();
this.appLanguages =
new AppLanguagesDto(
appLanguages.languages.sort((a, b) => {
if (a.isMaster === b.isMaster) {
return a.iso2Code.localeCompare(b.iso2Code);
} else {
return (a.isMaster ? 0 : 1) - (b.isMaster ? 0 : 1);
} }
}), appLanguages.version);
this.updateNewLanguages(); public trackByLanguage(index: number, language: { language: AppLanguageDto }) {
} return language.language;
private updateNewLanguages() {
if (this.appLanguages) {
this.newLanguages = this.allLanguages.filter(x => !this.appLanguages.languages.find(l => l.iso2Code === x.iso2Code));
this.addLanguageForm.controls['language'].setValue(this.newLanguages[0]);
}
} }
} }

4
src/Squidex/app/features/settings/pages/patterns/pattern.component.ts

@ -52,14 +52,14 @@ export class PatternComponent implements OnInit {
.subscribe(() => { .subscribe(() => {
this.editForm.submitCompleted(); this.editForm.submitCompleted();
}, error => { }, error => {
this.editForm.submitFailed(); this.editForm.submitFailed(error);
}); });
} else { } else {
this.patternsState.create(value) this.patternsState.create(value)
.subscribe(() => { .subscribe(() => {
this.editForm.submitCompleted({}); this.editForm.submitCompleted({});
}, error => { }, error => {
this.editForm.submitFailed(); this.editForm.submitFailed(error);
}); });
} }
} }

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

@ -1,6 +1,6 @@
<ng-content></ng-content> <ng-content></ng-content>
<sqx-modal-dialog *sqxModalView="dialogView;onRoot:true" showClose="false"> <sqx-modal-dialog *sqxModalView="dialogView;onRoot:true" showClose="false" (closed)="cancel()">
<ng-container title> <ng-container title>
{{dialogRequest?.title}} {{dialogRequest?.title}}
</ng-container> </ng-container>
@ -11,7 +11,7 @@
<ng-container footer> <ng-container footer>
<button type="button" class="float-left btn btn-secondary" (click)="cancel()">No</button> <button type="button" class="float-left btn btn-secondary" (click)="cancel()">No</button>
<button type="button" class="float-right btn btn-danger" (click)="confirm()">Yes</button> <button type="button" class="float-right btn btn-danger" (click)="confirm()" autofocus sqxFocusOnInit>Yes</button>
</ng-container> </ng-container>
</sqx-modal-dialog> </sqx-modal-dialog>

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

@ -33,7 +33,7 @@ export class Form<T extends AbstractControl> {
public load(value: any) { public load(value: any) {
this.state.next({ submitted: false, error: null }); this.state.next({ submitted: false, error: null });
this.form.reset(value); this.form.reset(value, { emitEvent: true });
} }
public submit(): any | null { public submit(): any | null {

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

@ -134,7 +134,7 @@ export class AssetComponent implements OnInit {
}, error => { }, error => {
this.dialogs.notifyError(error); this.dialogs.notifyError(error);
this.renameForm.submitFailed(); this.renameForm.submitFailed(error);
}); });
} }
} }

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

@ -43,6 +43,7 @@ export * from './state/assets.state';
export * from './state/backups.state'; export * from './state/backups.state';
export * from './state/clients.state'; export * from './state/clients.state';
export * from './state/contributors.state'; export * from './state/contributors.state';
export * from './state/languages.state';
export * from './state/patterns.state'; export * from './state/patterns.state';
export * from './state/plans.state'; export * from './state/plans.state';
export * from './state/rules.state'; export * from './state/rules.state';

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

@ -45,6 +45,7 @@ import {
HistoryService, HistoryService,
LanguageSelectorComponent, LanguageSelectorComponent,
LanguagesService, LanguagesService,
LanguagesState,
LoadAppsGuard, LoadAppsGuard,
MarkdownEditorComponent, MarkdownEditorComponent,
MustBeAuthenticatedGuard, MustBeAuthenticatedGuard,
@ -151,6 +152,7 @@ export class SqxSharedModule {
HelpService, HelpService,
HistoryService, HistoryService,
LanguagesService, LanguagesService,
LanguagesState,
LoadAppsGuard, LoadAppsGuard,
MustBeAuthenticatedGuard, MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard, MustBeNotAuthenticatedGuard,

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

@ -19,71 +19,6 @@ import {
Version Version
} from './../'; } from './../';
describe('AppLanguageDto', () => {
const version = new Version('1');
const newVersion = new Version('2');
it('should update languages when adding language', () => {
const language1_1 = new AppLanguageDto('de', 'English', true, false, []);
const language2_1 = new AppLanguageDto('it', 'Italien', false, false, []);
const languages_1 = new AppLanguagesDto([language1_1], version);
const languages_2 = languages_1.addLanguage(language2_1, newVersion);
expect(languages_2.languages).toEqual([language1_1, language2_1]);
expect(languages_2.version).toEqual(newVersion);
});
it('should update languages when removing language', () => {
const language1_1 = new AppLanguageDto('de', 'English', true, false, ['it']);
const language1_2 = new AppLanguageDto('de', 'English', true, false, []);
const language2_1 = new AppLanguageDto('it', 'Italien', false, false, []);
const languages_1 = new AppLanguagesDto([language1_1, language2_1], version);
const languages_2 = languages_1.removeLanguage(language2_1, newVersion);
expect(languages_2.languages).toEqual([language1_2]);
expect(languages_2.version).toEqual(newVersion);
});
it('should update languages when updating language', () => {
const language1_1 = new AppLanguageDto('de', 'English', true, false, ['it']);
const language2_1 = new AppLanguageDto('it', 'Italien', false, false, []);
const language2_2 = new AppLanguageDto('it', 'Italien', false, false, ['de']);
const languages_1 = new AppLanguagesDto([language1_1, language2_1], version);
const languages_2 = languages_1.updateLanguage(language2_2, newVersion);
expect(languages_2.languages).toEqual([language1_1, language2_2]);
expect(languages_2.version).toEqual(newVersion);
});
it('should update master language when updating language', () => {
const language1_1 = new AppLanguageDto('de', 'English', true, false, ['it']);
const language1_2 = new AppLanguageDto('de', 'English', false, false, ['it']);
const language2_1 = new AppLanguageDto('it', 'Italien', false, false, []);
const language2_2 = new AppLanguageDto('it', 'Italien', true, false, ['de']);
const languages_1 = new AppLanguagesDto([language1_1, language2_1], version);
const languages_2 = languages_1.updateLanguage(language2_2, newVersion);
expect(languages_2.languages).toEqual([language1_2, language2_2]);
expect(languages_2.version).toEqual(newVersion);
expect(languages_2.languages[0].isMaster).toBeFalsy();
});
});
describe('AppLanguageDto', () => {
it('should update properties when updating', () => {
const language_1 = new AppLanguageDto('de', 'English', false, false, []);
const language_2 = language_1.update(true, true, ['de', 'it']);
expect(language_2.isMaster).toBeTruthy();
expect(language_2.isOptional).toBeTruthy();
expect(language_2.fallback).toEqual(['de', 'it']);
});
});
describe('AppLanguagesService', () => { describe('AppLanguagesService', () => {
const version = new Version('1'); const version = new Version('1');
@ -152,7 +87,7 @@ describe('AppLanguagesService', () => {
let language: AppLanguageDto | null = null; let language: AppLanguageDto | null = null;
appLanguagesService.postLanguages('my-app', dto, version).subscribe(result => { appLanguagesService.postLanguage('my-app', dto, version).subscribe(result => {
language = result.payload; language = result.payload;
}); });

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

@ -25,35 +25,6 @@ export class AppLanguagesDto {
public readonly version: Version public readonly version: Version
) { ) {
} }
public addLanguage(language: AppLanguageDto, version: Version) {
return new AppLanguagesDto([...this.languages, language], version);
}
public removeLanguage(language: AppLanguageDto, version: Version) {
return new AppLanguagesDto(
this.languages.filter(l => l.iso2Code !== language.iso2Code).map(l => {
return new AppLanguageDto(
l.iso2Code,
l.englishName,
l.isMaster,
l.isOptional,
l.fallback.filter(f => f !== language.iso2Code));
}), version);
}
public updateLanguage(language: AppLanguageDto, version: Version) {
return new AppLanguagesDto(
this.languages.map(l => {
if (l.iso2Code === language.iso2Code) {
return language;
} else if (l.isMaster && language.isMaster) {
return new AppLanguageDto(l.iso2Code, l.englishName, false, l.isOptional, l.fallback);
} else {
return l;
}
}), version);
}
} }
export class AppLanguageDto { export class AppLanguageDto {
@ -65,10 +36,6 @@ export class AppLanguageDto {
public readonly fallback: string[] public readonly fallback: string[]
) { ) {
} }
public update(isMaster: boolean, isOptional: boolean, fallback: string[]): AppLanguageDto {
return new AppLanguageDto(this.iso2Code, this.englishName, isMaster, isOptional, fallback);
}
} }
export class AddAppLanguageDto { export class AddAppLanguageDto {
@ -119,7 +86,7 @@ export class AppLanguagesService {
.pretifyError('Failed to load languages. Please reload.'); .pretifyError('Failed to load languages. Please reload.');
} }
public postLanguages(appName: string, dto: AddAppLanguageDto, version: Version): Observable<Versioned<AppLanguageDto>> { public postLanguage(appName: string, dto: AddAppLanguageDto, version: Version): Observable<Versioned<AppLanguageDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return HTTP.postVersioned<any>(this.http, url, dto, version) return HTTP.postVersioned<any>(this.http, url, dto, version)

24
src/Squidex/app/shared/state/contributors.state.spec.ts

@ -59,7 +59,10 @@ describe('ContributorsState', () => {
}); });
it('should load contributors', () => { it('should load contributors', () => {
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.map(x => c(x))); expect(contributorsState.snapshot.contributors.values).toEqual([
{ isCurrentUser: false, contributor: oldContributors[0] },
{ isCurrentUser: true, contributor: oldContributors[1] }
]);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy(); expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.isLoaded).toBeTruthy(); expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3); expect(contributorsState.snapshot.maxContributors).toBe(3);
@ -84,7 +87,11 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe(); contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([...oldContributors.map(x => c(x)), c(newContributor)]); expect(contributorsState.snapshot.contributors.values).toEqual([
{ isCurrentUser: false, contributor: oldContributors[0] },
{ isCurrentUser: true, contributor: oldContributors[1] },
{ isCurrentUser: false, contributor: newContributor }
]);
expect(contributorsState.snapshot.isMaxReached).toBeTruthy(); expect(contributorsState.snapshot.isMaxReached).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3); expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion); expect(contributorsState.snapshot.version).toEqual(newVersion);
@ -100,7 +107,10 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe(); contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[0]), c(newContributor)]); expect(contributorsState.snapshot.contributors.values).toEqual([
{ isCurrentUser: false, contributor: oldContributors[0] },
{ isCurrentUser: true, contributor: newContributor }
]);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy(); expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3); expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion); expect(contributorsState.snapshot.version).toEqual(newVersion);
@ -112,11 +122,9 @@ describe('ContributorsState', () => {
contributorsState.revoke(oldContributors[0]).subscribe(); contributorsState.revoke(oldContributors[0]).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[1])]); expect(contributorsState.snapshot.contributors.values).toEqual([
{ isCurrentUser: true, contributor: oldContributors[1] }
]);
expect(contributorsState.snapshot.version).toEqual(newVersion); expect(contributorsState.snapshot.version).toEqual(newVersion);
}); });
function c(contributor: AppContributorDto) {
return { contributor, isCurrentUser: contributor.contributorId === 'id2' };
}
}); });

35
src/Squidex/app/shared/state/contributors.state.ts

@ -109,21 +109,24 @@ export class ContributorsState extends State<Snapshot> {
public assign(request: AppContributorDto): Observable<any> { public assign(request: AppContributorDto): Observable<any> {
return this.appContributorsService.postContributor(this.appName, request, this.version) return this.appContributorsService.postContributor(this.appName, request, this.version)
.do(dto => { .do(dto => {
const contributor = this.createContributor(new AppContributorDto(dto.payload.contributorId, request.permission)); const contributors = this.updateContributors(dto.payload.contributorId, request.permission, dto.version);
let contributors = this.snapshot.contributors;
if (contributors.find(x => x.contributor.contributorId === dto.payload.contributorId)) {
contributors = contributors.map(c => c.contributor.contributorId === dto.payload.contributorId ? contributor : c);
} else {
contributors = contributors.push(contributor);
}
this.replaceContributors(contributors, dto.version); this.replaceContributors(contributors, dto.version);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
private updateContributors(id: string, permission: string, version: Version) {
const contributor = new AppContributorDto(id, permission);
const contributors = this.snapshot.contributors;
if (contributors.find(x => x.contributor.contributorId === id)) {
return contributors.map(x => x.contributor.contributorId === id ? this.createContributor(contributor, x) : x);
} else {
return contributors.push(this.createContributor(contributor));
}
}
private replaceContributors(contributors: ImmutableArray<SnapshotContributor>, version: Version, maxContributors?: number) { private replaceContributors(contributors: ImmutableArray<SnapshotContributor>, version: Version, maxContributors?: number) {
this.next(s => { this.next(s => {
maxContributors = maxContributors || s.maxContributors; maxContributors = maxContributors || s.maxContributors;
@ -139,11 +142,21 @@ export class ContributorsState extends State<Snapshot> {
return this.appsState.appName; return this.appsState.appName;
} }
private get userId() {
return this.authState.user!.id;
}
private get version() { private get version() {
return this.snapshot.version; return this.snapshot.version;
} }
private createContributor(contributor: AppContributorDto): SnapshotContributor { private createContributor(contributor: AppContributorDto, current?: SnapshotContributor): SnapshotContributor {
return { contributor, isCurrentUser: contributor.contributorId === this.authState.user!.id }; if (!contributor) {
return null!;
} else if (current && current.contributor === contributor) {
return current;
} else {
return { contributor, isCurrentUser: contributor.contributorId === this.userId };
}
} }
} }

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

@ -0,0 +1,163 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Observable } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppLanguageDto,
AppLanguagesDto,
AppLanguagesService,
AppsState,
DialogService,
ImmutableArray,
LanguageDto,
LanguagesService,
LanguagesState,
UpdateAppLanguageDto,
Version,
Versioned
} from '@app/shared';
describe('LanguagesState', () => {
const app = 'my-app';
const version = new Version('1');
const newVersion = new Version('2');
const languageDE = new LanguageDto('de', 'German');
const languageEN = new LanguageDto('en', 'English');
const languageIT = new LanguageDto('it', 'Italian');
const languageES = new LanguageDto('es', 'Spanish');
const oldLanguages = [
new AppLanguageDto(languageEN.iso2Code, languageEN.englishName, true, false, []),
new AppLanguageDto(languageDE.iso2Code, languageDE.englishName, false, true, [languageEN.iso2Code])
];
let dialogs: IMock<DialogService>;
let appsState: IMock<AppsState>;
let allLanguagesService: IMock<LanguagesService>;
let languagesService: IMock<AppLanguagesService>;
let languagesState: LanguagesState;
beforeEach(() => {
dialogs = Mock.ofType<DialogService>();
appsState = Mock.ofType<AppsState>();
appsState.setup(x => x.appName)
.returns(() => app);
allLanguagesService = Mock.ofType<LanguagesService>();
allLanguagesService.setup(x => x.getLanguages())
.returns(() => Observable.of([languageDE, languageEN, languageIT, languageES]));
languagesService = Mock.ofType<AppLanguagesService>();
languagesService.setup(x => x.getLanguages(app))
.returns(() => Observable.of(new AppLanguagesDto(oldLanguages, version)));
languagesState = new LanguagesState(languagesService.object, appsState.object, dialogs.object, allLanguagesService.object);
languagesState.load().subscribe();
});
it('should load languages', () => {
expect(languagesState.snapshot.languages.values).toEqual([
{
language: oldLanguages[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([oldLanguages[1]])
}, {
language: oldLanguages[1],
fallbackLanguages: ImmutableArray.of([oldLanguages[0]]),
fallbackLanguagesNew: ImmutableArray.empty()
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageIT, languageES]);
expect(languagesState.snapshot.isLoaded).toBeTruthy();
expect(languagesState.snapshot.version).toEqual(version);
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when flag is true', () => {
languagesState.load(true).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
it('should add language to snapshot when assigned', () => {
const newLanguage = new AppLanguageDto(languageIT.iso2Code, languageIT.englishName, false, false, []);
languagesService.setup(x => x.postLanguage(app, It.isAny(), version))
.returns(() => Observable.of(new Versioned<AppLanguageDto>(newVersion, newLanguage)));
languagesState.add(languageIT).subscribe();
expect(languagesState.snapshot.languages.values).toEqual([
{
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]])
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
});
it('should update language in snapshot when updated', () => {
const request = new UpdateAppLanguageDto(true, false, []);
languagesService.setup(x => x.putLanguage(app, oldLanguages[1].iso2Code, request, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {})));
languagesState.update(oldLanguages[1], request).subscribe();
const newLanguage1 = new AppLanguageDto(languageDE.iso2Code, languageDE.englishName, true, false, []);
const newLanguage2 = new AppLanguageDto(languageEN.iso2Code, languageEN.englishName, false, false, []);
expect(languagesState.snapshot.languages.values).toEqual([
{
language: newLanguage1,
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([newLanguage2])
}, {
language: newLanguage2,
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.of([newLanguage1])
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageIT, languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
});
it('should remove language from snapshot when deleted', () => {
languagesService.setup(x => x.deleteLanguage(app, oldLanguages[1].iso2Code, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {})));
languagesState.remove(oldLanguages[1]).subscribe();
expect(languagesState.snapshot.languages.values).toEqual([
{
language: oldLanguages[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.empty()
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageDE, languageIT, languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
});
});

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

@ -0,0 +1,223 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import '@app/framework/utils/rxjs-extensions';
import {
DialogService,
Form,
ImmutableArray,
State,
Version
} from '@app/framework';
import { AddAppLanguageDto, AppLanguageDto, AppLanguagesService, UpdateAppLanguageDto } from './../services/app-languages.service';
import { LanguageDto, LanguagesService } from './../services/languages.service';
import { AppsState } from './apps.state';
export class EditLanguageForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
isMaster: false,
isOptional: false
}));
this.form.controls['isMaster'].valueChanges
.subscribe(value => {
if (value) {
this.form.controls['isOptional'].setValue(false);
}
});
this.form.controls['isOptional'].valueChanges
.subscribe(value => {
if (value) {
this.form.controls['isMaster'].setValue(false);
}
});
}
}
export class AddLanguageForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
language: [null,
[
Validators.required
]
]
}));
}
}
interface SnapshotLanguage {
language: AppLanguageDto;
fallbackLanguages: ImmutableArray<LanguageDto>;
fallbackLanguagesNew: ImmutableArray<LanguageDto>;
}
interface Snapshot {
plainLanguages: ImmutableArray<AppLanguageDto>;
allLanguages: ImmutableArray<LanguageDto>;
allLanguagesNew: ImmutableArray<LanguageDto>;
languages: ImmutableArray<SnapshotLanguage>;
isLoaded?: boolean;
version: Version;
}
@Injectable()
export class LanguagesState extends State<Snapshot> {
public languages =
this.changes.map(x => x.languages)
.distinctUntilChanged();
public newLanguages =
this.changes.map(x => x.allLanguagesNew)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appLanguagesService: AppLanguagesService,
private readonly appsState: AppsState,
private readonly dialogs: DialogService,
private readonly languagesService: LanguagesService
) {
super({
plainLanguages: ImmutableArray.empty(),
allLanguages: ImmutableArray.empty(),
allLanguagesNew: ImmutableArray.empty(),
languages: ImmutableArray.empty(),
version: new Version('')
});
}
public load(notifyLoad = false): Observable<any> {
return Observable.forkJoin(
this.languagesService.getLanguages(),
this.appLanguagesService.getLanguages(this.appName),
(allLanguages, languages) => ({ allLanguages, languages })
)
.do(dtos => {
if (notifyLoad) {
this.dialogs.notifyInfo('Languages reloaded.');
}
const sorted = ImmutableArray.of(dtos.allLanguages).sortByStringAsc(x => x.englishName);
this.replaceLanguages(ImmutableArray.of(dtos.languages.languages), dtos.languages.version, sorted);
})
.notify(this.dialogs);
}
public add(language: LanguageDto): Observable<any> {
return this.appLanguagesService.postLanguage(this.appName, new AddAppLanguageDto(language.iso2Code), this.version)
.do(dto => {
const languages = this.snapshot.plainLanguages.push(dto.payload).sortByStringAsc(x => x.englishName);
this.replaceLanguages(languages, dto.version);
})
.notify(this.dialogs);
}
public remove(language: AppLanguageDto): Observable<any> {
return this.appLanguagesService.deleteLanguage(this.appName, language.iso2Code, this.version)
.do(dto => {
const languages = this.snapshot.plainLanguages.filter(x => x.iso2Code !== language.iso2Code);
this.replaceLanguages(languages, dto.version);
})
.notify(this.dialogs);
}
public update(language: AppLanguageDto, request: UpdateAppLanguageDto): Observable<any> {
return this.appLanguagesService.putLanguage(this.appName, language.iso2Code, request, this.version)
.do(dto => {
const languages = this.snapshot.plainLanguages.map(l => {
if (l.iso2Code === language.iso2Code) {
return update(l, request.isMaster, request.isOptional, request.fallback);
} else if (l.isMaster && request.isMaster) {
return update(l, false, l.isOptional, l.fallback);
} else {
return l;
}
});
this.replaceLanguages(languages, dto.version);
})
.notify(this.dialogs);
}
private replaceLanguages(languages: ImmutableArray<AppLanguageDto>, version: Version, allLanguages?: ImmutableArray<LanguageDto>) {
this.next(s => {
allLanguages = allLanguages || s.allLanguages;
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,
allLanguages: allLanguages,
allLanguagesNew: allLanguages.filter(x => !languages.find(l => l.iso2Code === x.iso2Code)),
isLoaded: true,
version: version
};
});
}
private get appName() {
return this.appsState.appName;
}
private get version() {
return this.snapshot.version;
}
private createLanguage(language: AppLanguageDto, languages: ImmutableArray<AppLanguageDto>): SnapshotLanguage {
return {
language,
fallbackLanguages:
ImmutableArray.of(
language.fallback
.map(l => languages.find(x => x.iso2Code === l)).filter(x => !!x)
.map(x => <AppLanguageDto>x)),
fallbackLanguagesNew:
languages
.filter(l =>
language.iso2Code !== l.iso2Code &&
language.fallback.indexOf(l.iso2Code) < 0)
.sortByStringAsc(x => x.englishName)
};
}
}
const update = (language: AppLanguageDto, isMaster: boolean, isOptional: boolean, fallback: string[]) =>
new AppLanguageDto(
language.iso2Code,
language.englishName,
isMaster,
isOptional,
fallback);
Loading…
Cancel
Save