/* * 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 }> { private readonly items: { [id: string]: Deferred } = {}; private pending: { [id: string]: boolean } | null = null; public resolveMany(ids: ReadonlyArray): Observable { if (ids.length === 0) { return of(this.createResult([])); } const nonResolved: string[] = []; const promises: Promise[] = []; for (const id of ids) { let deferred = this.items[id]; if (!deferred) { deferred = new Deferred(); 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[]) { 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; 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 { private readonly schemas: { [name: string]: Observable } = {}; 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 { 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(array: T[], size: number): T[][] { if (array.length > size) { return [array.slice(0, size), ...chunkArray(array.slice(size), size)]; } else { return [array]; } } class Deferred { private handleResolve: Function; private handleReject: Function; private isHandled = false; public readonly promise: Promise; constructor() { this.promise = new Promise((resolve, reject) => { this.handleResolve = resolve; this.handleReject = reject; }); } public resolve(value: T | PromiseLike) { if (this.isHandled) { return; } this.isHandled = true; this.handleResolve(value); } public reject(reason?: any) { if (this.isHandled) { return; } this.isHandled = true; this.handleReject(reason); } }