mirror of https://github.com/Squidex/squidex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
5.3 KiB
206 lines
5.3 KiB
/*
|
|
* Squidex Headless CMS
|
|
*
|
|
* @license
|
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
|
|
*/
|
|
|
|
import { Injectable } from '@angular/core';
|
|
import { Observable, from, of, shareReplay } from 'rxjs';
|
|
import { UIOptions } from '@app/framework';
|
|
import { AssetDto, AssetsDto, AssetsService } from './../services/assets.service';
|
|
import { AppsState } from './apps.state';
|
|
import { ContentDto, ContentsDto, ContentsService } from './../services/contents.service';
|
|
|
|
abstract class ResolverBase<T extends { id: string }, TResult extends { items: ReadonlyArray<T> }> {
|
|
private readonly items: { [id: string]: Deferred<T | undefined> } = {};
|
|
private pending: { [id: string]: boolean } | null = null;
|
|
|
|
public resolveMany(ids: ReadonlyArray<string>): Observable<TResult> {
|
|
if (ids.length === 0) {
|
|
return of(this.createResult([]));
|
|
}
|
|
|
|
const nonResolved: string[] = [];
|
|
|
|
const promises: Promise<T | undefined>[] = [];
|
|
|
|
for (const id of ids) {
|
|
let deferred = this.items[id];
|
|
|
|
if (!deferred) {
|
|
deferred = new Deferred<T>();
|
|
this.items[id] = deferred;
|
|
|
|
nonResolved.push(id);
|
|
}
|
|
|
|
promises.push(deferred.promise);
|
|
}
|
|
|
|
if (nonResolved.length > 0) {
|
|
if (this.pending === null) {
|
|
this.pending = {};
|
|
|
|
setTimeout(() => {
|
|
this.resolvePending();
|
|
}, 100);
|
|
}
|
|
|
|
for (const id of nonResolved) {
|
|
this.pending[id] = true;
|
|
}
|
|
}
|
|
|
|
return from(this.buildPromise(promises));
|
|
}
|
|
|
|
private async buildPromise(promises: Promise<T | undefined>[]) {
|
|
const promise = await Promise.all(promises);
|
|
|
|
return this.createResult(promise.filter(x => !!x) as any);
|
|
}
|
|
|
|
private resolvePending() {
|
|
if (!this.pending) {
|
|
return;
|
|
}
|
|
|
|
const allIds = Object.keys(this.pending);
|
|
|
|
if (allIds.length === 0) {
|
|
return;
|
|
}
|
|
|
|
this.pending = null;
|
|
|
|
for (const ids of chunkArray(allIds, 100)) {
|
|
this.resolveIds(ids);
|
|
}
|
|
}
|
|
|
|
protected abstract createResult(items: T[]): TResult;
|
|
|
|
protected abstract loadMany(ids: string[]): Observable<TResult>;
|
|
|
|
private resolveIds(ids: string[]) {
|
|
this.loadMany(ids)
|
|
.subscribe({
|
|
next: results => {
|
|
for (const id of ids) {
|
|
const content = results.items.find(x => x.id === id);
|
|
|
|
this.items[id]?.resolve(content);
|
|
}
|
|
},
|
|
error: ex => {
|
|
for (const id of ids) {
|
|
this.items[id]?.reject(ex);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
@Injectable()
|
|
export class ResolveContents extends ResolverBase<ContentDto, ContentsDto> {
|
|
private readonly schemas: { [name: string]: Observable<ContentsDto> } = {};
|
|
private readonly itemCount;
|
|
|
|
constructor(
|
|
uiOptions: UIOptions,
|
|
private readonly appsState: AppsState,
|
|
private readonly contentsService: ContentsService,
|
|
) {
|
|
super();
|
|
|
|
this.itemCount = uiOptions.get('referencesDropdownItemCount');
|
|
}
|
|
|
|
public resolveAll(schema: string) {
|
|
let result = this.schemas[schema];
|
|
|
|
if (!result) {
|
|
result = this.contentsService.getContents(this.appName, schema, { take: this.itemCount }).pipe(shareReplay(1));
|
|
|
|
this.schemas[schema] = result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected createResult(items: ContentDto[]) {
|
|
return new ContentsDto([], items.length, items);
|
|
}
|
|
|
|
protected loadMany(ids: string[]) {
|
|
return this.contentsService.getAllContents(this.appName, { ids });
|
|
}
|
|
|
|
private get appName() {
|
|
return this.appsState.appName;
|
|
}
|
|
}
|
|
|
|
@Injectable()
|
|
export class ResolveAssets extends ResolverBase<AssetDto, AssetsDto> {
|
|
constructor(
|
|
private readonly appsState: AppsState,
|
|
private readonly assetsService: AssetsService,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
protected createResult(items: AssetDto[]) {
|
|
return new AssetsDto(items.length, items);
|
|
}
|
|
|
|
protected loadMany(ids: string[]) {
|
|
return this.assetsService.getAssets(this.appName, { ids });
|
|
}
|
|
|
|
private get appName() {
|
|
return this.appsState.appName;
|
|
}
|
|
}
|
|
|
|
function chunkArray<T>(array: T[], size: number): T[][] {
|
|
if (array.length > size) {
|
|
return [array.slice(0, size), ...chunkArray(array.slice(size), size)];
|
|
} else {
|
|
return [array];
|
|
}
|
|
}
|
|
|
|
class Deferred<T> {
|
|
private handleResolve: Function;
|
|
private handleReject: Function;
|
|
private isHandled = false;
|
|
|
|
public readonly promise: Promise<T>;
|
|
|
|
constructor() {
|
|
this.promise = new Promise<T>((resolve, reject) => {
|
|
this.handleResolve = resolve;
|
|
this.handleReject = reject;
|
|
});
|
|
}
|
|
|
|
public resolve(value: T | PromiseLike<T>) {
|
|
if (this.isHandled) {
|
|
return;
|
|
}
|
|
|
|
this.isHandled = true;
|
|
this.handleResolve(value);
|
|
}
|
|
|
|
public reject(reason?: any) {
|
|
if (this.isHandled) {
|
|
return;
|
|
}
|
|
|
|
this.isHandled = true;
|
|
this.handleReject(reason);
|
|
}
|
|
}
|
|
|