Browse Source

Get rid of immutable array.

pull/422/head
Sebastian 7 years ago
parent
commit
e753d7ae09
  1. 8
      src/Squidex/app/features/administration/state/event-consumers.state.spec.ts
  2. 9
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  3. 10
      src/Squidex/app/features/administration/state/users.state.spec.ts
  4. 10
      src/Squidex/app/features/administration/state/users.state.ts
  5. 2
      src/Squidex/app/features/content/pages/content/content-page.component.html
  6. 5
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  7. 2
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  8. 21
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  9. 2
      src/Squidex/app/features/content/shared/assets-editor.component.html
  10. 35
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  11. 4
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  12. 7
      src/Squidex/app/features/content/shared/preview-button.component.ts
  13. 3
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  14. 24
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
  15. 3
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  16. 8
      src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.ts
  17. 8
      src/Squidex/app/features/schemas/pages/schema/forms/field-form.component.ts
  18. 3
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  19. 5
      src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.ts
  20. 3
      src/Squidex/app/features/settings/pages/contributors/contributor.component.ts
  21. 3
      src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts
  22. 5
      src/Squidex/app/features/settings/pages/languages/language-add-form.component.ts
  23. 2
      src/Squidex/app/features/settings/pages/languages/language.component.html
  24. 21
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  25. 7
      src/Squidex/app/framework/angular/forms/checkbox-group.component.ts
  26. 3
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts
  27. 2
      src/Squidex/app/framework/internal.ts
  28. 73
      src/Squidex/app/framework/utils/array-extensions.ts
  29. 10
      src/Squidex/app/framework/utils/array-helper.ts
  30. 2
      src/Squidex/app/framework/utils/date-helper.spec.ts
  31. 2
      src/Squidex/app/framework/utils/date-helper.ts
  32. 2
      src/Squidex/app/framework/utils/date-time.ts
  33. 2
      src/Squidex/app/framework/utils/duration.ts
  34. 215
      src/Squidex/app/framework/utils/immutable-array.spec.ts
  35. 233
      src/Squidex/app/framework/utils/immutable-array.ts
  36. 18
      src/Squidex/app/shared/components/assets-list.component.ts
  37. 5
      src/Squidex/app/shared/components/schema-category.component.ts
  38. 4
      src/Squidex/app/shared/services/app-languages.service.spec.ts
  39. 6
      src/Squidex/app/shared/services/workflows.service.ts
  40. 14
      src/Squidex/app/shared/state/apps.state.spec.ts
  41. 11
      src/Squidex/app/shared/state/apps.state.ts
  42. 16
      src/Squidex/app/shared/state/asset-uploader.state.spec.ts
  43. 7
      src/Squidex/app/shared/state/asset-uploader.state.ts
  44. 8
      src/Squidex/app/shared/state/assets.state.spec.ts
  45. 14
      src/Squidex/app/shared/state/assets.state.ts
  46. 2
      src/Squidex/app/shared/state/backups.state.spec.ts
  47. 9
      src/Squidex/app/shared/state/backups.state.ts
  48. 4
      src/Squidex/app/shared/state/clients.state.spec.ts
  49. 9
      src/Squidex/app/shared/state/clients.state.ts
  50. 17
      src/Squidex/app/shared/state/comments.state.spec.ts
  51. 11
      src/Squidex/app/shared/state/comments.state.ts
  52. 7
      src/Squidex/app/shared/state/contents.forms.spec.ts
  53. 7
      src/Squidex/app/shared/state/contents.forms.ts
  54. 10
      src/Squidex/app/shared/state/contents.state.ts
  55. 12
      src/Squidex/app/shared/state/contributors.state.spec.ts
  56. 9
      src/Squidex/app/shared/state/contributors.state.ts
  57. 21
      src/Squidex/app/shared/state/languages.state.spec.ts
  58. 28
      src/Squidex/app/shared/state/languages.state.ts
  59. 4
      src/Squidex/app/shared/state/patterns.state.spec.ts
  60. 9
      src/Squidex/app/shared/state/patterns.state.ts
  61. 8
      src/Squidex/app/shared/state/plans.state.spec.ts
  62. 7
      src/Squidex/app/shared/state/plans.state.ts
  63. 4
      src/Squidex/app/shared/state/queries.ts
  64. 4
      src/Squidex/app/shared/state/roles.state.spec.ts
  65. 9
      src/Squidex/app/shared/state/roles.state.ts
  66. 2
      src/Squidex/app/shared/state/rule-events.state.spec.ts
  67. 8
      src/Squidex/app/shared/state/rule-events.state.ts
  68. 14
      src/Squidex/app/shared/state/rules.state.spec.ts
  69. 13
      src/Squidex/app/shared/state/rules.state.ts
  70. 2
      src/Squidex/app/shared/state/schema-tag-converter.ts
  71. 77
      src/Squidex/app/shared/state/schemas.state.spec.ts
  72. 19
      src/Squidex/app/shared/state/schemas.state.ts
  73. 4
      src/Squidex/app/shared/state/workflows.state.spec.ts
  74. 9
      src/Squidex/app/shared/state/workflows.state.ts

8
src/Squidex/app/features/administration/state/event-consumers.state.spec.ts

@ -42,7 +42,7 @@ describe('EventConsumersState', () => {
eventConsumersState.load().subscribe();
expect(eventConsumersState.snapshot.eventConsumers.values).toEqual([eventConsumer1, eventConsumer2]);
expect(eventConsumersState.snapshot.eventConsumers).toEqual([eventConsumer1, eventConsumer2]);
expect(eventConsumersState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
@ -87,7 +87,7 @@ describe('EventConsumersState', () => {
eventConsumersState.start(eventConsumer2).subscribe();
const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1);
const newConsumer2 = eventConsumersState.snapshot.eventConsumers[1];
expect(newConsumer2).toEqual(updated);
});
@ -100,7 +100,7 @@ describe('EventConsumersState', () => {
eventConsumersState.stop(eventConsumer2).subscribe();
const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1);
const newConsumer2 = eventConsumersState.snapshot.eventConsumers[1];
expect(newConsumer2).toEqual(updated);
});
@ -113,7 +113,7 @@ describe('EventConsumersState', () => {
eventConsumersState.reset(eventConsumer2).subscribe();
const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1);
const newConsumer2 = eventConsumersState.snapshot.eventConsumers[1];
expect(newConsumer2).toEqual(updated);
});

