Browse Source

Status queries and duplicate code removed.

pull/364/head
Sebastian Stehle 7 years ago
parent
commit
371f101b3b
  1. 4
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  2. 26
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  3. 8
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  4. 19
      src/Squidex/app/features/administration/state/users.state.ts
  5. 12
      src/Squidex/app/features/content/pages/contents/contents-filters-page.component.html
  6. 2
      src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts
  7. 2
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  8. 19
      src/Squidex/app/framework/state.ts
  9. 96
      src/Squidex/app/framework/utils/immutable-array.ts
  10. 6
      src/Squidex/app/shared/services/app-languages.service.ts
  11. 6
      src/Squidex/app/shared/services/clients.service.ts
  12. 6
      src/Squidex/app/shared/services/contents.service.spec.ts
  13. 23
      src/Squidex/app/shared/services/contents.service.ts
  14. 6
      src/Squidex/app/shared/services/contributors.service.ts
  15. 6
      src/Squidex/app/shared/services/patterns.service.ts
  16. 6
      src/Squidex/app/shared/services/roles.service.ts
  17. 6
      src/Squidex/app/shared/services/schemas.service.ts
  18. 10
      src/Squidex/app/shared/state/apps.state.ts
  19. 5
      src/Squidex/app/shared/state/asset-uploader.state.ts
  20. 36
      src/Squidex/app/shared/state/assets.state.ts
  21. 18
      src/Squidex/app/shared/state/backups.state.ts
  22. 17
      src/Squidex/app/shared/state/clients.state.ts
  23. 8
      src/Squidex/app/shared/state/comments.state.ts
  24. 52
      src/Squidex/app/shared/state/contents.state.ts
  25. 1
      src/Squidex/app/shared/state/contributors.state.spec.ts
  26. 28
      src/Squidex/app/shared/state/contributors.state.ts
  27. 16
      src/Squidex/app/shared/state/filter.state.ts
  28. 23
      src/Squidex/app/shared/state/languages.state.ts
  29. 17
      src/Squidex/app/shared/state/patterns.state.ts
  30. 17
      src/Squidex/app/shared/state/plans.state.ts
  31. 17
      src/Squidex/app/shared/state/roles.state.ts
  32. 11
      src/Squidex/app/shared/state/rule-events.state.ts
  33. 18
      src/Squidex/app/shared/state/rules.state.ts
  34. 45
      src/Squidex/app/shared/state/schemas.state.ts
  35. 15
      src/Squidex/app/shared/state/ui.state.ts

