mirror of https://github.com/Squidex/squidex.git
20 changed files with 615 additions and 374 deletions
@ -1,8 +1,2 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.table-item-row-details { |
|||
&::before { |
|||
right: 5.2rem; |
|||
} |
|||
} |
|||
@ -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); |
|||
}); |
|||
}); |
|||
@ -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…
Reference in new issue