9
src/Squidex/app/features/administration/state/event-consumers.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State
} from '@app/shared';
@ -26,7 +25,7 @@ interface Snapshot {
isLoaded?: boolean;
}
type EventConsumersList = ImmutableArray<EventConsumerDto>;
type EventConsumersList = ReadonlyArray<EventConsumerDto>;
@Injectable()
export class EventConsumersState extends State<Snapshot> {
@ -40,7 +39,7 @@ export class EventConsumersState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly eventConsumersService: EventConsumersService
) {
super({ eventConsumers: ImmutableArray.empty() });
super({ eventConsumers: [] });
}
public load(isReload = false, silent = false): Observable<any> {
@ -49,13 +48,11 @@ export class EventConsumersState extends State<Snapshot> {
}
return this.eventConsumersService.getEventConsumers().pipe(
tap(({ items }) => {
tap(({ items: eventConsumers }) => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.');
}
const eventConsumers = ImmutableArray.of(items);
this.next(s => {
return { ...s, eventConsumers, isLoaded: true };
});

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

@ -50,7 +50,7 @@ describe('UsersState', () => {
usersState.load().subscribe();
expect(usersState.snapshot.users.values).toEqual([user1, user2]);
expect(usersState.snapshot.users).toEqual([user1, user2]);
expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200);
expect(usersState.snapshot.isLoaded).toBeTruthy();
@ -178,7 +178,7 @@ describe('UsersState', () => {
usersState.select(user2.id).subscribe();
usersState.lock(user2).subscribe();
const user2New = usersState.snapshot.users.at(1);
const user2New = usersState.snapshot.users[1];
expect(user2New).toBe(usersState.snapshot.selectedUser!);
});
@ -192,7 +192,7 @@ describe('UsersState', () => {
usersState.select(user2.id).subscribe();
usersState.unlock(user2).subscribe();
const user2New = usersState.snapshot.users.at(1);
const user2New = usersState.snapshot.users[1];
expect(user2New).toEqual(updated);
expect(user2New).toBe(usersState.snapshot.selectedUser!);
@ -209,7 +209,7 @@ describe('UsersState', () => {
usersState.select(user2.id).subscribe();
usersState.update(user2, request).subscribe();
const user2New = usersState.snapshot.users.at(1);
const user2New = usersState.snapshot.users[1];
expect(user2New).toEqual(updated);
expect(user2New).toBe(usersState.snapshot.selectedUser!);
@ -223,7 +223,7 @@ describe('UsersState', () => {
usersState.create(request).subscribe();
expect(usersState.snapshot.users.values).toEqual([newUser, user1, user2]);
expect(usersState.snapshot.users).toEqual([newUser, user1, user2]);
expect(usersState.snapshot.usersPager.numberOfItems).toBe(201);
});
});

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

@ -13,7 +13,6 @@ import '@app/framework/utils/rxjs-extensions';
import {
DialogService,
ImmutableArray,
Pager,
shareSubscribed,
State
@ -46,7 +45,7 @@ interface Snapshot {
canCreate?: boolean;
}
export type UsersList = ImmutableArray<UserDto>;
export type UsersList = ReadonlyArray<UserDto>;
export type UsersResult = { total: number, users: UsersList };
@Injectable()
@ -70,7 +69,7 @@ export class UsersState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly usersService: UsersService
) {
super({ users: ImmutableArray.empty(), usersPager: new Pager(0) });
super({ users: [], usersPager: new Pager(0) });
}
public select(id: string | null): Observable<UserDto | null> {
@ -110,14 +109,13 @@ export class UsersState extends State<Snapshot> {
this.snapshot.usersPager.pageSize,
this.snapshot.usersPager.skip,
this.snapshot.usersQuery).pipe(
tap(({ total, items, canCreate }) => {
tap(({ total, items: users, canCreate }) => {
if (isReload) {
this.dialogs.notifyInfo('Users reloaded.');
}
this.next(s => {
const usersPager = s.usersPager.setCount(total);
const users = ImmutableArray.of(items);
let selectedUser = s.selectedUser;
@ -141,7 +139,7 @@ export class UsersState extends State<Snapshot> {
return this.usersService.postUser(request).pipe(
tap(created => {
this.next(s => {
const users = s.users.pushFront(created);
const users = [created, ...s.users];
const usersPager = s.usersPager.incrementCount();
return { ...s, users, usersPager };

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

@ -112,7 +112,7 @@
[fieldForm]="contentForm.form.get(field.name)"
[fieldFormCompare]="contentFormCompare?.form.get(field.name)"
[schema]="schema"
[languages]="languages.mutableValues"
[languages]="languages"
[(language)]="language">
</sqx-content-field>
</ng-container>

5
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -25,7 +25,6 @@ import {
EditContentForm,
fadeAnimation,
FieldDto,
ImmutableArray,
LanguagesState,
MessageBus,
ModalModel,
@ -61,7 +60,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
public dropdown = new ModalModel();
public language: AppLanguageDto;
public languages: ImmutableArray<AppLanguageDto>;
public languages: ReadonlyArray<AppLanguageDto>;
public trackByFieldFn: Function;
@ -92,7 +91,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
this.languagesState.languages
.subscribe(languages => {
this.languages = languages.map(x => x.language);
this.language = this.languages.at(0);
this.language = this.languages[0];
}));
this.own(

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

@ -24,7 +24,7 @@
</sqx-search-form>
</div>
<div class="col-auto pl-1" *ngIf="languages.length > 1">
<sqx-language-selector class="languages-buttons" (selectedLanguageChange)="selectLanguage($event)" [languages]="languages.mutableValues"></sqx-language-selector>
<sqx-language-selector class="languages-buttons" (selectedLanguageChange)="selectLanguage($event)" [languages]="languages"></sqx-language-selector>
</div>
<div class="col-auto pl-1">
<button type="button" class="btn btn-success" #newButton routerLink="new" title="New Content (CTRL + SHIFT + G)" [disabled]="(contentsState.canCreateAny | async) === false">

21
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -12,7 +12,6 @@ import {
AppLanguageDto,
ContentDto,
ContentsState,
ImmutableArray,
LanguagesState,
ModalModel,
Queries,
@ -45,7 +44,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
public nextStatuses: ReadonlyArray<string> = [];
public language: AppLanguageDto;
public languages: ImmutableArray<AppLanguageDto>;
public languages: ReadonlyArray<AppLanguageDto>;
public queryModel: QueryModel;
public queries: Queries;
@ -96,7 +95,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
this.languagesState.languages
.subscribe(languages => {
this.languages = languages.map(x => x.language);
this.language = this.languages.at(0);
this.language = this.languages[0];
this.updateModel();
}));
@ -161,7 +160,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
}
private selectItems(predicate?: (content: ContentDto) => boolean) {
return this.contentsState.snapshot.contents.values.filter(c => this.selectedItems[c.id] && (!predicate || predicate(c)));
return this.contentsState.snapshot.contents.filter(c => this.selectedItems[c.id] && (!predicate || predicate(c)));
}
public selectItem(content: ContentDto, isSelected: boolean) {
@ -180,9 +179,9 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
this.selectedItems = {};
if (isSelected) {
this.contentsState.snapshot.contents.each(content => {
for (let content of this.contentsState.snapshot.contents) {
this.selectedItems[content.id] = true;
});
}
}
this.updateSelectionSummary();
@ -199,13 +198,13 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
const allActions = {};
this.contentsState.snapshot.contents.each(content => {
for (let content of this.contentsState.snapshot.contents) {
for (const info of content.statusUpdates) {
allActions[info.status] = info.color;
}
});
}
this.contentsState.snapshot.contents.each(content => {
for (let content of this.contentsState.snapshot.contents) {
if (this.selectedItems[content.id]) {
this.selectionCount++;
@ -221,7 +220,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
} else {
this.selectedAll = false;
}
});
}
this.nextStatuses = Object.keys(allActions);
}
@ -234,7 +233,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
private updateModel() {
if (this.schema && this.languages) {
this.queryModel = queryModelFromSchema(this.schema, this.languages.values, this.contentsState.snapshot.statuses);
this.queryModel = queryModelFromSchema(this.schema, this.languages, this.contentsState.snapshot.statuses);
}
}
}

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

@ -42,7 +42,7 @@
</sqx-asset>
<div
[sqxSortModel]="snapshot.assets.mutableValues"
[sqxSortModel]="snapshot.assets"
(sqxSort)="sortAssets($event)">
<div *ngFor="let asset of snapshot.assets; trackBy: trackByAsset">
<sqx-asset [asset]="asset" removeMode="true"

35
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -13,7 +13,6 @@ import {
AssetDto,
AssetsService,
DialogModel,
ImmutableArray,
LocalStoreService,
MessageBus,
StatefulControlComponent,
@ -33,9 +32,9 @@ class AssetUpdated {
}
interface State {
assetFiles: ImmutableArray<File>;
assetFiles: ReadonlyArray<File>;
assets: ImmutableArray<AssetDto>;
assets: ReadonlyArray<AssetDto>;
isListView: boolean;
}
@ -61,30 +60,30 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
private readonly messageBus: MessageBus
) {
super(changeDetector, {
assets: ImmutableArray.empty(),
assetFiles: ImmutableArray.empty(),
assets: [],
assetFiles: [],
isListView: localStore.getBoolean('squidex.assets.list-view')
});
}
public writeValue(obj: any) {
if (Types.isArrayOfString(obj)) {
if (!Types.isEquals(obj, this.snapshot.assets.map(x => x.id).values)) {
if (!Types.isEquals(obj, this.snapshot.assets.map(x => x.id))) {
const assetIds: string[] = obj;
this.assetsService.getAssets(this.appsState.appName, 0, 0, undefined, undefined, obj)
.subscribe(dtos => {
this.setAssets(ImmutableArray.of(assetIds.map(id => dtos.items.find(x => x.id === id)!).filter(a => !!a)));
this.setAssets(assetIds.map(id => dtos.items.find(x => x.id === id)!).filter(a => !!a));
if (this.snapshot.assets.length !== assetIds.length) {
this.updateValue();
}
}, () => {
this.setAssets(ImmutableArray.empty());
this.setAssets([]);
});
}
} else {
this.setAssets(ImmutableArray.empty());
this.setAssets([]);
}
}
@ -102,18 +101,18 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
}));
}
public setAssets(assets: ImmutableArray<AssetDto>) {
public setAssets(assets: ReadonlyArray<AssetDto>) {
this.next(s => ({ ...s, assets }));
}
public addFiles(files: ReadonlyArray<File>) {
for (const file of files) {
this.next(s => ({ ...s, assetFiles: s.assetFiles.pushFront(file) }));
this.next(s => ({ ...s, assetFiles: [file, ...s.assetFiles] }));
}
}
public selectAssets(assets: ReadonlyArray<AssetDto>) {
this.setAssets(this.snapshot.assets.push(...assets));
this.setAssets([...this.snapshot.assets, ...assets]);
if (assets.length > 0) {
this.updateValue();
@ -126,8 +125,8 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
if (asset && file) {
this.next(s => ({
...s,
assetFiles: s.assetFiles.remove(file),
assets: s.assets.pushFront(asset)
assetFiles: s.assetFiles.removed(file),
assets: [asset, ...s.assets]
}));
this.updateValue();
@ -136,7 +135,7 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
public sortAssets(assets: ReadonlyArray<AssetDto>) {
if (assets) {
this.setAssets(ImmutableArray.of(assets));
this.setAssets(assets);
this.updateValue();
}
@ -144,14 +143,14 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
public removeLoadedAsset(asset: AssetDto) {
if (asset) {
this.setAssets(this.snapshot.assets.remove(asset));
this.setAssets(this.snapshot.assets.removed(asset));
this.updateValue();
}
}
public removeLoadingAsset(file: File) {
this.next(s => ({ ...s, assetFiles: s.assetFiles.remove(file) }));
this.next(s => ({ ...s, assetFiles: s.assetFiles.removed(file) }));
}
public changeView(isListView: boolean) {
@ -161,7 +160,7 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, strin
}
private updateValue() {
const ids = this.snapshot.assets.values.map(x => x.id);
const ids = this.snapshot.assets.map(x => x.id);
if (ids.length === 0) {
this.callChange(null);

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

@ -74,7 +74,7 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
this.updateModel();
}));
this.schemas = this.schemasState.snapshot.schemas.values;
this.schemas = this.schemasState.snapshot.schemas;
if (this.schemaIds && this.schemaIds.length > 0) {
this.schemas = this.schemas.filter(x => this.schemaIds.indexOf(x.id) >= 0);
@ -147,7 +147,7 @@ export class ContentsSelectorComponent extends ResourceOwner implements OnInit {
this.selectedItems = {};
if (isSelected) {
for (const content of this.contentsState.snapshot.contents.values) {
for (const content of this.contentsState.snapshot.contents) {
if (!this.isItemAlreadySelected(content)) {
this.selectedItems[content.id] = content;
}

7
src/Squidex/app/features/content/shared/preview-button.component.ts

@ -20,8 +20,7 @@ import {
interface State {
selectedName?: string;
// tslint:disable-next-line: readonly-array
alternativeNames: string[];
alternativeNames: ReadonlyArray<string>;
}
@Component({
@ -78,9 +77,7 @@ export class PreviewButtonComponent extends StatefulComponent<State> implements
const keys = Object.keys(this.schema.previewUrls);
state.selectedName = selectedName;
state.alternativeNames = keys.filter(x => x !== s.selectedName);
state.alternativeNames.sort();
state.alternativeNames = keys.removed(s.selectedName).sorted();
this.localStore.set(this.configKey(), selectedName);

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

@ -10,7 +10,6 @@ import { FormGroup } from '@angular/forms';
import {
Form,
ImmutableArray,
RuleDto,
RuleElementDto,
RulesState,
@ -37,7 +36,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit {
public ruleTriggers: { [name: string]: RuleElementDto };
@Input()
public schemas: ImmutableArray<SchemaDto>;
public schemas: ReadonlyArray<SchemaDto>;
@Input()
public rule: RuleDto;

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

@ -8,11 +8,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import {
ImmutableArray,
SchemaDto,
Types
} from '@app/shared';
import { SchemaDto, Types } from '@app/shared';
export interface TriggerSchemaForm {
schema: SchemaDto;
@ -27,7 +23,7 @@ export interface TriggerSchemaForm {
})
export class ContentChangedTriggerComponent implements OnInit {
@Input()
public schemas: ImmutableArray<SchemaDto>;
public schemas: ReadonlyArray<SchemaDto>;
@Input()
public trigger: any;
@ -38,10 +34,10 @@ export class ContentChangedTriggerComponent implements OnInit {
@Input()
public triggerFormSubmitted = false;
public triggerSchemas: ImmutableArray<TriggerSchemaForm>;
public triggerSchemas: ReadonlyArray<TriggerSchemaForm>;
public schemaToAdd: SchemaDto;
public schemasToAdd: ImmutableArray<SchemaDto>;
public schemasToAdd: ReadonlyArray<SchemaDto>;
public get hasSchema() {
return !!this.schemaToAdd;
@ -68,20 +64,20 @@ export class ContentChangedTriggerComponent implements OnInit {
}
}
this.triggerSchemas = ImmutableArray.of(schemas).sortByStringAsc(s => s.schema.name);
this.triggerSchemas = schemas.sortedByString(s => s.schema.name);
this.updateSchemaToAdd();
}
public removeSchema(schemaForm: TriggerSchemaForm) {
this.triggerSchemas = this.triggerSchemas.remove(schemaForm);
this.triggerSchemas = this.triggerSchemas.removed(schemaForm);
this.updateValue();
this.updateSchemaToAdd();
}
public addSchema() {
this.triggerSchemas = this.triggerSchemas.push({ schema: this.schemaToAdd }).sortByStringAsc(x => x.schema.name);
this.triggerSchemas = [{ schema: this.schemaToAdd }, ...this.triggerSchemas].sortedByString(x => x.schema.name);
this.updateValue();
this.updateSchemaToAdd();
@ -94,14 +90,14 @@ export class ContentChangedTriggerComponent implements OnInit {
}
public updateValue() {
const schemas = this.triggerSchemas.values.map(s => ({ schemaId: s.schema.id, condition: s.condition }));
const schemas = this.triggerSchemas.map(s => ({ schemaId: s.schema.id, condition: s.condition }));
this.triggerForm.controls['schemas'].setValue(schemas);
}
private updateSchemaToAdd() {
this.schemasToAdd = this.schemas.filter(schema => !this.triggerSchemas.find(s => s.schema.id === schema.id)).sortByStringAsc(x => x.name);
this.schemaToAdd = this.schemasToAdd.at(0);
this.schemasToAdd = this.schemas.filter(schema => !this.triggerSchemas.find(s => s.schema.id === schema.id)).sortedByString(x => x.name);
this.schemaToAdd = this.schemasToAdd[0];
}
public trackBySchema(index: number, schema: SchemaDto) {

3
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -14,7 +14,6 @@ import {
DialogService,
EditFieldForm,
fadeAnimation,
ImmutableArray,
ModalModel,
NestedFieldDto,
PatternDto,
@ -42,7 +41,7 @@ export class FieldComponent implements OnChanges {
public parent: RootFieldDto;
@Input()
public patterns: ImmutableArray<PatternDto>;
public patterns: ReadonlyArray<PatternDto>;
public dropdown = new ModalModel();

8
src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.ts

@ -8,11 +8,7 @@
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
FieldDto,
ImmutableArray,
PatternDto
} from '@app/shared';
import { FieldDto, PatternDto } from '@app/shared';
@Component({
selector: 'sqx-field-form-validation',
@ -55,5 +51,5 @@ export class FieldFormValidationComponent {
public field: FieldDto;
@Input()
public patterns: ImmutableArray<PatternDto>;
public patterns: ReadonlyArray<PatternDto>;
}

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

@ -8,11 +8,7 @@
import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
FieldDto,
ImmutableArray,
PatternDto
} from '@app/shared';
import { FieldDto, PatternDto } from '@app/shared';
@Component({
selector: 'sqx-field-form',
@ -63,7 +59,7 @@ export class FieldFormComponent implements AfterViewInit {
public editFormSubmitted: boolean;
@Input()
public patterns: ImmutableArray<PatternDto>;
public patterns: ReadonlyArray<PatternDto>;
@Input()
public field: FieldDto;

3
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -13,7 +13,6 @@ import {
fadeAnimation,
FieldDto,
hasNoValue$,
ImmutableArray,
ModalModel,
PatternDto,
ResourceOwner,
@ -41,7 +40,7 @@ export class StringValidationComponent extends ResourceOwner implements OnInit {
public properties: StringFieldPropertiesDto;
@Input()
public patterns: ImmutableArray<PatternDto>;
public patterns: ReadonlyArray<PatternDto>;
public showDefaultValue: Observable<boolean>;
public showPatternMessage: boolean;

5
src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.ts

@ -16,7 +16,6 @@ import {
ContributorsState,
DialogModel,
DialogService,
ImmutableArray,
RoleDto,
UsersService
} from '@app/shared';
@ -56,7 +55,7 @@ export class ContributorAddFormComponent implements OnInit {
private defaultValue: any;
@Input()
public roles: ImmutableArray<RoleDto>;
public roles: ReadonlyArray<RoleDto>;
public assignContributorForm = new AssignContributorForm(this.formBuilder);
@ -71,7 +70,7 @@ export class ContributorAddFormComponent implements OnInit {
}
public ngOnInit() {
this.defaultValue = { role: this.roles.at(0).name, contributorId: '' };
this.defaultValue = { role: this.roles[0].name, contributorId: '' };
this.assignContributorForm.submitCompleted({ newValue: this.defaultValue });
}

3
src/Squidex/app/features/settings/pages/contributors/contributor.component.ts

@ -12,7 +12,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import {
ContributorDto,
ContributorsState,
ImmutableArray,
RoleDto
} from '@app/shared';
@ -45,7 +44,7 @@ import {
})
export class ContributorComponent {
@Input()
public roles: ImmutableArray<RoleDto>;
public roles: ReadonlyArray<RoleDto>;
@Input()
public search: string;

3
src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts

@ -13,7 +13,6 @@ import { catchError, mergeMap, tap } from 'rxjs/operators';
import {
ContributorsState,
ErrorDto,
ImmutableArray,
ImportContributorsForm,
RoleDto
} from '@app/shared';
@ -37,7 +36,7 @@ export class ImportContributorsDialogComponent {
public close = new EventEmitter();
@Input()
public roles: ImmutableArray<RoleDto>;
public roles: ReadonlyArray<RoleDto>;
public importForm = new ImportContributorsForm(this.formBuilder);
public importStatus: ReadonlyArray<ImportStatus> = [];

5
src/Squidex/app/features/settings/pages/languages/language-add-form.component.ts

@ -10,7 +10,6 @@ import { FormBuilder } from '@angular/forms';
import {
AddLanguageForm,
ImmutableArray,
LanguageDto,
LanguagesState
} from '@app/shared';
@ -36,7 +35,7 @@ import {
})
export class LanguageAddFormComponent implements OnChanges {
@Input()
public newLanguages: ImmutableArray<LanguageDto>;
public newLanguages: ReadonlyArray<LanguageDto>;
public addLanguageForm = new AddLanguageForm(this.formBuilder);
@ -48,7 +47,7 @@ export class LanguageAddFormComponent implements OnChanges {
public ngOnChanges() {
if (this.newLanguages.length > 0) {
const language = this.newLanguages.at(0);
const language = this.newLanguages[0];
this.addLanguageForm.load({ language });
}

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

@ -40,7 +40,7 @@
<div class="col-9">
<div class="fallback-languages"
[sqxSortModel]="fallbackLanguages.mutableValues"
[sqxSortModel]="fallbackLanguages"
[sqxSortDisabled]="!isEditable"
*ngIf="fallbackLanguages.length > 0">
<div class="fallback-language" *ngFor="let language of fallbackLanguages">

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

@ -12,7 +12,6 @@ import {
AppLanguageDto,
EditLanguageForm,
fadeAnimation,
ImmutableArray,
LanguagesState
} from '@app/shared';
@ -29,10 +28,10 @@ export class LanguageComponent implements OnChanges {
public language: AppLanguageDto;
@Input()
public fallbackLanguages: ImmutableArray<AppLanguageDto>;
public fallbackLanguages: ReadonlyArray<AppLanguageDto>;
@Input()
public fallbackLanguagesNew: ImmutableArray<AppLanguageDto>;
public fallbackLanguagesNew: ReadonlyArray<AppLanguageDto>;
public otherLanguage: AppLanguageDto;
@ -53,7 +52,7 @@ export class LanguageComponent implements OnChanges {
this.editForm.load(this.language);
this.editForm.setEnabled(this.isEditable);
this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.otherLanguage = this.fallbackLanguagesNew[0];
}
public toggleEditing() {
@ -72,7 +71,7 @@ export class LanguageComponent implements OnChanges {
const value = this.editForm.submit();
if (value) {
const request = { ...value, fallback: this.fallbackLanguages.map(x => x.iso2Code).values };
const request = { ...value, fallback: this.fallbackLanguages.map(x => x.iso2Code) };
this.languagesState.update(this.language, request)
.subscribe(() => {
@ -86,17 +85,17 @@ export class LanguageComponent implements OnChanges {
}
public removeFallbackLanguage(language: AppLanguageDto) {
this.fallbackLanguages = this.fallbackLanguages.remove(language);
this.fallbackLanguagesNew = this.fallbackLanguagesNew.push(language).sortByStringAsc(x => x.iso2Code);
this.fallbackLanguages = this.fallbackLanguages.removed(language);
this.fallbackLanguagesNew = [...this.fallbackLanguagesNew, language].sortedByString(x => x.iso2Code);
this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.otherLanguage = this.fallbackLanguagesNew[0];
}
public addFallbackLanguage() {
this.fallbackLanguages = this.fallbackLanguages.push(this.otherLanguage);
this.fallbackLanguagesNew = this.fallbackLanguagesNew.remove(this.otherLanguage);
this.fallbackLanguages = [...this.fallbackLanguages, this.otherLanguage].sortedByString(x => x.iso2Code);
this.fallbackLanguagesNew = this.fallbackLanguagesNew.removed(this.otherLanguage);
this.otherLanguage = this.fallbackLanguagesNew.at(0);
this.otherLanguage = this.fallbackLanguagesNew[0];
}
public trackByLanguage(index: number, language: AppLanguageDto) {

7
src/Squidex/app/framework/angular/forms/checkbox-group.component.ts

@ -19,8 +19,7 @@ export const SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR: any = {
};
interface State {
// tslint:disable-next-line: readonly-array
checkedValues: string[];
checkedValues: ReadonlyArray<string>;
}
@Component({
@ -55,12 +54,12 @@ export class CheckboxGroupComponent extends StatefulControlComponent<State, stri
if (isChecked) {
checkedValues = [value, ...checkedValues];
} else {
checkedValues = checkedValues.filter(x => x !== value);
checkedValues = checkedValues.removed(value);
}
this.next(s => ({ ...s, checkedValues }));
this.callChange(checkedValues);
this.callChange([...checkedValues]);
}
public isChecked(value: string) {

3
src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts

@ -21,8 +21,7 @@ import {
interface State {
dialogRequest?: DialogRequest | null;
// tslint:disable-next-line: readonly-array
notifications: Notification[];
notifications: ReadonlyArray<Notification>;
tooltip?: Tooltip | null;
}

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

@ -19,13 +19,13 @@ export * from './services/resource-loader.service';
export * from './services/shortcut.service';
export * from './services/title.service';
export * from './utils/array-helper';
export * from './utils/date-helper';
export * from './utils/date-time';
export * from './utils/duration';
export * from './utils/error';
export * from './utils/hateos';
export * from './utils/interpolator';
export * from './utils/immutable-array';
export * from './utils/keys';
export * from './utils/math-helper';
export * from './utils/modal-positioner';

73
src/Squidex/app/framework/utils/array-extensions.ts

@ -0,0 +1,73 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: readonly-array
interface ReadonlyArray<T> {
replaceBy(field: string, value: T): ReadonlyArray<T>;
removeBy(field: string, value: T): ReadonlyArray<T>;
removed(value?: T): ReadonlyArray<T>;
sorted(): ReadonlyArray<T>;
sortedByString(selector: (value: T) => string): ReadonlyArray<T>;
}
interface Array<T> {
replaceBy(field: string, value: T): Array<T>;
removeBy(field: string, value: T): Array<T>;
removed(value?: T): Array<T>;
sorted(): Array<T>;
sortedByString(selector: (value: T) => string): Array<T>;
}
Array.prototype.replaceBy = function<T>(field: string, value: T) {
if (!value) {
return this;
}
return this.map((v: T) => v[field] === value[field] ? value : v);
};
Array.prototype.removeBy = function<T>(field: string, value: T) {
if (!value) {
return this;
}
return this.filter((v: T) => v[field] !== value[field]);
};
Array.prototype.removed = function<T>(value?: T) {
if (!value) {
return this;
}
return this.filter((v: T) => v !== value);
};
Array.prototype.sorted = function<T>() {
const copy = [...this];
copy.sort();
return copy;
};
Array.prototype.sortedByString = function<T>(selector: (value: T) => string) {
const copy = [...this];
copy.sort((a, b) => selector(a).localeCompare(selector(b), undefined, { sensitivity: 'base' }));
return copy;
};

10
src/Squidex/app/framework/utils/array-helper.ts

@ -0,0 +1,10 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export function compareStrings(a: string, b: string) {
return a.localeCompare(b, undefined, { sensitivity: 'base' });
}

2
src/Squidex/app/framework/utils/date-helper.spec.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import * as moment from 'moment';
import moment from 'moment';
import { DateHelper } from './date-helper';

2
src/Squidex/app/framework/utils/date-helper.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import * as moment from 'moment';
import moment from 'moment';
export module DateHelper {
let momentInstance: any;

2
src/Squidex/app/framework/utils/date-time.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import * as moment from 'moment';
import moment from 'moment';
export class DateTime {
public get raw(): Date {

2
src/Squidex/app/framework/utils/duration.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import * as moment from 'moment';
import moment from 'moment';
import { DateTime } from './date-time';

215
src/Squidex/app/framework/utils/immutable-array.spec.ts

@ -1,215 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ImmutableArray } from './immutable-array';
describe('ImmutableArray', () => {
it('should create empty instance', () => {
const array_1 = ImmutableArray.of();
expect(array_1.length).toBe(0);
});
it('should create same instance for empty arrays', () => {
const array_a = ImmutableArray.of();
const array_b = ImmutableArray.of();
const array_c = ImmutableArray.of([]);
const array_d = ImmutableArray.empty();
expect(array_b).toBe(<any>array_a);
expect(array_c).toBe(<any>array_a);
expect(array_d).toBe(<any>array_a);
});
it('should create non empty instance', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
expect(array_1.length).toBe(3);
expect(array_1.values).toEqual([1, 2, 3]);
});
it('should push items', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.push(4, 5);
expect(array_2.length).toBe(5);
expect(array_2.values).toEqual([1, 2, 3, 4, 5]);
});
it('should return same array if pushing zero items', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.push();
expect(array_2).toBe(array_1);
});
it('should push front items', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.pushFront(4, 5);
expect(array_2.length).toBe(5);
expect(array_2.values).toEqual([4, 5, 1, 2, 3]);
});
it('should return same array if pushing zero items to the front', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.pushFront();
expect(array_2).toBe(array_1);
});
it('should remove item', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.remove(2);
expect(array_2.length).toBe(2);
expect(array_2.values).toEqual([1, 3]);
});
it('should return same array if removing zero items', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.remove();
expect(array_2).toBe(array_1);
});
it('should remove all by predicate', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.removeAll((i: number) => i % 2 === 0);
expect(array_2.values).toEqual([1, 3]);
});
it('should return original if nothing has been removed', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.removeAll((i: number) => i % 200 === 0);
expect(array_2).toEqual(array_1);
});
it('should replace item if found', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.replace(2, 4);
expect(array_2.values).toEqual([1, 4, 3]);
});
it('should not replace item if not found', () => {
const array_1 = ImmutableArray.of([1, 2, 3]);
const array_2 = array_1.replace(5, 5);
expect(array_2).toBe(array_1);
});
it('should replace all by predicate', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const array_2 = array_1.replaceAll((i: number) => i % 2 === 0, i => i * 2);
expect(array_2.values).toEqual([1, 4, 3, 8]);
});
it('should replace by field', () => {
const array_1 = ImmutableArray.of([{ id: 1, v: 1 }, { id: 2, v: 2 }]);
const array_2 = array_1.replaceBy('id', { id: 1, v: 11 });
expect(array_2.values).toEqual([{ id: 1, v: 11 }, { id: 2, v: 2 }]);
});
it('should return original if nothing has been replace', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const array_2 = array_1.replaceAll((i: number) => i % 200 === 0, i => i);
expect(array_2).toBe(array_1);
});
it('should filter items', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const array_2 = array_1.filter((i: number) => i % 2 === 0);
expect(array_2.values).toEqual([2, 4]);
});
it('should map items', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const array_2 = array_1.map((i: number) => i * 2);
expect(array_2.values).toEqual([2, 4, 6, 8]);
});
it('should find item', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const result = array_1.find(i => i >= 2.5);
expect(result).toEqual(3);
});
it('should not return item if not found', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const result = array_1.find(i => i >= 4.5);
expect(result).toBeUndefined();
});
it('should sort items', () => {
const array_1 = ImmutableArray.of([3, 1, 4, 2]);
const array_2 = array_1.sort((x, y) => x - y);
expect(array_2.values).toEqual([1, 2, 3, 4]);
});
it('should sort ascending by numbers', () => {
const array_1 = ImmutableArray.of([{ id: 3 }, { id: 2 }, { id: 1 }]);
const array_2 = array_1.sortByNumberAsc(x => x.id);
expect(array_2.values).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
});
it('should sort descending by numbers', () => {
const array_1 = ImmutableArray.of([{ id: 1 }, { id: 2 }, { id: 3 }]);
const array_2 = array_1.sortByNumberDesc(x => x.id);
expect(array_2.values).toEqual([{ id: 3 }, { id: 2 }, { id: 1 }]);
});
it('should sort ascending by string', () => {
const array_1 = ImmutableArray.of([{ id: '3' }, { id: '2' }, { id: '1' }]);
const array_2 = array_1.sortByStringAsc(x => x.id);
expect(array_2.values).toEqual([{ id: '1' }, { id: '2' }, { id: '3' }]);
});
it('should sort descending by string', () => {
const array_1 = ImmutableArray.of([{ id: '1' }, { id: '2' }, { id: '3' }]);
const array_2 = array_1.sortByStringDesc(x => x.id);
expect(array_2.values).toEqual([{ id: '3' }, { id: '2' }, { id: '1' }]);
});
it('should provide mutable values', () => {
const array_1 = ImmutableArray.of([3, 1, 4, 2]);
expect(array_1.mutableValues).toBe(array_1.mutableValues);
});
it('should provider value at index', () => {
const array_1 = ImmutableArray.of([3, 1, 4, 2]);
expect(array_1.at(2)).toBe(4);
});
it('should iterate over array items', () => {
const array_1 = ImmutableArray.of([3, 1, 4, 2]);
const values: number[] = [];
for (let iter = array_1[Symbol.iterator](), _step: any; !(_step = iter.next()).done; ) {
values.push(_step.value);
}
expect(values).toEqual([3, 1, 4, 2]);
});
});

233
src/Squidex/app/framework/utils/immutable-array.ts

@ -1,233 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export interface IdField {
id: string;
}
function freeze<T>(items: ReadonlyArray<T>): ReadonlyArray<T> {
for (const item of items) {
Object.freeze(item);
}
return items;
}
export class ImmutableArray<T> implements Iterable<T> {
private static readonly EMPTY = new ImmutableArray<any>([]);
private readonly items: ReadonlyArray<T>;
public [Symbol.iterator](): Iterator<T> {
return this.items.values();
}
public get length(): number {
return this.items.length;
}
public get values(): ReadonlyArray<T> {
return [...this.items];
}
public get mutableValues(): ReadonlyArray<T> {
return this.items;
}
private constructor(items: ReadonlyArray<T>) {
this.items = items;
}
public static empty<V>(): ImmutableArray<V> {
return ImmutableArray.EMPTY;
}
public static of<V>(items?: ReadonlyArray<V>): ImmutableArray<V> {
if (!items || items.length === 0) {
return ImmutableArray.EMPTY;
} else {
return new ImmutableArray<V>(freeze([...items]));
}
}
public at(index: number) {
return this.items[index];
}
public each(action: (item: T, index?: number) => void) {
for (let i = 0; i < this.items.length; i++) {
action(this.items[i], i);
}
}
public map<R>(projection: (item: T) => R): ImmutableArray<R> {
return new ImmutableArray<R>(freeze(this.items.map(v => projection(v!))));
}
public filter(predicate: (item: T) => boolean): ImmutableArray<T> {
return new ImmutableArray<T>(this.items.filter(v => predicate(v!)));
}
public find(predicate: (item: T, index: number) => boolean): T | undefined {
return this.items.find(predicate);
}
public slice(start?: number, end?: number) {
return new ImmutableArray<T>(this.items.slice(start, end));
}
public sort(compareFn?: (a: T, b: T) => number): ImmutableArray<T> {
const clone = [...this.items];
clone.sort(compareFn);
return new ImmutableArray<T>(clone);
}
public sortByStringAsc(filter: (a: T) => string): ImmutableArray<T> {
return this.sort((a, b) => compareStringsAsc(filter(a), filter(b)));
}
public sortByStringDesc(filter: (a: T) => string): ImmutableArray<T> {
return this.sort((a, b) => compareStringsDesc(filter(a), filter(b)));
}
public sortByNumberAsc(filter: (a: T) => number): ImmutableArray<T> {
return this.sort((a, b) => compareNumbersAsc(filter(a), filter(b)));
}
public sortByNumberDesc(filter: (a: T) => number): ImmutableArray<T> {
return this.sort((a, b) => compareNumbersDesc(filter(a), filter(b)));
}
public pushFront(...items: ReadonlyArray<T>): ImmutableArray<T> {
if (items.length === 0) {
return this;
}
return new ImmutableArray<T>([...freeze(items), ...this.items]);
}
public push(...items: ReadonlyArray<T>): ImmutableArray<T> {
if (items.length === 0) {
return this;
}
return new ImmutableArray<T>([...this.items, ...freeze(items)]);
}
public remove(...items: ReadonlyArray<T>): ImmutableArray<T> {
if (items.length === 0) {
return this;
}
const copy = this.items.slice();
for (const item of items) {
const index = copy.indexOf(item);
if (index >= 0) {
copy.splice(index, 1);
}
}
return new ImmutableArray<T>(copy);
}
public removeAll(predicate: (item: T, index: number) => boolean): ImmutableArray<T> {
const copy = this.items.slice();
let hasChange = false;
for (let i = 0; i < copy.length; ) {
if (predicate(copy[i], i)) {
copy.splice(i, 1);
hasChange = true;
} else {
++i;
}
}
return hasChange ? new ImmutableArray<T>(copy) : this;
}
public replace(oldItem: T, newItem: T): ImmutableArray<T> {
const index = this.items.indexOf(oldItem);
if (index >= 0) {
if (newItem) {
Object.freeze(newItem);
}
const copy = [...this.items.slice(0, index), newItem, ...this.items.slice(index + 1)];
return new ImmutableArray<T>(copy);
} else {
return this;
}
}
public replaceAll(predicate: (item: T, index: number) => boolean, replacer: (item: T) => T): ImmutableArray<T> {
const copy = this.items.slice();
let hasChange = false;
for (let i = 0; i < copy.length; i++) {
if (predicate(copy[i], i)) {
const newItem = replacer(copy[i]);
if (newItem) {
Object.freeze(newItem);
}
if (copy[i] !== newItem) {
copy[i] = newItem;
hasChange = true;
}
}
}
return hasChange ? new ImmutableArray<T>(copy) : this;
}
public replaceBy(field: string, newValue: T, replacer?: (o: T, n: T) => T) {
return this.replaceAll(x => x[field] === newValue[field], o => replacer ? replacer(o, newValue) : newValue);
}
public removeBy(field: string, value: T) {
return this.removeAll(x => x[field] === value[field]);
}
}
export function compareStringsAsc(a: string, b: string) {
return a.localeCompare(b, undefined, { sensitivity: 'base' });
}
export function compareStringsDesc(a: string, b: string) {
return b.localeCompare(a, undefined, { sensitivity: 'base' });
}
export function compareNumbersAsc(a: number, b: number) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
export function compareNumbersDesc(a: number, b: number) {
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
}

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

@ -8,11 +8,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AssetDto,
AssetsState,
ImmutableArray
} from '@app/shared/internal';
import { AssetDto, AssetsState } from '@app/shared/internal';
@Component({
selector: 'sqx-assets-list',
@ -36,7 +32,7 @@ export class AssetsListComponent {
@Input()
public selectedIds: object;
public newFiles = ImmutableArray.empty<File>();
public newFiles: ReadonlyArray<File> = [];
constructor(
private readonly changeDetector: ChangeDetectorRef
@ -46,12 +42,12 @@ export class AssetsListComponent {
public add(file: File, asset: AssetDto) {
if (asset.isDuplicate) {
setTimeout(() => {
this.newFiles = this.newFiles.remove(file);
this.newFiles = this.newFiles.removed(file);
this.changeDetector.detectChanges();
}, 2000);
} else {
this.newFiles = this.newFiles.remove(file);
this.newFiles = this.newFiles.removed(file);
this.state.add(asset);
}
@ -86,13 +82,11 @@ export class AssetsListComponent {
}
public remove(file: File) {
this.newFiles = this.newFiles.remove(file);
this.newFiles = this.newFiles.removed(file);
}
public addFiles(files: ReadonlyArray<File>) {
for (const file of files) {
this.newFiles = this.newFiles.pushFront(file);
}
this.newFiles = [...files, ...this.newFiles];
return true;
}

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

@ -9,7 +9,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, In
import {
fadeAnimation,
ImmutableArray,
isSameCategory,
LocalStoreService,
SchemaCategory,
@ -21,7 +20,7 @@ import {
} from '@app/shared/internal';
interface State {
filtered: ImmutableArray<SchemaDto>;
filtered: ReadonlyArray<SchemaDto>;
isOpen?: boolean;
}
@ -56,7 +55,7 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
private readonly localStore: LocalStoreService,
private readonly schemasState: SchemasState
) {
super(changeDetector, { filtered: ImmutableArray.empty(), isOpen: true });
super(changeDetector, { filtered: [], isOpen: true });
}
public ngOnInit() {

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

@ -155,7 +155,7 @@ describe('AppLanguagesService', () => {
englishName: code,
isMaster: i === 0,
isOptional: i % 2 === 1,
fallback: codes.filter(x => x !== code),
fallback: codes.removed(code),
_links: {
update: { method: 'PUT', href: `/languages/${code}` }
}
@ -181,5 +181,5 @@ function createLanguage(code: string, codes: ReadonlyArray<string>, i: number) {
update: { method: 'PUT', href: `/languages/${code}` }
};
return new AppLanguageDto(links, code, code, i === 0, i % 2 === 1, codes.filter(x => x !== code));
return new AppLanguageDto(links, code, code, i === 0, i % 2 === 1, codes.removed(code));
}

6
src/Squidex/app/shared/services/workflows.service.ts

@ -15,7 +15,7 @@ import { tap } from 'rxjs/operators';
import {
AnalyticsService,
ApiUrlConfig,
compareStringsAsc,
compareStrings,
hasAnyLink,
HTTP,
mapVersioned,
@ -67,9 +67,9 @@ export class WorkflowDto extends Model<WorkflowDto> {
}
protected onCloned() {
this.steps.sort((a, b) => compareStringsAsc(a.name, b.name));
this.steps.sort((a, b) => compareStrings(a.name, b.name));
this.transitions.sort((a, b) => compareStringsAsc(a.to, b.to));
this.transitions.sort((a, b) => compareStrings(a.to, b.to));
}
public getOpenSteps(step: WorkflowStep) {

14
src/Squidex/app/shared/state/apps.state.spec.ts

@ -42,7 +42,7 @@ describe('AppsState', () => {
});
it('should load apps', () => {
expect(appsState.snapshot.apps.values).toEqual([app1, app2]);
expect(appsState.snapshot.apps).toEqual([app1, app2]);
});
it('should select app', () => {
@ -88,7 +88,7 @@ describe('AppsState', () => {
appsState.create(request).subscribe();
expect(appsState.snapshot.apps.values).toEqual([app1, app2, updated]);
expect(appsState.snapshot.apps).toEqual([app1, app2, updated]);
});
it('should update app in snapshot when updated', () => {
@ -99,7 +99,7 @@ describe('AppsState', () => {
appsState.update(app2, {}).subscribe();
expect(appsState.snapshot.apps.values).toEqual([app1, updated]);
expect(appsState.snapshot.apps).toEqual([app1, updated]);
});
it('should update selected app in snapshot when updated', () => {
@ -111,7 +111,7 @@ describe('AppsState', () => {
appsState.select(app1.name).subscribe();
appsState.update(app1, {}).subscribe();
expect(appsState.snapshot.apps.values).toEqual([updated, app2]);
expect(appsState.snapshot.apps).toEqual([updated, app2]);
expect(appsState.snapshot.selectedApp).toEqual(updated);
});
@ -125,7 +125,7 @@ describe('AppsState', () => {
appsState.uploadImage(app2, file).subscribe();
expect(appsState.snapshot.apps.values).toEqual([app1, updated]);
expect(appsState.snapshot.apps).toEqual([app1, updated]);
});
it('should update app in snapshot when image removed', () => {
@ -136,7 +136,7 @@ describe('AppsState', () => {
appsState.removeImage(app2).subscribe();
expect(appsState.snapshot.apps.values).toEqual([app1, updated]);
expect(appsState.snapshot.apps).toEqual([app1, updated]);
});
it('should remove app from snapshot when archived', () => {
@ -145,7 +145,7 @@ describe('AppsState', () => {
appsState.delete(app2).subscribe();
expect(appsState.snapshot.apps.values).toEqual([app1]);
expect(appsState.snapshot.apps).toEqual([app1]);
});
it('should remove selected app from snapshot when archived', () => {

11
src/Squidex/app/shared/state/apps.state.ts

@ -12,7 +12,6 @@ import { tap } from 'rxjs/operators';
import {
defined,
DialogService,
ImmutableArray,
shareSubscribed,
State,
Types
@ -27,7 +26,7 @@ import {
interface Snapshot {
// All apps, loaded once.
apps: ImmutableArray<AppDto>;
apps: ReadonlyArray<AppDto>;
// The selected app.
selectedApp: AppDto | null;
@ -56,7 +55,7 @@ export class AppsState extends State<Snapshot> {
private readonly appsService: AppsService,
private readonly dialogs: DialogService
) {
super({ apps: ImmutableArray.empty(), selectedApp: null });
super({ apps: [], selectedApp: null });
}
public select(name: string | null): Observable<AppDto | null> {
@ -73,10 +72,8 @@ export class AppsState extends State<Snapshot> {
public load(): Observable<any> {
return this.appsService.getApps().pipe(
tap(payload => {
tap(apps => {
this.next(s => {
const apps = ImmutableArray.of(payload);
return { ...s, apps };
});
}),
@ -87,7 +84,7 @@ export class AppsState extends State<Snapshot> {
return this.appsService.postApp(request).pipe(
tap(created => {
this.next(s => {
const apps = s.apps.push(created).sortByStringAsc(x => x.displayName);
const apps = [...s.apps, created].sortedByString(x => x.displayName);
return { ...s, apps };
});

16
src/Squidex/app/shared/state/asset-uploader.state.spec.ts

@ -53,7 +53,7 @@ describe('AssetUploaderState', () => {
assetUploader.uploadFile(file).subscribe();
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Running');
expect(upload.progress).toBe(1);
@ -67,7 +67,7 @@ describe('AssetUploaderState', () => {
assetUploader.uploadFile(file).subscribe();
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Running');
expect(upload.progress).toBe(20);
@ -81,7 +81,7 @@ describe('AssetUploaderState', () => {
assetUploader.uploadFile(file).pipe(onErrorResumeNext()).subscribe();
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Failed');
expect(upload.progress).toBe(1);
@ -103,7 +103,7 @@ describe('AssetUploaderState', () => {
cb();
});
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Completed');
expect(upload.progress).toBe(100);
@ -118,7 +118,7 @@ describe('AssetUploaderState', () => {
assetUploader.uploadAsset(asset, file).subscribe();
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Running');
expect(upload.progress).toBe(1);
@ -132,7 +132,7 @@ describe('AssetUploaderState', () => {
assetUploader.uploadAsset(asset, file).subscribe();
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Running');
expect(upload.progress).toBe(20);
@ -146,7 +146,7 @@ describe('AssetUploaderState', () => {
assetUploader.uploadAsset(asset, file).pipe(onErrorResumeNext()).subscribe();
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Failed');
expect(upload.progress).toBe(1);
@ -168,7 +168,7 @@ describe('AssetUploaderState', () => {
}
});
const upload = assetUploader.snapshot.uploads.at(0);
const upload = assetUploader.snapshot.uploads[0];
expect(upload.status).toBe('Completed');
expect(upload.progress).toBe(100);

7
src/Squidex/app/shared/state/asset-uploader.state.ts

@ -11,7 +11,6 @@ import { map, publishReplay, refCount, takeUntil } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
MathHelper,
State,
Types
@ -45,7 +44,7 @@ interface Snapshot {
export class UploadCanceled {}
type UploadList = ImmutableArray<Upload>;
type UploadList = ReadonlyArray<Upload>;
type UploadResult = AssetDto | number;
@Injectable()
@ -58,7 +57,7 @@ export class AssetUploaderState extends State<Snapshot> {
private readonly assetsService: AssetsService,
private readonly dialogs: DialogService
) {
super({ uploads: ImmutableArray.empty() });
super({ uploads: [] });
}
public stopUpload(upload: Upload) {
@ -152,7 +151,7 @@ export class AssetUploaderState extends State<Snapshot> {
private addUpload(upload: Upload) {
this.next(s => {
const uploads = s.uploads.push(upload);
const uploads = [upload, ...s.uploads];
return { ...s, uploads };
});

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

@ -59,7 +59,7 @@ describe('AssetsState', () => {
assetsState.load().subscribe();
expect(assetsState.snapshot.assets.values).toEqual([asset1, asset2]);
expect(assetsState.snapshot.assets).toEqual([asset1, asset2]);
expect(assetsState.snapshot.assetsPager.numberOfItems).toEqual(200);
expect(assetsState.snapshot.isLoaded).toBeTruthy();
@ -155,7 +155,7 @@ describe('AssetsState', () => {
it('should add asset to snapshot when created', () => {
assetsState.add(newAsset);
expect(assetsState.snapshot.assets.values).toEqual([newAsset, asset1, asset2]);
expect(assetsState.snapshot.assets).toEqual([newAsset, asset1, asset2]);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(201);
});
@ -171,7 +171,7 @@ describe('AssetsState', () => {
assetsState.update(update);
const newAsset1 = assetsState.snapshot.assets.at(0);
const newAsset1 = assetsState.snapshot.assets[0];
expect(newAsset1).toEqual(update);
expect(assetsState.snapshot.tags).toEqual({ tag2: 1, shared: 1, new: 1 });
@ -183,7 +183,7 @@ describe('AssetsState', () => {
assetsState.delete(asset1).subscribe();
expect(assetsState.snapshot.assets.values.length).toBe(1);
expect(assetsState.snapshot.assets.length).toBe(1);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(199);
expect(assetsState.snapshot.tags).toEqual({ shared: 1, tag2: 1 });
});

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

@ -10,9 +10,8 @@ import { combineLatest, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import {
compareStringsAsc,
compareStrings,
DialogService,
ImmutableArray,
Pager,
shareSubscribed,
State
@ -31,7 +30,7 @@ interface Snapshot {
tagsSelected: { [name: string]: boolean };
// The current assets.
assets: ImmutableArray<AssetDto>;
assets: ReadonlyArray<AssetDto>;
// The pagination information.
assetsPager: Pager;
@ -86,7 +85,7 @@ export class AssetsState extends State<Snapshot> {
private readonly assetsService: AssetsService,
private readonly dialogs: DialogService
) {
super({ assets: ImmutableArray.empty(), assetsPager: new Pager(0, 0, 30), assetsQueryJson: '', tags: {}, tagsSelected: {} });
super({ assets: [], assetsPager: new Pager(0, 0, 30), assetsQueryJson: '', tags: {}, tagsSelected: {} });
}
public load(isReload = false): Observable<any> {
@ -106,13 +105,12 @@ export class AssetsState extends State<Snapshot> {
Object.keys(this.snapshot.tagsSelected)),
this.assetsService.getTags(this.appName)
).pipe(
tap(([ { items, total, canCreate }, tags ]) => {
tap(([ { items: assets, total, canCreate }, tags ]) => {
if (isReload) {
this.dialogs.notifyInfo('Assets reloaded.');
}
this.next(s => {
const assets = ImmutableArray.of(items);
const assetsPager = s.assetsPager.setCount(total);
return { ...s, assets, assetsPager, isLoaded: true, tags, canCreate };
@ -123,7 +121,7 @@ export class AssetsState extends State<Snapshot> {
public add(asset: AssetDto) {
this.next(s => {
const assets = s.assets.pushFront(asset);
const assets = [asset, ...s.assets];
const assetsPager = s.assetsPager.incrementCount();
const tags = { ...s.tags };
@ -266,7 +264,7 @@ function removeTags(previous: AssetDto, tags: { [x: string]: number; }, tagsSele
}
function sort(tags: { [name: string]: number }) {
return Object.keys(tags).sort(compareStringsAsc).map(name => ({ name, count: tags[name] }));
return Object.keys(tags).sort(compareStrings).map(name => ({ name, count: tags[name] }));
}
@Injectable()

2
src/Squidex/app/shared/state/backups.state.spec.ts

@ -51,7 +51,7 @@ describe('BackupsState', () => {
backupsState.load().subscribe();
expect(backupsState.snapshot.backups.values).toEqual([backup1, backup2]);
expect(backupsState.snapshot.backups).toEqual([backup1, backup2]);
expect(backupsState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());

9
src/Squidex/app/shared/state/backups.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State
} from '@app/framework';
@ -31,7 +30,7 @@ interface Snapshot {
canCreate?: boolean;
}
type BackupsList = ImmutableArray<BackupDto>;
type BackupsList = ReadonlyArray<BackupDto>;
@Injectable()
export class BackupsState extends State<Snapshot> {
@ -52,7 +51,7 @@ export class BackupsState extends State<Snapshot> {
private readonly backupsService: BackupsService,
private readonly dialogs: DialogService
) {
super({ backups: ImmutableArray.empty() });
super({ backups: [] });
}
public load(isReload = false, silent = false): Observable<any> {
@ -61,14 +60,12 @@ export class BackupsState extends State<Snapshot> {
}
return this.backupsService.getBackups(this.appName).pipe(
tap(({ items, canCreate }) => {
tap(({ items: backups, canCreate }) => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Backups reloaded.');
}
const backups = ImmutableArray.of(items);
this.next(s => {
return { ...s, backups, isLoaded: true, canCreate };
});
}),

4
src/Squidex/app/shared/state/clients.state.spec.ts

@ -52,7 +52,7 @@ describe('ClientsState', () => {
clientsState.load().subscribe();
expect(clientsState.snapshot.clients.values).toEqual(oldClients.items);
expect(clientsState.snapshot.clients).toEqual(oldClients.items);
expect(clientsState.snapshot.version).toEqual(version);
expect(clientsState.isLoaded).toBeTruthy();
@ -130,7 +130,7 @@ describe('ClientsState', () => {
});
function expectNewClients(updated: ClientsPayload) {
expect(clientsState.snapshot.clients.values).toEqual(updated.items);
expect(clientsState.snapshot.clients).toEqual(updated.items);
expect(clientsState.snapshot.version).toEqual(newVersion);
}

9
src/Squidex/app/shared/state/clients.state.ts

@ -13,7 +13,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
@ -43,7 +42,7 @@ interface Snapshot {
canCreate?: boolean;
}
type ClientsList = ImmutableArray<ClientDto>;
type ClientsList = ReadonlyArray<ClientDto>;
@Injectable()
export class ClientsState extends State<Snapshot> {
@ -61,7 +60,7 @@ export class ClientsState extends State<Snapshot> {
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ clients: ImmutableArray.empty(), version: Version.EMPTY });
super({ clients: [], version: Version.EMPTY });
}
public load(isReload = false): Observable<any> {
@ -105,9 +104,7 @@ export class ClientsState extends State<Snapshot> {
}
private replaceClients(payload: ClientsPayload, version: Version) {
const { canCreate, items } = payload;
const clients = ImmutableArray.of(items);
const { canCreate, items: clients } = payload;
this.next(s => {
return {

17
src/Squidex/app/shared/state/comments.state.spec.ts

@ -14,7 +14,6 @@ import {
CommentsService,
CommentsState,
DialogService,
ImmutableArray,
Version
} from '@app/shared/internal';
@ -68,10 +67,10 @@ describe('CommentsState', () => {
commentsState.load().subscribe();
expect(commentsState.snapshot.isLoaded).toBeTruthy();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
expect(commentsState.snapshot.comments).toEqual([
new CommentDto('2', modified, 'text2_2', creator),
new CommentDto('3', modified, 'text3', creator)
]));
]);
});
});
@ -93,11 +92,11 @@ describe('CommentsState', () => {
commentsState.create('text3').subscribe();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
expect(commentsState.snapshot.comments).toEqual([
new CommentDto('1', modified, 'text1', creator),
new CommentDto('2', modified, 'text2', creator),
new CommentDto('3', modified, 'text3', creator)
]));
]);
});
it('should update properties when updated', () => {
@ -108,10 +107,10 @@ describe('CommentsState', () => {
commentsState.update(oldComments.createdComments[1], 'text2_2', modified).subscribe();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
expect(commentsState.snapshot.comments).toEqual([
new CommentDto('1', modified, 'text1', creator),
new CommentDto('2', modified, 'text2_2', creator)
]));
]);
});
it('should remove comment from snapshot when deleted', () => {
@ -120,9 +119,9 @@ describe('CommentsState', () => {
commentsState.delete(oldComments.createdComments[1]).subscribe();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
expect(commentsState.snapshot.comments).toEqual([
new CommentDto('1', modified, 'text1', creator)
]));
]);
});
});
});

11
src/Squidex/app/shared/state/comments.state.ts

@ -11,7 +11,6 @@ import { map, tap } from 'rxjs/operators';
import {
DateTime,
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
@ -31,7 +30,7 @@ interface Snapshot {
isLoaded?: boolean;
}
type CommentsList = ImmutableArray<CommentDto>;
type CommentsList = ReadonlyArray<CommentDto>;
export class CommentsState extends State<Snapshot> {
public comments =
@ -46,7 +45,7 @@ export class CommentsState extends State<Snapshot> {
private readonly commentsService: CommentsService,
private readonly dialogs: DialogService
) {
super({ comments: ImmutableArray.empty(), version: new Version('-1') });
super({ comments: [], version: new Version('-1') });
}
public load(): Observable<any> {
@ -57,7 +56,7 @@ export class CommentsState extends State<Snapshot> {
for (const created of payload.createdComments) {
if (!comments.find(x => x.id === created.id)) {
comments = comments.push(created);
comments = [...comments, created];
}
}
@ -77,9 +76,9 @@ export class CommentsState extends State<Snapshot> {
public create(text: string): Observable<CommentDto> {
return this.commentsService.postComment(this.appName, this.commentsId, { text }).pipe(
tap(comment => {
tap(created => {
this.next(s => {
const comments = s.comments.push(comment);
const comments = [...s.comments, created];
return { ...s, comments };
});

7
src/Squidex/app/shared/state/contents.forms.spec.ts

@ -18,7 +18,6 @@ import {
FieldsValidators,
getContentValue,
HtmlValue,
ImmutableArray,
LanguageDto,
NestedFieldDto,
PartitionConfig,
@ -507,10 +506,10 @@ describe('GetContentValue', () => {
});
describe('ContentForm', () => {
const languages = ImmutableArray.of([
const languages = [
new AppLanguageDto({}, 'en', 'English', true, false, []),
new AppLanguageDto({}, 'de', 'English', false, true, [])
]);
];
const complexSchema = createSchema({ fields: [
createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
@ -543,7 +542,7 @@ describe('ContentForm', () => {
});
it('should return partition for language', () => {
const result = partitions.get(languages.at(1));
const result = partitions.get(languages[1]);
expect(result).toEqual({ key: 'de', isOptional: true });
});

7
src/Squidex/app/shared/state/contents.forms.ts

@ -14,7 +14,6 @@ import {
DateTime,
Form,
formControls,
ImmutableArray,
Types,
ValidatorsEx
} from '@app/framework';
@ -404,8 +403,8 @@ export class PartitionConfig {
private readonly invariant: ReadonlyArray<Partition> = [{ key: fieldInvariant, isOptional: false }];
private readonly languages: ReadonlyArray<Partition>;
constructor(languages: ImmutableArray<AppLanguageDto>) {
this.languages = languages.values.map(l => this.get(l));
constructor(languages: ReadonlyArray<AppLanguageDto>) {
this.languages = languages.map(l => this.get(l));
}
public get(language?: AppLanguageDto) {
@ -427,7 +426,7 @@ export class EditContentForm extends Form<FormGroup, any> {
public value = new BehaviorSubject<any>(this.form.value);
constructor(languages: ImmutableArray<AppLanguageDto>,
constructor(languages: ReadonlyArray<AppLanguageDto>,
private readonly schema: SchemaDetailsDto
) {
super(new FormGroup({}));

10
src/Squidex/app/shared/state/contents.state.ts

@ -12,7 +12,6 @@ import { catchError, switchMap, tap } from 'rxjs/operators';
import {
DialogService,
ErrorDto,
ImmutableArray,
Pager,
shareSubscribed,
State,
@ -29,7 +28,7 @@ import { SchemasState } from './schemas.state';
interface Snapshot {
// The current comments.
contents: ImmutableArray<ContentDto>;
contents: ReadonlyArray<ContentDto>;
// The pagination information.
contentsPager: Pager;
@ -96,7 +95,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
private readonly contentsService: ContentsService,
private readonly dialogs: DialogService
) {
super({ contents: ImmutableArray.empty(), contentsPager: new Pager(0), contentsQueryJson: '' });
super({ contents: [], contentsPager: new Pager(0), contentsQueryJson: '' });
}
public select(id: string | null): Observable<ContentDto | null> {
@ -152,13 +151,12 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.snapshot.contentsPager.pageSize,
this.snapshot.contentsPager.skip,
this.snapshot.contentsQuery, undefined).pipe(
tap(({ total, items, canCreate, canCreateAndPublish, statuses }) => {
tap(({ total, items: contents, canCreate, canCreateAndPublish, statuses }) => {
if (isReload) {
this.dialogs.notifyInfo('Contents reloaded.');
}
return this.next(s => {
const contents = ImmutableArray.of(items);
const contentsPager = s.contentsPager.setCount(total);
statuses = s.statuses || statuses;
@ -193,7 +191,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.dialogs.notifyInfo('Content created successfully.');
return this.next(s => {
const contents = s.contents.pushFront(payload);
const contents = [payload, ...s.contents];
const contentsPager = s.contentsPager.incrementCount();
return { ...s, contents, contentsPager };

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

@ -59,7 +59,7 @@ describe('ContributorsState', () => {
it('should load contributors', () => {
contributorsState.load().subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items);
expect(contributorsState.snapshot.contributors).toEqual(oldContributors.items);
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
expect(contributorsState.snapshot.version).toEqual(version);
@ -73,7 +73,7 @@ describe('ContributorsState', () => {
let contributors: ReadonlyArray<ContributorDto>;
contributorsState.contributorsPaged.subscribe(result => {
contributors = result.values;
contributors = result;
});
expect(contributors!).toEqual(oldContributors.items.slice(0, 10));
@ -87,7 +87,7 @@ describe('ContributorsState', () => {
let contributors: ReadonlyArray<ContributorDto>;
contributorsState.contributorsPaged.subscribe(result => {
contributors = result.values;
contributors = result;
});
expect(contributors!).toEqual(oldContributors.items.slice(10, 20));
@ -102,7 +102,7 @@ describe('ContributorsState', () => {
let contributors: ReadonlyArray<ContributorDto>;
contributorsState.contributorsPaged.subscribe(result => {
contributors = result.values;
contributors = result;
});
expect(contributors!).toEqual(oldContributors.items.slice(0, 10));
@ -116,7 +116,7 @@ describe('ContributorsState', () => {
let contributors: ReadonlyArray<ContributorDto>;
contributorsState.contributorsPaged.subscribe(result => {
contributors = result.values;
contributors = result;
});
expect(contributors!).toEqual(createContributors(4, 14).items);
@ -162,7 +162,7 @@ describe('ContributorsState', () => {
});
function expectNewContributors(updated: ContributorsPayload) {
expect(contributorsState.snapshot.contributors.values).toEqual(updated.items);
expect(contributorsState.snapshot.contributors).toEqual(updated.items);
expect(contributorsState.snapshot.maxContributors).toBe(updated.maxContributors);
expect(contributorsState.snapshot.version).toEqual(newVersion);
}

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

@ -12,7 +12,6 @@ import { catchError, tap } from 'rxjs/operators';
import {
DialogService,
ErrorDto,
ImmutableArray,
Pager,
shareMapSubscribed,
shareSubscribed,
@ -56,7 +55,7 @@ interface Snapshot {
canCreate?: boolean;
}
type ContributorsList = ImmutableArray<ContributorDto>;
type ContributorsList = ReadonlyArray<ContributorDto>;
@Injectable()
export class ContributorsState extends State<Snapshot> {
@ -95,7 +94,7 @@ export class ContributorsState extends State<Snapshot> {
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ contributors: ImmutableArray.empty(), page: 0, maxContributors: -1, version: Version.EMPTY });
super({ contributors: [], page: 0, maxContributors: -1, version: Version.EMPTY });
}
public load(isReload = false): Observable<any> {
@ -151,9 +150,7 @@ export class ContributorsState extends State<Snapshot> {
private replaceContributors(version: Version, payload: ContributorsPayload) {
this.next(() => {
const { canCreate, items, maxContributors } = payload;
const contributors = ImmutableArray.of(items);
const { canCreate, items: contributors, maxContributors } = payload;
return {
canCreate,

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

@ -12,7 +12,6 @@ import {
AppLanguagesPayload,
AppLanguagesService,
DialogService,
ImmutableArray,
LanguageDto,
LanguagesService,
LanguagesState,
@ -69,18 +68,18 @@ describe('LanguagesState', () => {
it('should load languages', () => {
languagesState.load().subscribe();
expect(languagesState.snapshot.languages.values).toEqual([
expect(languagesState.snapshot.languages).toEqual([
{
language: oldLanguages.items[0],
fallbackLanguages: ImmutableArray.of([oldLanguages.items[1]]),
fallbackLanguagesNew: ImmutableArray.empty()
fallbackLanguages: [oldLanguages.items[1]],
fallbackLanguagesNew: []
}, {
language: oldLanguages.items[1],
fallbackLanguages: ImmutableArray.of([oldLanguages.items[0]]),
fallbackLanguagesNew: ImmutableArray.empty()
fallbackLanguages: [oldLanguages.items[0]],
fallbackLanguagesNew: []
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageIT, languageES]);
expect(languagesState.snapshot.allLanguagesNew).toEqual([languageIT, languageES]);
expect(languagesState.snapshot.isLoaded).toBeTruthy();
expect(languagesState.snapshot.version).toEqual(version);
@ -137,14 +136,14 @@ describe('LanguagesState', () => {
});
function expectNewLanguages(updated: AppLanguagesPayload) {
expect(languagesState.snapshot.languages.values).toEqual([
expect(languagesState.snapshot.languages).toEqual([
{
language: updated.items[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.empty()
fallbackLanguages: [],
fallbackLanguagesNew: []
}
]);
expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageEN, languageIT, languageES]);
expect(languagesState.snapshot.allLanguagesNew).toEqual([languageEN, languageIT, languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
}
});

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

@ -11,7 +11,6 @@ import { map, shareReplay, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareMapSubscribed,
shareSubscribed,
State,
@ -59,9 +58,9 @@ interface Snapshot {
canCreate?: boolean;
}
type AppLanguagesList = ImmutableArray<AppLanguageDto>;
type LanguageList = ImmutableArray<LanguageDto>;
type LanguageResultList = ImmutableArray<SnapshotLanguage>;
type AppLanguagesList = ReadonlyArray<AppLanguageDto>;
type LanguageList = ReadonlyArray<LanguageDto>;
type LanguageResultList = ReadonlyArray<SnapshotLanguage>;
@Injectable()
export class LanguagesState extends State<Snapshot> {
@ -86,9 +85,9 @@ export class LanguagesState extends State<Snapshot> {
private readonly languagesService: LanguagesService
) {
super({
allLanguages: ImmutableArray.empty(),
allLanguagesNew: ImmutableArray.empty(),
languages: ImmutableArray.empty(),
allLanguages: [],
allLanguagesNew: [],
languages: [],
version: Version.EMPTY
});
}
@ -107,7 +106,7 @@ export class LanguagesState extends State<Snapshot> {
this.dialogs.notifyInfo('Languages reloaded.');
}
const sorted = ImmutableArray.of(allLanguages).sortByStringAsc(x => x.englishName);
const sorted = allLanguages.sortedByString(x => x.englishName);
this.replaceLanguages(languages.payload, languages.version, sorted);
}),
@ -142,9 +141,7 @@ export class LanguagesState extends State<Snapshot> {
this.next(s => {
allLanguages = allLanguages || s.allLanguages;
const { canCreate, items } = payload;
const languages = ImmutableArray.of(items);
const { canCreate, items: languages } = payload;
return {
...s,
@ -184,16 +181,15 @@ export class LanguagesState extends State<Snapshot> {
return {
language,
fallbackLanguages:
ImmutableArray.of(
language.fallback
.map(l => languages.find(x => x.iso2Code === l)).filter(x => !!x)
.map(l => l!)),
language.fallback
.map(l => languages.find(x => x.iso2Code === l)).filter(x => !!x)
.map(l => l!),
fallbackLanguagesNew:
languages
.filter(l =>
language.iso2Code !== l.iso2Code &&
language.fallback.indexOf(l.iso2Code) < 0)
.sortByStringAsc(x => x.englishName)
.sortedByString(x => x.englishName)
};
}
}

4
src/Squidex/app/shared/state/patterns.state.spec.ts

@ -52,7 +52,7 @@ describe('PatternsState', () => {
patternsState.load().subscribe();
expect(patternsState.snapshot.patterns.values).toEqual(oldPatterns.items);
expect(patternsState.snapshot.patterns).toEqual(oldPatterns.items);
expect(patternsState.snapshot.version).toEqual(version);
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
@ -116,7 +116,7 @@ describe('PatternsState', () => {
});
function expectNewPatterns(updated: PatternsPayload) {
expect(patternsState.snapshot.patterns.values).toEqual(updated.items);
expect(patternsState.snapshot.patterns).toEqual(updated.items);
expect(patternsState.snapshot.version).toEqual(newVersion);
}
});

9
src/Squidex/app/shared/state/patterns.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareMapSubscribed,
shareSubscribed,
State,
@ -41,7 +40,7 @@ interface Snapshot {
canCreate?: boolean;
}
type PatternsList = ImmutableArray<PatternDto>;
type PatternsList = ReadonlyArray<PatternDto>;
@Injectable()
export class PatternsState extends State<Snapshot> {
@ -59,7 +58,7 @@ export class PatternsState extends State<Snapshot> {
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ patterns: ImmutableArray.empty(), version: Version.EMPTY });
super({ patterns: [], version: Version.EMPTY });
}
public load(isReload = false): Observable<any> {
@ -103,9 +102,7 @@ export class PatternsState extends State<Snapshot> {
}
private replacePatterns(payload: PatternsPayload, version: Version) {
const patterns = ImmutableArray.of(payload.items);
const { canCreate } = payload;
const { canCreate, items: patterns } = payload;
this.next(s => {
return { ...s, patterns, isLoaded: true, version, canCreate };

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

@ -61,7 +61,7 @@ describe('PlansState', () => {
plansState.load().subscribe();
expect(plansState.snapshot.plans.values).toEqual([
expect(plansState.snapshot.plans).toEqual([
{ isSelected: true, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[1] }
]);
@ -79,7 +79,7 @@ describe('PlansState', () => {
plansState.load(false, 'id2_yearly').subscribe();
expect(plansState.snapshot.plans.values).toEqual([
expect(plansState.snapshot.plans).toEqual([
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: true, plan: oldPlans.plans[1] }
]);
@ -123,7 +123,7 @@ describe('PlansState', () => {
plansState.change('free').pipe(onErrorResumeNext()).subscribe();
expect(plansState.snapshot.plans.values).toEqual([
expect(plansState.snapshot.plans).toEqual([
{ isSelected: true, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[1] }
]);
@ -137,7 +137,7 @@ describe('PlansState', () => {
plansState.change('id2_yearly').pipe(onErrorResumeNext()).subscribe();
expect(plansState.snapshot.plans.values).toEqual([
expect(plansState.snapshot.plans).toEqual([
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: true, plan: oldPlans.plans[1] }
]);

7
src/Squidex/app/shared/state/plans.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
@ -34,7 +33,7 @@ export interface PlanInfo {
interface Snapshot {
// The current plans.
plans: ImmutableArray<PlanInfo>;
plans: ReadonlyArray<PlanInfo>;
// Indicates if the user is the plan owner.
isOwner?: boolean;
@ -74,7 +73,7 @@ export class PlansState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly plansService: PlansService
) {
super({ plans: ImmutableArray.empty(), version: Version.EMPTY });
super({ plans: [], version: Version.EMPTY });
}
public load(isReload = false, overridePlanId?: string): Observable<any> {
@ -90,7 +89,7 @@ export class PlansState extends State<Snapshot> {
this.next(s => {
const planId = overridePlanId || payload.currentPlanId;
const plans = ImmutableArray.of(payload.plans.map(x => this.createPlan(x, planId)));
const plans = payload.plans.map(x => this.createPlan(x, planId));
return {
...s,

4
src/Squidex/app/shared/state/queries.ts

@ -8,7 +8,7 @@
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { compareStringsAsc, Types } from '@app/framework';
import { compareStrings, Types } from '@app/framework';
import { UIState } from './ui.state';
@ -96,7 +96,7 @@ export class Queries {
function parseQueries(settings: {}) {
let queries = Object.keys(settings).map(name => parseStored(name, settings[name]));
return queries.sort((a, b) => compareStringsAsc(a.name, b.name));
return queries.sort((a, b) => compareStrings(a.name, b.name));
}
export function parseStored(name: string, raw?: string) {

4
src/Squidex/app/shared/state/roles.state.spec.ts

@ -48,7 +48,7 @@ describe('RolesState', () => {
rolesState.load().subscribe();
expect(rolesState.snapshot.roles.values).toEqual(oldRoles.items);
expect(rolesState.snapshot.roles).toEqual(oldRoles.items);
expect(rolesState.snapshot.isLoaded).toBeTruthy();
expect(rolesState.snapshot.version).toEqual(version);
@ -113,7 +113,7 @@ describe('RolesState', () => {
});
function expectNewRoles(updated: RolesPayload) {
expect(rolesState.snapshot.roles.values).toEqual(updated.items);
expect(rolesState.snapshot.roles).toEqual(updated.items);
expect(rolesState.snapshot.version).toEqual(newVersion);
}
});

9
src/Squidex/app/shared/state/roles.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
@ -41,7 +40,7 @@ interface Snapshot {
canCreate?: boolean;
}
type RolesList = ImmutableArray<RoleDto>;
type RolesList = ReadonlyArray<RoleDto>;
@Injectable()
export class RolesState extends State<Snapshot> {
@ -59,7 +58,7 @@ export class RolesState extends State<Snapshot> {
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ roles: ImmutableArray.empty(), version: Version.EMPTY });
super({ roles: [], version: Version.EMPTY });
}
public load(isReload = false): Observable<any> {
@ -103,9 +102,7 @@ export class RolesState extends State<Snapshot> {
}
private replaceRoles(payload: RolesPayload, version: Version) {
const { canCreate, items } = payload;
const roles = ImmutableArray.of(items);
const { canCreate, items: roles } = payload;
this.next(s => {
return { ...s, roles, isLoaded: true, version, canCreate };

2
src/Squidex/app/shared/state/rule-events.state.spec.ts

@ -47,7 +47,7 @@ describe('RuleEventsState', () => {
});
it('should load ruleEvents', () => {
expect(ruleEventsState.snapshot.ruleEvents.values).toEqual(oldRuleEvents);
expect(ruleEventsState.snapshot.ruleEvents).toEqual(oldRuleEvents);
expect(ruleEventsState.snapshot.ruleEventsPager.numberOfItems).toEqual(200);
expect(ruleEventsState.snapshot.isLoaded).toBeTruthy();

8
src/Squidex/app/shared/state/rule-events.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
Pager,
shareSubscribed,
State
@ -23,7 +22,7 @@ import { RuleEventDto, RulesService } from './../services/rules.service';
interface Snapshot {
// The current rule events.
ruleEvents: ImmutableArray<RuleEventDto>;
ruleEvents: ReadonlyArray<RuleEventDto>;
// The pagination information.
ruleEventsPager: Pager;
@ -48,7 +47,7 @@ export class RuleEventsState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly rulesService: RulesService
) {
super({ ruleEvents: ImmutableArray.of(), ruleEventsPager: new Pager(0) });
super({ ruleEvents: [], ruleEventsPager: new Pager(0) });
}
public load(isReload = false): Observable<any> {
@ -63,13 +62,12 @@ export class RuleEventsState extends State<Snapshot> {
return this.rulesService.getEvents(this.appName,
this.snapshot.ruleEventsPager.pageSize,
this.snapshot.ruleEventsPager.skip).pipe(
tap(({ total, items }) => {
tap(({ total, items: ruleEvents }) => {
if (isReload) {
this.dialogs.notifyInfo('RuleEvents reloaded.');
}
return this.next(s => {
const ruleEvents = ImmutableArray.of(items);
const ruleEventsPager = s.ruleEventsPager.setCount(total);
return { ...s, ruleEvents, ruleEventsPager, isLoaded: true };

14
src/Squidex/app/shared/state/rules.state.spec.ts

@ -56,7 +56,7 @@ describe('RulesState', () => {
rulesState.load().subscribe();
expect(rulesState.snapshot.rules.values).toEqual([rule1, rule2]);
expect(rulesState.snapshot.rules).toEqual([rule1, rule2]);
expect(rulesState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
@ -91,7 +91,7 @@ describe('RulesState', () => {
rulesState.create(request).subscribe();
expect(rulesState.snapshot.rules.values).toEqual([rule1, rule2, newRule]);
expect(rulesState.snapshot.rules).toEqual([rule1, rule2, newRule]);
});
it('should update rule when updated action', () => {
@ -104,7 +104,7 @@ describe('RulesState', () => {
rulesState.updateAction(rule1, newAction).subscribe();
const newRule1 = rulesState.snapshot.rules.at(0);
const newRule1 = rulesState.snapshot.rules[0];
expect(newRule1).toEqual(updated);
});
@ -119,7 +119,7 @@ describe('RulesState', () => {
rulesState.updateTrigger(rule1, newTrigger).subscribe();
const rule1New = rulesState.snapshot.rules.at(0);
const rule1New = rulesState.snapshot.rules[0];
expect(rule1New).toEqual(updated);
});
@ -132,7 +132,7 @@ describe('RulesState', () => {
rulesState.enable(rule1).subscribe();
const rule1New = rulesState.snapshot.rules.at(0);
const rule1New = rulesState.snapshot.rules[0];
expect(rule1New).toEqual(updated);
});
@ -145,7 +145,7 @@ describe('RulesState', () => {
rulesState.disable(rule1).subscribe();
const rule1New = rulesState.snapshot.rules.at(0);
const rule1New = rulesState.snapshot.rules[0];
expect(rule1New).toEqual(updated);
});
@ -156,7 +156,7 @@ describe('RulesState', () => {
rulesState.delete(rule1).subscribe();
expect(rulesState.snapshot.rules.values).toEqual([rule2]);
expect(rulesState.snapshot.rules).toEqual([rule2]);
});
});
});

13
src/Squidex/app/shared/state/rules.state.ts

@ -11,7 +11,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State
} from '@app/framework';
@ -38,7 +37,7 @@ interface Snapshot {
canReadEvents?: boolean;
}
type RulesList = ImmutableArray<RuleDto>;
type RulesList = ReadonlyArray<RuleDto>;
@Injectable()
export class RulesState extends State<Snapshot> {
@ -59,7 +58,7 @@ export class RulesState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly rulesService: RulesService
) {
super({ rules: ImmutableArray.empty() });
super({ rules: [] });
}
public load(isReload = false): Observable<any> {
@ -68,14 +67,12 @@ export class RulesState extends State<Snapshot> {
}
return this.rulesService.getRules(this.appName).pipe(
tap(({ items, canCreate, canReadEvents }) => {
tap(({ items: rules, canCreate, canReadEvents }) => {
if (isReload) {
this.dialogs.notifyInfo('Rules reloaded.');
}
this.next(s => {
const rules = ImmutableArray.of(items);
return { ...s,
canCreate,
canReadEvents,
@ -91,7 +88,7 @@ export class RulesState extends State<Snapshot> {
return this.rulesService.postRule(this.appName, request).pipe(
tap(created => {
this.next(s => {
const rules = s.rules.push(created);
const rules = [...s.rules, created];
return { ...s, rules };
});
@ -103,7 +100,7 @@ export class RulesState extends State<Snapshot> {
return this.rulesService.deleteRule(this.appName, rule, rule.version).pipe(
tap(() => {
this.next(s => {
const rules = s.rules.removeAll(x => x.id === rule.id);
const rules = s.rules.removeBy('id', rule);
return { ...s, rules };
});

2
src/Squidex/app/shared/state/schema-tag-converter.ts

@ -25,7 +25,7 @@ export class SchemaTagConverter implements Converter, OnDestroy {
) {
this.schemasSubscription =
schemasState.schemas.subscribe(schemas => {
this.schemas = schemas.values;
this.schemas = schemas;
this.suggestions = this.schemas.map(x => new TagValue(x.id, x.name, x.id));
});

77
src/Squidex/app/shared/state/schemas.state.spec.ts

@ -13,7 +13,6 @@ import { SchemaCategory, SchemasState } from './schemas.state';
import {
DialogService,
FieldDto,
ImmutableArray,
SchemaDetailsDto,
SchemasService,
UpdateSchemaCategoryDto,
@ -68,14 +67,14 @@ describe('SchemasState', () => {
schemasState.load().subscribe();
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items);
expect(schemasState.snapshot.schemas).toEqual(oldSchemas.items);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) }
{ name: 'category1', upper: 'CATEGORY1', schemas: [schema1] },
{ name: 'category2', upper: 'CATEGORY2', schemas: [schema2] }
]);
schemasService.verifyAll();
@ -88,15 +87,15 @@ describe('SchemasState', () => {
schemasState.addCategory('category3');
schemasState.load(true).subscribe();
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items);
expect(schemasState.snapshot.schemas).toEqual(oldSchemas.items);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) },
{ name: 'category3', upper: 'CATEGORY3', schemas: ImmutableArray.empty() }
{ name: 'category1', upper: 'CATEGORY1', schemas: [schema1] },
{ name: 'category2', upper: 'CATEGORY2', schemas: [schema2] },
{ name: 'category3', upper: 'CATEGORY3', schemas: [] }
]);
schemasService.verifyAll();
@ -152,9 +151,9 @@ describe('SchemasState', () => {
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) },
{ name: 'category3', upper: 'CATEGORY3', schemas: ImmutableArray.empty() }
{ name: 'category1', upper: 'CATEGORY1', schemas: [schema1] },
{ name: 'category2', upper: 'CATEGORY2', schemas: [schema2] },
{ name: 'category3', upper: 'CATEGORY3', schemas: [] }
]);
});
@ -164,8 +163,8 @@ describe('SchemasState', () => {
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) }
{ name: 'category1', upper: 'CATEGORY1', schemas: [schema1] },
{ name: 'category2', upper: 'CATEGORY2', schemas: [schema2] }
]);
});
@ -176,8 +175,8 @@ describe('SchemasState', () => {
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) }
{ name: 'category1', upper: 'CATEGORY1', schemas: [schema1] },
{ name: 'category2', upper: 'CATEGORY2', schemas: [schema2] }
]);
});
@ -203,7 +202,7 @@ describe('SchemasState', () => {
expect(selectedSchema!).toBe(schema);
expect(schemasState.snapshot.selectedSchema).toBe(schema);
expect(schemasState.snapshot.selectedSchema).toBe(<SchemaDetailsDto>schemasState.snapshot.schemas.at(0));
expect(schemasState.snapshot.selectedSchema).toBe(<SchemaDetailsDto>schemasState.snapshot.schemas[0]);
});
it('should return schema on get and cache it', () => {
@ -259,7 +258,7 @@ describe('SchemasState', () => {
schemasState.publish(schema1).subscribe();
const schema1New = schemasState.snapshot.schemas.at(0);
const schema1New = schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
});
@ -272,7 +271,7 @@ describe('SchemasState', () => {
schemasState.unpublish(schema1).subscribe();
const schema1New = schemasState.snapshot.schemas.at(0);
const schema1New = schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
});
@ -287,7 +286,7 @@ describe('SchemasState', () => {
schemasState.changeCategory(schema1, category).subscribe();
const schema1New = schemasState.snapshot.schemas.at(0);
const schema1New = schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
});
@ -308,7 +307,7 @@ describe('SchemasState', () => {
schemasState.publish(schema1).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -324,7 +323,7 @@ describe('SchemasState', () => {
schemasState.changeCategory(schema1, category).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -340,7 +339,7 @@ describe('SchemasState', () => {
schemasState.update(schema1, request).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -356,7 +355,7 @@ describe('SchemasState', () => {
schemasState.synchronize(schema1, request).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -372,7 +371,7 @@ describe('SchemasState', () => {
schemasState.configureScripts(schema1, request).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -388,7 +387,7 @@ describe('SchemasState', () => {
schemasState.configurePreviewUrls(schema1, request).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -404,8 +403,8 @@ describe('SchemasState', () => {
schemasState.create(request).subscribe();
expect(schemasState.snapshot.schemas.values.length).toBe(3);
expect(schemasState.snapshot.schemas.at(2)).toEqual(updated);
expect(schemasState.snapshot.schemas.length).toBe(3);
expect(schemasState.snapshot.schemas[2]).toEqual(updated);
});
it('should remove schema from snapshot when deleted', () => {
@ -414,7 +413,7 @@ describe('SchemasState', () => {
schemasState.delete(schema1).subscribe();
expect(schemasState.snapshot.schemas.values.length).toBe(1);
expect(schemasState.snapshot.schemas.length).toBe(1);
expect(schemasState.snapshot.selectedSchema).toBeNull();
});
@ -432,7 +431,7 @@ describe('SchemasState', () => {
newField = result;
});
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -453,7 +452,7 @@ describe('SchemasState', () => {
newField = result;
});
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -468,7 +467,7 @@ describe('SchemasState', () => {
schemasState.deleteField(schema1, schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -482,7 +481,7 @@ describe('SchemasState', () => {
schemasState.orderFields(schema1, [schema.fields[1], schema.fields[2]]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -496,7 +495,7 @@ describe('SchemasState', () => {
schemasState.orderFields(schema1, [schema.fields[1], schema.fields[2]], schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -512,7 +511,7 @@ describe('SchemasState', () => {
schemasState.updateField(schema1, schema.fields[0], request).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -526,7 +525,7 @@ describe('SchemasState', () => {
schemasState.hideField(schema1, schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -540,7 +539,7 @@ describe('SchemasState', () => {
schemasState.disableField(schema1, schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -554,7 +553,7 @@ describe('SchemasState', () => {
schemasState.lockField(schema1, schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -568,7 +567,7 @@ describe('SchemasState', () => {
schemasState.showField(schema1, schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
@ -582,7 +581,7 @@ describe('SchemasState', () => {
schemasState.enableField(schema1, schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas[0];
expect(schema1New).toEqual(updated);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);

19
src/Squidex/app/shared/state/schemas.state.ts

@ -10,10 +10,9 @@ import { empty, Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import {
compareStringsAsc,
compareStrings,
defined,
DialogService,
ImmutableArray,
shareMapSubscribed,
shareSubscribed,
State,
@ -54,7 +53,7 @@ interface Snapshot {
canCreate?: boolean;
}
export type SchemasList = ImmutableArray<SchemaDto>;
export type SchemasList = ReadonlyArray<SchemaDto>;
export type SchemaCategory = { name: string; schemas: SchemasList; upper: string; };
function sameSchema(lhs: SchemaDetailsDto | null, rhs?: SchemaDetailsDto | null): boolean {
@ -96,7 +95,7 @@ export class SchemasState extends State<Snapshot> {
private readonly dialogs: DialogService,
private readonly schemasService: SchemasService
) {
super({ schemas: ImmutableArray.empty(), categories: [] });
super({ schemas: [], categories: [] });
}
public select(idOrName: string | null): Observable<SchemaDetailsDto | null> {
@ -156,7 +155,7 @@ export class SchemasState extends State<Snapshot> {
}
return this.next(s => {
const schemas = ImmutableArray.of(items).sortByStringAsc(x => x.displayName);
const schemas = items.sortedByString(x => x.displayName);
return { ...s, schemas, isLoaded: true, canCreate };
});
@ -168,7 +167,7 @@ export class SchemasState extends State<Snapshot> {
return this.schemasService.postSchema(this.appName, request).pipe(
tap(created => {
this.next(s => {
const schemas = s.schemas.push(created).sortByStringAsc(x => x.displayName);
const schemas = [...s.schemas, created].sortedByString(x => x.displayName);
return { ...s, schemas };
});
@ -199,7 +198,7 @@ export class SchemasState extends State<Snapshot> {
public removeCategory(name: string) {
this.next(s => {
const categories = s.categories.filter(x => x !== name);
const categories = s.categories.removed(name);
return { ...s, categories: categories };
});
@ -335,7 +334,7 @@ export class SchemasState extends State<Snapshot> {
private replaceSchema(schema: SchemaDto) {
return this.next(s => {
const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName);
const schemas = s.schemas.replaceBy('id', schema).sortedByString(x => x.displayName);
const selectedSchema =
Types.is(schema, SchemaDetailsDto) &&
@ -369,7 +368,7 @@ function buildCategories(categories: ReadonlyArray<string>, schemas: SchemasList
uniqueCategories[category] = true;
}
for (const schema of schemas.values) {
for (const schema of schemas) {
uniqueCategories[getCategory(schema)] = true;
}
@ -381,7 +380,7 @@ function buildCategories(categories: ReadonlyArray<string>, schemas: SchemasList
}
}
result.sort((a, b) => compareStringsAsc(a.upper, b.upper));
result.sort((a, b) => compareStrings(a.upper, b.upper));
return result;
}

4
src/Squidex/app/shared/state/workflows.state.spec.ts

@ -52,7 +52,7 @@ describe('WorkflowsState', () => {
workflowsState.load().subscribe();
expect(workflowsState.snapshot.workflows.values).toEqual(oldWorkflows.items);
expect(workflowsState.snapshot.workflows).toEqual(oldWorkflows.items);
expect(workflowsState.snapshot.isLoaded).toBeTruthy();
expect(workflowsState.snapshot.version).toEqual(version);
@ -117,7 +117,7 @@ describe('WorkflowsState', () => {
});
function expectNewWorkflows(updated: WorkflowsPayload) {
expect(workflowsState.snapshot.workflows.values).toEqual(updated.items);
expect(workflowsState.snapshot.workflows).toEqual(updated.items);
expect(workflowsState.snapshot.version).toEqual(newVersion);
}
});

9
src/Squidex/app/shared/state/workflows.state.ts

@ -13,7 +13,6 @@ import { tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
@ -29,7 +28,7 @@ import {
interface Snapshot {
// The current workflow.
workflows: ImmutableArray<WorkflowDto>;
workflows: ReadonlyArray<WorkflowDto>;
// The app version.
version: Version;
@ -63,7 +62,7 @@ export class WorkflowsState extends State<Snapshot> {
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ errors: [], workflows: ImmutableArray.empty(), version: Version.EMPTY });
super({ errors: [], workflows: [], version: Version.EMPTY });
}
public load(isReload = false): Observable<any> {
@ -109,9 +108,7 @@ export class WorkflowsState extends State<Snapshot> {
}
private replaceWorkflows(payload: WorkflowsPayload, version: Version) {
const { canCreate, errors, items } = payload;
const workflows = ImmutableArray.of(items);
const { canCreate, errors, items: workflows } = payload;
this.next(s => {
return { ...s, workflows, errors, isLoaded: true, version, canCreate };

Loading…
Cancel
Save