4
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -124,7 +124,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context(); var context = Context();
var contents = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids); var contents = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids);
var response = ContentsDto.FromContents(contents, context, this, app, null); var response = ContentsDto.FromContents(contents.Count, contents, context, this, app, null);
if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys) if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys)
{ {
@ -159,7 +159,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context(); var context = Context();
var contents = await contentQuery.QueryAsync(context, name, Q.Empty.WithIds(ids).WithODataQuery(Request.QueryString.ToString())); var contents = await contentQuery.QueryAsync(context, name, Q.Empty.WithIds(ids).WithODataQuery(Request.QueryString.ToString()));
var response = ContentsDto.FromContents(contents, context, this, app, name); var response = ContentsDto.FromContents(contents.Total, contents, context, this, app, name);
if (ShouldProvideSurrogateKeys(response)) if (ShouldProvideSurrogateKeys(response))
{ {

26
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -30,6 +30,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
[Required] [Required]
public ContentDto[] Items { get; set; } public ContentDto[] Items { get; set; }
/// <summary>
/// The possible statuses.
/// </summary>
[Required]
public string[] Statuses { get; set; }
public string ToEtag() public string ToEtag()
{ {
return Items.ToManyEtag(Total); return Items.ToManyEtag(Total);
@ -40,24 +46,18 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
return Items.ToSurrogateKeys(); return Items.ToSurrogateKeys();
} }
public static ContentsDto FromContents(IList<IContentEntity> contents, QueryContext context, ApiController controller, string app, string schema) public static ContentsDto FromContents(long total, IEnumerable<IContentEntity> contents, QueryContext context,
ApiController controller,
string app,
string schema)
{ {
var result = new ContentsDto var result = new ContentsDto
{ {
Total = contents.Count, Total = total,
Items = contents.Select(x => ContentDto.FromContent(x, context, controller, app, schema)).ToArray() Items = contents.Select(x => ContentDto.FromContent(x, context, controller, app, schema)).ToArray(),
}; };
return result.CreateLinks(controller, app, schema); result.Statuses = new string[] { "Archived", "Draft", "Published" };
}
public static ContentsDto FromContents(IResultList<IContentEntity> contents, QueryContext context, ApiController controller, string app, string schema)
{
var result = new ContentsDto
{
Total = contents.Total,
Items = contents.Select(x => ContentDto.FromContent(x, context, controller, app, schema)).ToArray()
};
return result.CreateLinks(controller, app, schema); return result.CreateLinks(controller, app, schema);
} }

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -31,12 +31,10 @@ type EventConsumersList = ImmutableArray<EventConsumerDto>;
@Injectable() @Injectable()
export class EventConsumersState extends State<Snapshot> { export class EventConsumersState extends State<Snapshot> {
public eventConsumers = public eventConsumers =
this.changes.pipe(map(x => x.eventConsumers), this.project(x => x.eventConsumers);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
constructor( constructor(
private readonly dialogs: DialogService, private readonly dialogs: DialogService,

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import '@app/framework/utils/rxjs-extensions'; import '@app/framework/utils/rxjs-extensions';
@ -56,24 +56,19 @@ export type UsersResult = { total: number, users: UsersList };
@Injectable() @Injectable()
export class UsersState extends State<Snapshot> { export class UsersState extends State<Snapshot> {
public users = public users =
this.changes.pipe(map(x => x.users), this.project(x => x.users);
distinctUntilChanged());
public usersPager = public usersPager =
this.changes.pipe(map(x => x.usersPager), this.project(x => x.usersPager);
distinctUntilChanged());
public selectedUser = public selectedUser =
this.changes.pipe(map(x => x.selectedUser), this.project(x => x.selectedUser);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly dialogs: DialogService, private readonly dialogs: DialogService,
@ -119,7 +114,7 @@ export class UsersState extends State<Snapshot> {
this.snapshot.usersPager.pageSize, this.snapshot.usersPager.pageSize,
this.snapshot.usersPager.skip, this.snapshot.usersPager.skip,
this.snapshot.usersQuery).pipe( this.snapshot.usersQuery).pipe(
tap(({ total, items, _links, canCreate }) => { tap(({ total, items, canCreate, _links }) => {
if (isReload) { if (isReload) {
this.dialogs.notifyInfo('Users reloaded.'); this.dialogs.notifyInfo('Users reloaded.');
} }

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

@ -11,6 +11,18 @@
<hr /> <hr />
<div class="sidebar-section">
<h3>Status Queries</h3>
<a class="sidebar-item" *ngFor="let query of contentsState.statusQueries | async; trackBy: trackByQuery" (click)="search(query.filter)"
[class.active]="isSelectedQuery(query.filter)">
{{query.name}}
</a>
</div>
<hr />
<div class="sidebar-section"> <div class="sidebar-section">
<h3>Saved queries</h3> <h3>Saved queries</h3>

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

@ -24,7 +24,7 @@ export class ContentsFiltersPageComponent extends ResourceOwner implements OnIni
public schemaQueries: Queries; public schemaQueries: Queries;
constructor( constructor(
private readonly contentsState: ContentsState, public readonly contentsState: ContentsState,
private readonly schemasState: SchemasState, private readonly schemasState: SchemasState,
private readonly uiState: UIState private readonly uiState: UIState
) { ) {

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

@ -7,7 +7,7 @@
<ng-container menu> <ng-container menu>
<div class="row no-gutters pl-1"> <div class="row no-gutters pl-1">
<div class="col-auto offset-xl-4"> <div class="col-auto ml-8">
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh Contents (CTRL + SHIFT + R)"> <button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh Contents (CTRL + SHIFT + R)">

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

@ -7,10 +7,12 @@
import { AbstractControl } from '@angular/forms'; import { AbstractControl } from '@angular/forms';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { ErrorDto } from './utils/error'; import { ErrorDto } from './utils/error';
import { ResourceLinks } from './utils/hateos'; import { ResourceLinks } from './utils/hateos';
import { Types } from './utils/types'; import { Types } from './utils/types';
import { fullValue } from './angular/forms/forms-helper'; import { fullValue } from './angular/forms/forms-helper';
@ -25,10 +27,10 @@ export class Form<T extends AbstractControl, V> {
private readonly state = new State<FormState>({ submitted: false }); private readonly state = new State<FormState>({ submitted: false });
public submitted = public submitted =
this.state.changes.pipe(map(s => s.submitted)); this.state.project(s => s.submitted);
public error = public error =
this.state.changes.pipe(map(s => s.error)); this.state.project(s => s.error);
constructor( constructor(
public readonly form: T public readonly form: T
@ -173,6 +175,17 @@ export class State<T extends {}> {
return this.state.value; return this.state.value;
} }
public project<R1>(project1: (value: T) => R1, compare?: (x: R1, y: R1) => boolean) {
return this.changes.pipe(
map(x => project1(x)), distinctUntilChanged(compare));
}
public project2<R1, R2>(project1: (value: T) => R1, project2: (value: R1) => R2, compare?: (x: R2, y: R2) => boolean) {
return this.changes.pipe(
map(x => project1(x)), distinctUntilChanged(),
map(x => project2(x)), distinctUntilChanged(compare));
}
constructor(state: Readonly<T>) { constructor(state: Readonly<T>) {
this.initialState = state; this.initialState = state;

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

@ -78,63 +78,19 @@ export class ImmutableArray<T> implements Iterable<T> {
} }
public sortByStringAsc(filter: (a: T) => string): ImmutableArray<T> { public sortByStringAsc(filter: (a: T) => string): ImmutableArray<T> {
return this.sort((a, b) => { return this.sort((a, b) => compareStringsAsc(filter(a), filter(b)));
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return -1;
}
if (av > bv) {
return 1;
}
return 0;
});
} }
public sortByStringDesc(filter: (a: T) => string): ImmutableArray<T> { public sortByStringDesc(filter: (a: T) => string): ImmutableArray<T> {
return this.sort((a, b) => { return this.sort((a, b) => compareStringsDesc(filter(a), filter(b)));
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return 1;
}
if (av > bv) {
return -1;
}
return 0;
});
} }
public sortByNumberAsc(filter: (a: T) => number): ImmutableArray<T> { public sortByNumberAsc(filter: (a: T) => number): ImmutableArray<T> {
return this.sort((a, b) => { return this.sort((a, b) => compareNumbersAsc(filter(a), filter(b)));
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return -1;
}
if (av > bv) {
return 1;
}
return 0;
});
} }
public sortByNumberDesc(filter: (a: T) => number): ImmutableArray<T> { public sortByNumberDesc(filter: (a: T) => number): ImmutableArray<T> {
return this.sort((a, b) => { return this.sort((a, b) => compareNumbersDesc(filter(a), filter(b)));
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return 1;
}
if (av > bv) {
return -1;
}
return 0;
});
} }
public pushFront(...items: T[]): ImmutableArray<T> { public pushFront(...items: T[]): ImmutableArray<T> {
@ -234,4 +190,48 @@ export class ImmutableArray<T> implements Iterable<T> {
public removeBy(field: string, value: T) { public removeBy(field: string, value: T) {
return this.removeAll(x => x[field] === value[field]); return this.removeAll(x => x[field] === value[field]);
} }
}
export function compareStringsAsc(a: string, b: string) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
export function compareStringsDesc(a: string, b: string) {
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
}
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;
} }

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

@ -125,9 +125,9 @@ export class AppLanguagesService {
} }
function parseLanguages(response: any) { function parseLanguages(response: any) {
const items: any[] = response.items; const raw: any[] = response.items;
const languages = items.map(item => const items = raw.map(item =>
new AppLanguageDto(item._links, new AppLanguageDto(item._links,
item.iso2Code, item.iso2Code,
item.englishName, item.englishName,
@ -137,5 +137,5 @@ function parseLanguages(response: any) {
const _links = response._links; const _links = response._links;
return { items: languages, _links, canCreate: hasAnyLink(_links, 'create') }; return { items, _links, canCreate: hasAnyLink(_links, 'create') };
} }

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

@ -149,9 +149,9 @@ export class ClientsService {
} }
function parseClients(response: any): ClientsPayload { function parseClients(response: any): ClientsPayload {
const items: any[] = response.items; const raw: any[] = response.items;
const clients = items.map(item => const items = raw.map(item =>
new ClientDto(item._links, new ClientDto(item._links,
item.id, item.id,
item.name || item.id, item.name || item.id,
@ -160,5 +160,5 @@ function parseClients(response: any): ClientsPayload {
const _links = response._links; const _links = response._links;
return { items: clients, _links, canCreate: hasAnyLink(_links, 'create') }; return { items, _links, canCreate: hasAnyLink(_links, 'create') };
} }

6
src/Squidex/app/shared/services/contents.service.spec.ts

@ -47,11 +47,11 @@ describe('ContentsService', () => {
let contents: ContentsDto; let contents: ContentsDto;
contentsService.getContents('my-app', 'my-schema', 17, 13, undefined, undefined).subscribe(result => { contentsService.getContents('my-app', 'my-schema', 17, 13, undefined, undefined, ['Draft', 'Published']).subscribe(result => {
contents = result; contents = result;
}); });
const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema?$top=17&$skip=13'); const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema?$top=17&$skip=13&status=Draft&status=Published');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
@ -66,7 +66,7 @@ describe('ContentsService', () => {
}); });
expect(contents!).toEqual( expect(contents!).toEqual(
new ContentsDto(10, [ new ContentsDto(['Draft', 'Published'], 10, [
createContent(12), createContent(12),
createContent(13) createContent(13)
])); ]));

23
src/Squidex/app/shared/services/contents.service.ts

@ -35,6 +35,15 @@ export class ScheduleDto {
} }
export class ContentsDto extends ResultSet<ContentDto> { export class ContentsDto extends ResultSet<ContentDto> {
constructor(
public readonly statuses: string[],
total: number,
items: ContentDto[],
links?: ResourceLinks
) {
super(total, items, links);
}
public get canCreate() { public get canCreate() {
return hasAnyLink(this._links, 'create'); return hasAnyLink(this._links, 'create');
} }
@ -89,7 +98,7 @@ export class ContentsService {
) { ) {
} }
public getContents(appName: string, schemaName: string, take: number, skip: number, query?: string, ids?: string[]): Observable<ContentsDto> { public getContents(appName: string, schemaName: string, take: number, skip: number, query?: string, ids?: string[], status?: string[]): Observable<ContentsDto> {
const queryParts: string[] = []; const queryParts: string[] = [];
if (query && query.length > 0) { if (query && query.length > 0) {
@ -114,15 +123,21 @@ export class ContentsService {
queryParts.push(`ids=${ids.join(',')}`); queryParts.push(`ids=${ids.join(',')}`);
} }
if (status) {
for (let s of status) {
queryParts.push(`status=${s}`);
}
}
const fullQuery = queryParts.join('&'); const fullQuery = queryParts.join('&');
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`);
return this.http.get<{ total: number, items: [] } & Resource>(url).pipe( return this.http.get<{ total: number, items: [], statuses: string[] } & Resource>(url).pipe(
map(({ total, items, _links }) => { map(({ total, items, statuses, _links }) => {
const contents = items.map(x => parseContent(x)); const contents = items.map(x => parseContent(x));
return new ContentsDto(total, contents, _links); return new ContentsDto(statuses, total, contents, _links);
}), }),
pretifyError('Failed to load contents. Please reload.')); pretifyError('Failed to load contents. Please reload.'));
} }

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

@ -107,14 +107,14 @@ export class ContributorsService {
} }
function parseContributors(response: any) { function parseContributors(response: any) {
const items: any[] = response.items; const raw: any[] = response.items;
const contributors = items.map(item => const items = raw.map(item =>
new ContributorDto(item._links, new ContributorDto(item._links,
item.contributorId, item.contributorId,
item.role)); item.role));
const { maxContributors, _links, _meta } = response; const { maxContributors, _links, _meta } = response;
return { items: contributors, maxContributors, _links, _meta, canCreate: hasAnyLink(_links, 'create') }; return { items, maxContributors, _links, _meta, canCreate: hasAnyLink(_links, 'create') };
} }

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

@ -122,9 +122,9 @@ export class PatternsService {
} }
function parsePatterns(response: any) { function parsePatterns(response: any) {
const items: any[] = response.items; const raw: any[] = response.items;
const patterns = items.map(item => const items = raw.map(item =>
new PatternDto(item._links, new PatternDto(item._links,
item.id, item.id,
item.name, item.name,
@ -133,5 +133,5 @@ function parsePatterns(response: any) {
const _links = response._links; const _links = response._links;
return { items: patterns, _links, canCreate: hasAnyLink(_links, 'create') }; return { items, _links, canCreate: hasAnyLink(_links, 'create') };
} }

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

@ -130,9 +130,9 @@ export class RolesService {
} }
export function parseRoles(response: any) { export function parseRoles(response: any) {
const items: any[] = response.items; const raw: any[] = response.items;
const roles = items.map(item => const items = raw.map(item =>
new RoleDto(item._links, new RoleDto(item._links,
item.name, item.name,
item.numClients, item.numClients,
@ -142,5 +142,5 @@ export function parseRoles(response: any) {
const _links = response._links; const _links = response._links;
return { items: roles, _links, canCreate: hasAnyLink(_links, 'create') }; return { items, _links, canCreate: hasAnyLink(_links, 'create') };
} }

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

@ -518,9 +518,9 @@ export class SchemasService {
} }
function parseSchemas(response: any) { function parseSchemas(response: any) {
const items: any[] = response.items; const raw: any[] = response.items;
const schemas = items.map(item => const items = raw.map(item =>
new SchemaDto(item._links, new SchemaDto(item._links,
item.id, item.id,
item.name, item.name,
@ -534,7 +534,7 @@ function parseSchemas(response: any) {
const _links = response._links; const _links = response._links;
return { items: schemas, _links, canCreate: hasAnyLink(_links, 'create') }; return { items, _links, canCreate: hasAnyLink(_links, 'create') };
} }
function parseSchemaWithDetails(response: any, version: Version) { function parseSchemaWithDetails(response: any, version: Version) {

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

@ -44,18 +44,16 @@ export class AppsState extends State<Snapshot> {
return this.snapshot.selectedApp; return this.snapshot.selectedApp;
} }
public apps =
this.project(s => s.apps);
public selectedApp = public selectedApp =
this.changes.pipe(map(s => s.selectedApp), this.project(s => s.selectedApp, sameApp);
distinctUntilChanged(sameApp));
public selectedValidApp = public selectedValidApp =
this.selectedApp.pipe(filter(x => !!x), map(x => <AppDto>x), this.selectedApp.pipe(filter(x => !!x), map(x => <AppDto>x),
distinctUntilChanged()); distinctUntilChanged());
public apps =
this.changes.pipe(map(s => s.apps),
distinctUntilChanged());
constructor( constructor(
private readonly appsService: AppsService, private readonly appsService: AppsService,
private readonly dialogs: DialogService private readonly dialogs: DialogService

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { distinctUntilChanged, map, publishReplay, refCount, takeUntil } from 'rxjs/operators'; import { map, publishReplay, refCount, takeUntil } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -51,8 +51,7 @@ type UploadResult = AssetDto | number;
@Injectable() @Injectable()
export class AssetUploaderState extends State<Snapshot> { export class AssetUploaderState extends State<Snapshot> {
public uploads = public uploads =
this.changes.pipe(map(x => x.uploads), this.project(x => x.uploads);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,

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

@ -7,9 +7,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { combineLatest, Observable } from 'rxjs'; import { combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
compareStringsAsc,
DialogService, DialogService,
ImmutableArray, ImmutableArray,
Pager, Pager,
@ -43,32 +44,25 @@ interface Snapshot {
@Injectable() @Injectable()
export class AssetsState extends State<Snapshot> { export class AssetsState extends State<Snapshot> {
public tags = public tags =
this.changes.pipe(map(x => x.tags), this.project2(x => x.tags, x => sort(x));
distinctUntilChanged(), map(x => sort(x)));
public tagsNames = public tagsNames =
this.tags.pipe( this.project2(x => x.tags, x => Object.keys(x));
distinctUntilChanged(), map(x => x.map(t => t.name)));
public selectedTagNames = public selectedTagNames =
this.changes.pipe( this.project2(x => x.tagsSelected, x => Object.keys(x));
distinctUntilChanged(), map(x => Object.keys(x.tagsSelected)));
public assets = public assets =
this.changes.pipe(map(x => x.assets), this.project(x => x.assets);
distinctUntilChanged());
public assetsQuery = public assetsQuery =
this.changes.pipe(map(x => x.assetsQuery), this.project(x => x.assetsQuery);
distinctUntilChanged());
public assetsPager = public assetsPager =
this.changes.pipe(map(x => x.assetsPager), this.project(x => x.assetsPager);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -251,17 +245,7 @@ function removeTags(previous: AssetDto, tags: { [x: string]: number; }, tagsSele
} }
function sort(tags: { [name: string]: number }) { function sort(tags: { [name: string]: number }) {
return Object.keys(tags).sort((a, b) => { return Object.keys(tags).sort(compareStringsAsc).map(name => ({ name, count: tags[name] }));
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}).map(key => {
return { name: key, count: tags[key] };
});
} }
@Injectable() @Injectable()

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -40,20 +40,16 @@ type BackupsList = ImmutableArray<BackupDto>;
@Injectable() @Injectable()
export class BackupsState extends State<Snapshot> { export class BackupsState extends State<Snapshot> {
public backups = public backups =
this.changes.pipe(map(x => x.backups), this.project(x => x.backups);
distinctUntilChanged());
public maxBackupsReached = public maxBackupsReached =
this.changes.pipe(map(x => x.backups.length >= 10), this.project(x => x.backups.length >= 10);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -69,7 +65,7 @@ export class BackupsState extends State<Snapshot> {
} }
return this.backupsService.getBackups(this.appName).pipe( return this.backupsService.getBackups(this.appName).pipe(
tap(({ items, _links, canCreate }) => { tap(({ items, canCreate, _links }) => {
if (isReload && !silent) { if (isReload && !silent) {
this.dialogs.notifyInfo('Backups reloaded.'); this.dialogs.notifyInfo('Backups reloaded.');
} }
@ -77,7 +73,7 @@ export class BackupsState extends State<Snapshot> {
this.next(s => { this.next(s => {
return { ...s, backups, isLoaded: true, _links, canCreate }; return { ...s, backups, isLoaded: true, canCreate, _links };
}); });
}), }),
shareSubscribed(this.dialogs, { silent })); shareSubscribed(this.dialogs, { silent }));

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

@ -9,7 +9,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -52,16 +52,13 @@ type ClientsList = ImmutableArray<ClientDto>;
@Injectable() @Injectable()
export class ClientsState extends State<Snapshot> { export class ClientsState extends State<Snapshot> {
public clients = public clients =
this.changes.pipe(map(x => x.clients), this.project(x => x.clients);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly clientsService: ClientsService, private readonly clientsService: ClientsService,
@ -112,12 +109,12 @@ export class ClientsState extends State<Snapshot> {
} }
private replaceClients(payload: ClientsPayload, version: Version) { private replaceClients(payload: ClientsPayload, version: Version) {
const clients = ImmutableArray.of(payload.items); const { canCreate, items, _links } = payload;
const { _links, canCreate } = payload; const clients = ImmutableArray.of(items);
this.next(s => { this.next(s => {
return { ...s, clients, isLoaded: true, version, _links, canCreate }; return { ...s, clients, isLoaded: true, version, canCreate, _links };
}); });
} }

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

@ -6,7 +6,7 @@
*/ */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { import {
DateTime, DateTime,
@ -35,12 +35,10 @@ type CommentsList = ImmutableArray<CommentDto>;
export class CommentsState extends State<Snapshot> { export class CommentsState extends State<Snapshot> {
public comments = public comments =
this.changes.pipe(map(x => x.comments), this.project(x => x.comments);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; import { catchError, switchMap, tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -40,6 +40,9 @@ interface Snapshot {
// Indicates if the contents are loaded. // Indicates if the contents are loaded.
isLoaded?: boolean; isLoaded?: boolean;
// The statuses.
statuses?: string[];
// The selected content. // The selected content.
selectedContent?: ContentDto | null; selectedContent?: ContentDto | null;
@ -59,36 +62,31 @@ function sameContent(lhs: ContentDto, rhs?: ContentDto): boolean {
export abstract class ContentsStateBase extends State<Snapshot> { export abstract class ContentsStateBase extends State<Snapshot> {
public selectedContent = public selectedContent =
this.changes.pipe(map(x => x.selectedContent), this.project(x => x.selectedContent, sameContent);
distinctUntilChanged(sameContent));
public contents = public contents =
this.changes.pipe(map(x => x.contents), this.project(x => x.contents);
distinctUntilChanged());
public contentsPager = public contentsPager =
this.changes.pipe(map(x => x.contentsPager), this.project(x => x.contentsPager);
distinctUntilChanged());
public contentsQuery = public contentsQuery =
this.changes.pipe(map(x => x.contentsQuery), this.project(x => x.contentsQuery);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreateAny =
this.changes.pipe(map(x => !!x.canCreate || !!x.canCreateAndPublish),
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
public canCreateAndPublish = public canCreateAndPublish =
this.changes.pipe(map(x => !!x.canCreateAndPublish), this.project(x => !!x.canCreateAndPublish);
distinctUntilChanged());
public canCreateAny =
this.project(x => !!x.canCreate || !!x.canCreateAndPublish);
public statusQueries =
this.project2(x => x.statuses, x => buildQueries(x));
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -139,7 +137,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.snapshot.contentsPager.pageSize, this.snapshot.contentsPager.pageSize,
this.snapshot.contentsPager.skip, this.snapshot.contentsPager.skip,
this.snapshot.contentsQuery, undefined).pipe( this.snapshot.contentsQuery, undefined).pipe(
tap(({ total, items, _links, canCreate, canCreateAndPublish }) => { tap(({ total, items, canCreate, canCreateAndPublish, statuses, _links }) => {
if (isReload) { if (isReload) {
this.dialogs.notifyInfo('Contents reloaded.'); this.dialogs.notifyInfo('Contents reloaded.');
} }
@ -148,11 +146,14 @@ export abstract class ContentsStateBase extends State<Snapshot> {
const contents = ImmutableArray.of(items); const contents = ImmutableArray.of(items);
const contentsPager = s.contentsPager.setCount(total); const contentsPager = s.contentsPager.setCount(total);
statuses = s.statuses || statuses;
let selectedContent = s.selectedContent; let selectedContent = s.selectedContent;
if (selectedContent) { if (selectedContent) {
selectedContent = contents.find(x => x.id === selectedContent!.id) || selectedContent; selectedContent = contents.find(x => x.id === selectedContent!.id) || selectedContent;
} }
return { ...s, return { ...s,
canCreate, canCreate,
canCreateAndPublish, canCreateAndPublish,
@ -160,6 +161,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
contentsPager, contentsPager,
isLoaded: true, isLoaded: true,
selectedContent, selectedContent,
statuses,
_links _links
}; };
}); });
@ -349,4 +351,12 @@ export class ManualContentsState extends ContentsStateBase {
protected get schemaName() { protected get schemaName() {
return this.schema.name; return this.schema.name;
} }
} }
function buildQueries(x: string[] | undefined): { name: string; filter: string; }[] {
return x ? x.map(s => buildQuery(s)) : [];
}
function buildQuery(s: string) {
return ({ name: s, filter: `$filter=status eq '${s}'` });
}

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

@ -53,7 +53,6 @@ describe('ContributorsState', () => {
contributorsState.load().subscribe(); contributorsState.load().subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items); expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.isLoaded).toBeTruthy(); expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors); expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
expect(contributorsState.snapshot.version).toEqual(version); expect(contributorsState.snapshot.version).toEqual(version);

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -34,9 +34,6 @@ interface Snapshot {
// All loaded contributors. // All loaded contributors.
contributors: ContributorsList; contributors: ContributorsList;
// Indicates if the maximum number of contributors are reached.
isMaxReached?: boolean;
// Indicates if the contributors are loaded. // Indicates if the contributors are loaded.
isLoaded?: boolean; isLoaded?: boolean;
@ -58,20 +55,16 @@ type ContributorsList = ImmutableArray<ContributorDto>;
@Injectable() @Injectable()
export class ContributorsState extends State<Snapshot> { export class ContributorsState extends State<Snapshot> {
public contributors = public contributors =
this.changes.pipe(map(x => x.contributors), this.project(x => x.contributors);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public maxContributors = public maxContributors =
this.changes.pipe(map(x => x.maxContributors), this.project(x => x.maxContributors);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly contributorsService: ContributorsService, private readonly contributorsService: ContributorsService,
@ -122,16 +115,11 @@ export class ContributorsState extends State<Snapshot> {
private replaceContributors(version: Version, payload: ContributorsPayload) { private replaceContributors(version: Version, payload: ContributorsPayload) {
this.next(s => { this.next(s => {
const maxContributors = payload.maxContributors || s.maxContributors; const { canCreate, items, maxContributors, _links } = payload;
const isLoaded = true;
const isMaxReached = maxContributors > 0 && maxContributors <= payload.items.length;
const contributors = ImmutableArray.of(payload.items);
const { _links, canCreate } = payload; const contributors = ImmutableArray.of(items);
return { ...s, contributors, maxContributors, isLoaded, isMaxReached, version: version, _links, canCreate }; return { ...s, contributors, maxContributors, isLoaded: true, version, canCreate, _links };
}); });
} }

16
src/Squidex/app/shared/state/filter.state.ts

@ -6,7 +6,7 @@
*/ */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/internal/operators'; import { distinctUntilChanged } from 'rxjs/internal/operators';
import { State, Types } from '@app/framework'; import { State, Types } from '@app/framework';
@ -45,20 +45,16 @@ export class FilterState extends State<Snapshot> {
private readonly sortModes: { [key: string]: Observable<Sorting> } = {}; private readonly sortModes: { [key: string]: Observable<Sorting> } = {};
public query = public query =
this.changes.pipe(map(x => x.query), this.project(x => x.query);
distinctUntilChanged());
public order = public order =
this.changes.pipe(map(x => x.order), this.project(x => x.order);
distinctUntilChanged());
public filter = public filter =
this.changes.pipe(map(x => x.filter), this.project(x => x.filter);
distinctUntilChanged());
public fullText = public fullText =
this.changes.pipe(map(x => x.fullText), this.project(x => x.fullText);
distinctUntilChanged());
public get apiFilter() { public get apiFilter() {
return this.snapshot.query; return this.snapshot.query;
@ -74,7 +70,7 @@ export class FilterState extends State<Snapshot> {
let result = this.sortModes[key]; let result = this.sortModes[key];
if (!result) { if (!result) {
result = this.changes.pipe(map(x => sortMode(x, field)), distinctUntilChanged()); result = this.project(x => sortMode(x, field)), distinctUntilChanged();
this.sortModes[key] = result; this.sortModes[key] = result;
} }

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { forkJoin, Observable } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -41,9 +41,6 @@ interface SnapshotLanguage {
} }
interface Snapshot { interface Snapshot {
// the configured languages as plan format.
plainLanguages: AppLanguagesList;
// All supported languages. // All supported languages.
allLanguages: LanguageList; allLanguages: LanguageList;
@ -73,20 +70,16 @@ type LanguageResultList = ImmutableArray<SnapshotLanguage>;
@Injectable() @Injectable()
export class LanguagesState extends State<Snapshot> { export class LanguagesState extends State<Snapshot> {
public languages = public languages =
this.changes.pipe(map(x => x.languages), this.project(x => x.languages);
distinctUntilChanged());
public newLanguages = public newLanguages =
this.changes.pipe(map(x => x.allLanguagesNew), this.project(x => x.allLanguagesNew);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly appLanguagesService: AppLanguagesService, private readonly appLanguagesService: AppLanguagesService,
@ -95,7 +88,6 @@ export class LanguagesState extends State<Snapshot> {
private readonly languagesService: LanguagesService private readonly languagesService: LanguagesService
) { ) {
super({ super({
plainLanguages: ImmutableArray.empty(),
allLanguages: ImmutableArray.empty(), allLanguages: ImmutableArray.empty(),
allLanguagesNew: ImmutableArray.empty(), allLanguagesNew: ImmutableArray.empty(),
languages: ImmutableArray.empty(), languages: ImmutableArray.empty(),
@ -154,15 +146,14 @@ export class LanguagesState extends State<Snapshot> {
this.next(s => { this.next(s => {
allLanguages = allLanguages || s.allLanguages; allLanguages = allLanguages || s.allLanguages;
const languages = ImmutableArray.of(payload.items); const { canCreate, items, _links } = payload;
const { _links, canCreate } = payload; const languages = ImmutableArray.of(items);
return { return {
...s, ...s,
canCreate, canCreate,
languages: languages.map(x => this.createLanguage(x, languages)), languages: languages.map(x => this.createLanguage(x, languages)),
plainLanguages: payload,
allLanguages: allLanguages, allLanguages: allLanguages,
allLanguagesNew: allLanguages.filter(x => !languages.find(l => l.iso2Code === x.iso2Code)), allLanguagesNew: allLanguages.filter(x => !languages.find(l => l.iso2Code === x.iso2Code)),
isLoaded: true, isLoaded: true,

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -42,7 +42,7 @@ interface Snapshot {
canCreate?: boolean; canCreate?: boolean;
// The links. // The links.
links: ResourceLinks; _links: ResourceLinks;
} }
type PatternsList = ImmutableArray<PatternDto>; type PatternsList = ImmutableArray<PatternDto>;
@ -50,23 +50,20 @@ type PatternsList = ImmutableArray<PatternDto>;
@Injectable() @Injectable()
export class PatternsState extends State<Snapshot> { export class PatternsState extends State<Snapshot> {
public patterns = public patterns =
this.changes.pipe(map(x => x.patterns), this.project(x => x.patterns);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly patternsService: PatternsService, private readonly patternsService: PatternsService,
private readonly appsState: AppsState, private readonly appsState: AppsState,
private readonly dialogs: DialogService private readonly dialogs: DialogService
) { ) {
super({ patterns: ImmutableArray.empty(), version: Version.EMPTY, links: {} }); super({ patterns: ImmutableArray.empty(), version: Version.EMPTY, _links: {} });
} }
public load(isReload = false): Observable<any> { public load(isReload = false): Observable<any> {
@ -115,7 +112,7 @@ export class PatternsState extends State<Snapshot> {
const { _links: links, canCreate } = payload; const { _links: links, canCreate } = payload;
this.next(s => { this.next(s => {
return { ...s, patterns, isLoaded: true, version, links, canCreate }; return { ...s, patterns, isLoaded: true, version, _links: links, canCreate };
}); });
} }

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -52,24 +52,19 @@ interface Snapshot {
@Injectable() @Injectable()
export class PlansState extends State<Snapshot> { export class PlansState extends State<Snapshot> {
public plans = public plans =
this.changes.pipe(map(x => x.plans), this.project(x => x.plans);
distinctUntilChanged());
public isOwner = public isOwner =
this.changes.pipe(map(x => !!x.isOwner), this.project(x => !!x.isOwner);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public isDisabled = public isDisabled =
this.changes.pipe(map(x => !x.isOwner), this.project(x => !x.isOwner);
distinctUntilChanged());
public hasPortal = public hasPortal =
this.changes.pipe(map(x => x.hasPortal), this.project(x => x.hasPortal);
distinctUntilChanged());
public window = window; public window = window;

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -50,16 +50,13 @@ type RolesList = ImmutableArray<RoleDto>;
@Injectable() @Injectable()
export class RolesState extends State<Snapshot> { export class RolesState extends State<Snapshot> {
public roles = public roles =
this.changes.pipe(map(x => x.roles), this.project(x => x.roles);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly rolesService: RolesService, private readonly rolesService: RolesService,
@ -110,12 +107,12 @@ export class RolesState extends State<Snapshot> {
} }
private replaceRoles(payload: RolesPayload, version: Version) { private replaceRoles(payload: RolesPayload, version: Version) {
const roles = ImmutableArray.of(payload.items); const { canCreate, items, _links } = payload;
const { _links, canCreate } = payload; const roles = ImmutableArray.of(items);
this.next(s => { this.next(s => {
return { ...s, roles, isLoaded: true, version, _links, canCreate }; return { ...s, roles, isLoaded: true, version, canCreate, _links };
}); });
} }

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -35,16 +35,13 @@ interface Snapshot {
@Injectable() @Injectable()
export class RuleEventsState extends State<Snapshot> { export class RuleEventsState extends State<Snapshot> {
public ruleEvents = public ruleEvents =
this.changes.pipe(map(x => x.ruleEvents), this.project(x => x.ruleEvents);
distinctUntilChanged());
public ruleEventsPager = public ruleEventsPager =
this.changes.pipe(map(x => x.ruleEventsPager), this.project(x => x.ruleEventsPager);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
@ -47,20 +47,16 @@ type RulesList = ImmutableArray<RuleDto>;
@Injectable() @Injectable()
export class RulesState extends State<Snapshot> { export class RulesState extends State<Snapshot> {
public rules = public rules =
this.changes.pipe(map(x => x.rules), this.project(x => x.rules);
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
public canReadEvents = public canReadEvents =
this.changes.pipe(map(x => !!x.canReadEvents), this.project(x => !!x.canReadEvents);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -76,7 +72,7 @@ export class RulesState extends State<Snapshot> {
} }
return this.rulesService.getRules(this.appName).pipe( return this.rulesService.getRules(this.appName).pipe(
tap(({ items, _links, canCreate, canReadEvents }) => { tap(({ items, canCreate, canReadEvents, _links }) => {
if (isReload) { if (isReload) {
this.dialogs.notifyInfo('Rules reloaded.'); this.dialogs.notifyInfo('Rules reloaded.');
} }
@ -84,7 +80,7 @@ export class RulesState extends State<Snapshot> {
this.next(s => { this.next(s => {
const rules = ImmutableArray.of(items); const rules = ImmutableArray.of(items);
return { ...s, rules, isLoaded: true, _links, canCreate, canReadEvents }; return { ...s, rules, isLoaded: true, canCreate, canReadEvents, _links };
}); });
}), }),
shareSubscribed(this.dialogs)); shareSubscribed(this.dialogs));

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

@ -7,9 +7,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import { import {
compareStringsAsc,
DialogService, DialogService,
ImmutableArray, ImmutableArray,
ResourceLinks, ResourceLinks,
@ -58,6 +59,8 @@ interface Snapshot {
export type SchemasList = ImmutableArray<SchemaDto>; export type SchemasList = ImmutableArray<SchemaDto>;
export type Categories = { [name: string]: boolean };
function sameSchema(lhs: SchemaDetailsDto | null, rhs?: SchemaDetailsDto | null): boolean { function sameSchema(lhs: SchemaDetailsDto | null, rhs?: SchemaDetailsDto | null): boolean {
return lhs === rhs || (!!lhs && !!rhs && lhs.id === rhs.id && lhs.version === rhs.version); return lhs === rhs || (!!lhs && !!rhs && lhs.id === rhs.id && lhs.version === rhs.version);
} }
@ -68,29 +71,23 @@ export class SchemasState extends State<Snapshot> {
return this.snapshot.selectedSchema ? this.snapshot.selectedSchema.name : ''; return this.snapshot.selectedSchema ? this.snapshot.selectedSchema.name : '';
} }
public selectedSchema =
this.changes.pipe(map(x => x.selectedSchema),
distinctUntilChanged(sameSchema));
public categories = public categories =
this.changes.pipe(map(x => ImmutableArray.of(Object.keys(x.categories)).sortByStringAsc(s => s)), this.project2(x => x.categories, x => sortedCategoryNames(x));
distinctUntilChanged());
public selectedSchema =
this.project(x => x.selectedSchema, sameSchema);
public schemas = public schemas =
this.changes.pipe(map(x => x.schemas), this.project(x => x.schemas);
distinctUntilChanged());
public publishedSchemas = public publishedSchemas =
this.changes.pipe(map(x => x.schemas.filter(s => s.isPublished)), this.project2(x => x.schemas, x => x.filter(s => s.isPublished));
distinctUntilChanged());
public isLoaded = public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded), this.project(x => !!x.isLoaded);
distinctUntilChanged());
public canCreate = public canCreate =
this.changes.pipe(map(x => !!x.canCreate), this.project(x => !!x.canCreate);
distinctUntilChanged());
constructor( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -125,19 +122,17 @@ export class SchemasState extends State<Snapshot> {
} }
return this.schemasService.getSchemas(this.appName).pipe( return this.schemasService.getSchemas(this.appName).pipe(
tap(payload => { tap(({ items, canCreate, _links }) => {
if (isReload) { if (isReload) {
this.dialogs.notifyInfo('Schemas reloaded.'); this.dialogs.notifyInfo('Schemas reloaded.');
} }
return this.next(s => { return this.next(s => {
const { _links, canCreate, items } = payload;
const schemas = ImmutableArray.of(items).sortByStringAsc(x => x.displayName); const schemas = ImmutableArray.of(items).sortByStringAsc(x => x.displayName);
const categories = buildCategories(s.categories, schemas); const categories = buildCategories(s.categories, schemas);
return { ...s, schemas, isLoaded: true, categories, _links, canCreate }; return { ...s, schemas, isLoaded: true, categories, canCreate, _links };
}); });
}), }),
shareSubscribed(this.dialogs)); shareSubscribed(this.dialogs));
@ -357,7 +352,7 @@ function buildCategories(categories: { [name: string]: boolean }, schemas?: Sche
return categories; return categories;
} }
function addCategory(categories: { [name: string]: boolean }, category: string) { function addCategory(categories: Categories, category: string) {
categories = { ...categories }; categories = { ...categories };
categories[category] = true; categories[category] = true;
@ -365,10 +360,18 @@ function addCategory(categories: { [name: string]: boolean }, category: string)
return categories; return categories;
} }
function removeCategory(categories: { [name: string]: boolean }, category: string) { function removeCategory(categories: Categories, category: string) {
categories = { ...categories }; categories = { ...categories };
delete categories[category]; delete categories[category];
return categories; return categories;
}
function sortedCategoryNames(categories: Categories) {
const names = Object.keys(categories);
names.sort(compareStringsAsc);
return names;
} }

15
src/Squidex/app/shared/state/ui.state.ts

@ -46,24 +46,19 @@ interface Snapshot {
@Injectable() @Injectable()
export class UIState extends State<Snapshot> { export class UIState extends State<Snapshot> {
public settings = public settings =
this.changes.pipe(map(x => x.settings), this.project(x => x.settings);
distinctUntilChanged());
public canReadEvents = public canReadEvents =
this.changes.pipe(map(x => !!x.canReadEvents), this.project(x => !!x.canReadEvents);
distinctUntilChanged());
public canReadUsers = public canReadUsers =
this.changes.pipe(map(x => !!x.canReadUsers), this.project(x => !!x.canReadUsers);
distinctUntilChanged());
public canRestore = public canRestore =
this.changes.pipe(map(x => !!x.canRestore), this.project(x => !!x.canRestore);
distinctUntilChanged());
public canUserAdminResource = public canUserAdminResource =
this.changes.pipe(map(x => !!x.canRestore || !!x.canReadUsers || !!x.canReadEvents), this.project(x => !!x.canRestore || !!x.canReadUsers || !!x.canReadEvents);
distinctUntilChanged());
public get<T>(path: string, defaultValue: T) { public get<T>(path: string, defaultValue: T) {
return this.settings.pipe(map(x => this.getValue(x, path, defaultValue)), return this.settings.pipe(map(x => this.getValue(x, path, defaultValue)),

Loading…
Cancel
Save