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 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)
{
@ -159,7 +159,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context();
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))
{

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

@ -30,6 +30,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
[Required]
public ContentDto[] Items { get; set; }
/// <summary>
/// The possible statuses.
/// </summary>
[Required]
public string[] Statuses { get; set; }
public string ToEtag()
{
return Items.ToManyEtag(Total);
@ -40,24 +46,18 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
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
{
Total = contents.Count,
Items = contents.Select(x => ContentDto.FromContent(x, context, controller, app, schema)).ToArray()
Total = total,
Items = contents.Select(x => ContentDto.FromContent(x, context, controller, app, schema)).ToArray(),
};
return result.CreateLinks(controller, app, schema);
}
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()
};
result.Statuses = new string[] { "Archived", "Draft", "Published" };
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 { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
DialogService,
@ -31,12 +31,10 @@ type EventConsumersList = ImmutableArray<EventConsumerDto>;
@Injectable()
export class EventConsumersState extends State<Snapshot> {
public eventConsumers =
this.changes.pipe(map(x => x.eventConsumers),
distinctUntilChanged());
this.project(x => x.eventConsumers);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
constructor(
private readonly dialogs: DialogService,

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

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

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

@ -11,6 +11,18 @@
<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">
<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;
constructor(
private readonly contentsState: ContentsState,
public readonly contentsState: ContentsState,
private readonly schemasState: SchemasState,
private readonly uiState: UIState
) {

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

@ -7,7 +7,7 @@
<ng-container menu>
<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>
<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 { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { ErrorDto } from './utils/error';
import { ResourceLinks } from './utils/hateos';
import { Types } from './utils/types';
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 });
public submitted =
this.state.changes.pipe(map(s => s.submitted));
this.state.project(s => s.submitted);
public error =
this.state.changes.pipe(map(s => s.error));
this.state.project(s => s.error);
constructor(
public readonly form: T
@ -173,6 +175,17 @@ export class State<T extends {}> {
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>) {
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> {
return this.sort((a, b) => {
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return -1;
}
if (av > bv) {
return 1;
}
return 0;
});
return this.sort((a, b) => compareStringsAsc(filter(a), filter(b)));
}
public sortByStringDesc(filter: (a: T) => string): ImmutableArray<T> {
return this.sort((a, b) => {
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return 1;
}
if (av > bv) {
return -1;
}
return 0;
});
return this.sort((a, b) => compareStringsDesc(filter(a), filter(b)));
}
public sortByNumberAsc(filter: (a: T) => number): ImmutableArray<T> {
return this.sort((a, b) => {
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return -1;
}
if (av > bv) {
return 1;
}
return 0;
});
return this.sort((a, b) => compareNumbersAsc(filter(a), filter(b)));
}
public sortByNumberDesc(filter: (a: T) => number): ImmutableArray<T> {
return this.sort((a, b) => {
const av = filter(a);
const bv = filter(b);
if (av < bv) {
return 1;
}
if (av > bv) {
return -1;
}
return 0;
});
return this.sort((a, b) => compareNumbersDesc(filter(a), filter(b)));
}
public pushFront(...items: T[]): ImmutableArray<T> {
@ -234,4 +190,48 @@ export class ImmutableArray<T> implements Iterable<T> {
public removeBy(field: string, value: T) {
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) {
const items: any[] = response.items;
const raw: any[] = response.items;
const languages = items.map(item =>
const items = raw.map(item =>
new AppLanguageDto(item._links,
item.iso2Code,
item.englishName,
@ -137,5 +137,5 @@ function parseLanguages(response: any) {
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 {
const items: any[] = response.items;
const raw: any[] = response.items;
const clients = items.map(item =>
const items = raw.map(item =>
new ClientDto(item._links,
item.id,
item.name || item.id,
@ -160,5 +160,5 @@ function parseClients(response: any): ClientsPayload {
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;
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;
});
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.headers.get('If-Match')).toBeNull();
@ -66,7 +66,7 @@ describe('ContentsService', () => {
});
expect(contents!).toEqual(
new ContentsDto(10, [
new ContentsDto(['Draft', 'Published'], 10, [
createContent(12),
createContent(13)
]));

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

@ -35,6 +35,15 @@ export class ScheduleDto {
}
export class ContentsDto extends ResultSet<ContentDto> {
constructor(
public readonly statuses: string[],
total: number,
items: ContentDto[],
links?: ResourceLinks
) {
super(total, items, links);
}
public get canCreate() {
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[] = [];
if (query && query.length > 0) {
@ -114,15 +123,21 @@ export class ContentsService {
queryParts.push(`ids=${ids.join(',')}`);
}
if (status) {
for (let s of status) {
queryParts.push(`status=${s}`);
}
}
const fullQuery = queryParts.join('&');
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`);
return this.http.get<{ total: number, items: [] } & Resource>(url).pipe(
map(({ total, items, _links }) => {
return this.http.get<{ total: number, items: [], statuses: string[] } & Resource>(url).pipe(
map(({ total, items, statuses, _links }) => {
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.'));
}

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

@ -107,14 +107,14 @@ export class ContributorsService {
}
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,
item.contributorId,
item.role));
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) {
const items: any[] = response.items;
const raw: any[] = response.items;
const patterns = items.map(item =>
const items = raw.map(item =>
new PatternDto(item._links,
item.id,
item.name,
@ -133,5 +133,5 @@ function parsePatterns(response: any) {
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) {
const items: any[] = response.items;
const raw: any[] = response.items;
const roles = items.map(item =>
const items = raw.map(item =>
new RoleDto(item._links,
item.name,
item.numClients,
@ -142,5 +142,5 @@ export function parseRoles(response: any) {
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) {
const items: any[] = response.items;
const raw: any[] = response.items;
const schemas = items.map(item =>
const items = raw.map(item =>
new SchemaDto(item._links,
item.id,
item.name,
@ -534,7 +534,7 @@ function parseSchemas(response: any) {
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) {

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

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

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

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

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

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

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

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

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

@ -9,7 +9,7 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
DialogService,
@ -52,16 +52,13 @@ type ClientsList = ImmutableArray<ClientDto>;
@Injectable()
export class ClientsState extends State<Snapshot> {
public clients =
this.changes.pipe(map(x => x.clients),
distinctUntilChanged());
this.project(x => x.clients);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
public canCreate =
this.changes.pipe(map(x => !!x.canCreate),
distinctUntilChanged());
this.project(x => !!x.canCreate);
constructor(
private readonly clientsService: ClientsService,
@ -112,12 +109,12 @@ export class ClientsState extends State<Snapshot> {
}
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 => {
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 { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { map, tap } from 'rxjs/operators';
import {
DateTime,
@ -35,12 +35,10 @@ type CommentsList = ImmutableArray<CommentDto>;
export class CommentsState extends State<Snapshot> {
public comments =
this.changes.pipe(map(x => x.comments),
distinctUntilChanged());
this.project(x => x.comments);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
constructor(
private readonly appsState: AppsState,

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
import { catchError, switchMap, tap } from 'rxjs/operators';
import {
DialogService,
@ -40,6 +40,9 @@ interface Snapshot {
// Indicates if the contents are loaded.
isLoaded?: boolean;
// The statuses.
statuses?: string[];
// The selected content.
selectedContent?: ContentDto | null;
@ -59,36 +62,31 @@ function sameContent(lhs: ContentDto, rhs?: ContentDto): boolean {
export abstract class ContentsStateBase extends State<Snapshot> {
public selectedContent =
this.changes.pipe(map(x => x.selectedContent),
distinctUntilChanged(sameContent));
this.project(x => x.selectedContent, sameContent);
public contents =
this.changes.pipe(map(x => x.contents),
distinctUntilChanged());
this.project(x => x.contents);
public contentsPager =
this.changes.pipe(map(x => x.contentsPager),
distinctUntilChanged());
this.project(x => x.contentsPager);
public contentsQuery =
this.changes.pipe(map(x => x.contentsQuery),
distinctUntilChanged());
this.project(x => x.contentsQuery);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
public canCreateAny =
this.changes.pipe(map(x => !!x.canCreate || !!x.canCreateAndPublish),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
public canCreate =
this.changes.pipe(map(x => !!x.canCreate),
distinctUntilChanged());
this.project(x => !!x.canCreate);
public canCreateAndPublish =
this.changes.pipe(map(x => !!x.canCreateAndPublish),
distinctUntilChanged());
this.project(x => !!x.canCreateAndPublish);
public canCreateAny =
this.project(x => !!x.canCreate || !!x.canCreateAndPublish);
public statusQueries =
this.project2(x => x.statuses, x => buildQueries(x));
constructor(
private readonly appsState: AppsState,
@ -139,7 +137,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.snapshot.contentsPager.pageSize,
this.snapshot.contentsPager.skip,
this.snapshot.contentsQuery, undefined).pipe(
tap(({ total, items, _links, canCreate, canCreateAndPublish }) => {
tap(({ total, items, canCreate, canCreateAndPublish, statuses, _links }) => {
if (isReload) {
this.dialogs.notifyInfo('Contents reloaded.');
}
@ -148,11 +146,14 @@ export abstract class ContentsStateBase extends State<Snapshot> {
const contents = ImmutableArray.of(items);
const contentsPager = s.contentsPager.setCount(total);
statuses = s.statuses || statuses;
let selectedContent = s.selectedContent;
if (selectedContent) {
selectedContent = contents.find(x => x.id === selectedContent!.id) || selectedContent;
}
return { ...s,
canCreate,
canCreateAndPublish,
@ -160,6 +161,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
contentsPager,
isLoaded: true,
selectedContent,
statuses,
_links
};
});
@ -349,4 +351,12 @@ export class ManualContentsState extends ContentsStateBase {
protected get schemaName() {
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();
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
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 { Observable, throwError } from 'rxjs';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { catchError, tap } from 'rxjs/operators';
import {
DialogService,
@ -34,9 +34,6 @@ interface Snapshot {
// All loaded contributors.
contributors: ContributorsList;
// Indicates if the maximum number of contributors are reached.
isMaxReached?: boolean;
// Indicates if the contributors are loaded.
isLoaded?: boolean;
@ -58,20 +55,16 @@ type ContributorsList = ImmutableArray<ContributorDto>;
@Injectable()
export class ContributorsState extends State<Snapshot> {
public contributors =
this.changes.pipe(map(x => x.contributors),
distinctUntilChanged());
this.project(x => x.contributors);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
public maxContributors =
this.changes.pipe(map(x => x.maxContributors),
distinctUntilChanged());
this.project(x => x.maxContributors);
public canCreate =
this.changes.pipe(map(x => !!x.canCreate),
distinctUntilChanged());
this.project(x => !!x.canCreate);
constructor(
private readonly contributorsService: ContributorsService,
@ -122,16 +115,11 @@ export class ContributorsState extends State<Snapshot> {
private replaceContributors(version: Version, payload: ContributorsPayload) {
this.next(s => {
const maxContributors = payload.maxContributors || s.maxContributors;
const isLoaded = true;
const isMaxReached = maxContributors > 0 && maxContributors <= payload.items.length;
const contributors = ImmutableArray.of(payload.items);
const { canCreate, items, maxContributors, _links } = payload;
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 { distinctUntilChanged, map } from 'rxjs/internal/operators';
import { distinctUntilChanged } from 'rxjs/internal/operators';
import { State, Types } from '@app/framework';
@ -45,20 +45,16 @@ export class FilterState extends State<Snapshot> {
private readonly sortModes: { [key: string]: Observable<Sorting> } = {};
public query =
this.changes.pipe(map(x => x.query),
distinctUntilChanged());
this.project(x => x.query);
public order =
this.changes.pipe(map(x => x.order),
distinctUntilChanged());
this.project(x => x.order);
public filter =
this.changes.pipe(map(x => x.filter),
distinctUntilChanged());
this.project(x => x.filter);
public fullText =
this.changes.pipe(map(x => x.fullText),
distinctUntilChanged());
this.project(x => x.fullText);
public get apiFilter() {
return this.snapshot.query;
@ -74,7 +70,7 @@ export class FilterState extends State<Snapshot> {
let result = this.sortModes[key];
if (!result) {
result = this.changes.pipe(map(x => sortMode(x, field)), distinctUntilChanged());
result = this.project(x => sortMode(x, field)), distinctUntilChanged();
this.sortModes[key] = result;
}

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

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

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
DialogService,
@ -42,7 +42,7 @@ interface Snapshot {
canCreate?: boolean;
// The links.
links: ResourceLinks;
_links: ResourceLinks;
}
type PatternsList = ImmutableArray<PatternDto>;
@ -50,23 +50,20 @@ type PatternsList = ImmutableArray<PatternDto>;
@Injectable()
export class PatternsState extends State<Snapshot> {
public patterns =
this.changes.pipe(map(x => x.patterns),
distinctUntilChanged());
this.project(x => x.patterns);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
public canCreate =
this.changes.pipe(map(x => !!x.canCreate),
distinctUntilChanged());
this.project(x => !!x.canCreate);
constructor(
private readonly patternsService: PatternsService,
private readonly appsState: AppsState,
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> {
@ -115,7 +112,7 @@ export class PatternsState extends State<Snapshot> {
const { _links: links, canCreate } = payload;
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 { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
DialogService,
@ -52,24 +52,19 @@ interface Snapshot {
@Injectable()
export class PlansState extends State<Snapshot> {
public plans =
this.changes.pipe(map(x => x.plans),
distinctUntilChanged());
this.project(x => x.plans);
public isOwner =
this.changes.pipe(map(x => !!x.isOwner),
distinctUntilChanged());
this.project(x => !!x.isOwner);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
public isDisabled =
this.changes.pipe(map(x => !x.isOwner),
distinctUntilChanged());
this.project(x => !x.isOwner);
public hasPortal =
this.changes.pipe(map(x => x.hasPortal),
distinctUntilChanged());
this.project(x => x.hasPortal);
public window = window;

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

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
DialogService,
@ -50,16 +50,13 @@ type RolesList = ImmutableArray<RoleDto>;
@Injectable()
export class RolesState extends State<Snapshot> {
public roles =
this.changes.pipe(map(x => x.roles),
distinctUntilChanged());
this.project(x => x.roles);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
public canCreate =
this.changes.pipe(map(x => !!x.canCreate),
distinctUntilChanged());
this.project(x => !!x.canCreate);
constructor(
private readonly rolesService: RolesService,
@ -110,12 +107,12 @@ export class RolesState extends State<Snapshot> {
}
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 => {
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 { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
DialogService,
@ -35,16 +35,13 @@ interface Snapshot {
@Injectable()
export class RuleEventsState extends State<Snapshot> {
public ruleEvents =
this.changes.pipe(map(x => x.ruleEvents),
distinctUntilChanged());
this.project(x => x.ruleEvents);
public ruleEventsPager =
this.changes.pipe(map(x => x.ruleEventsPager),
distinctUntilChanged());
this.project(x => x.ruleEventsPager);
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
this.project(x => !!x.isLoaded);
constructor(
private readonly appsState: AppsState,

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

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

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

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

Loading…
Cancel
Save