diff --git a/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts b/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts index 72d0e2e66fc7ff0b8da0d520a6ec4438432a88df..ed1aa7731dfeda9b771a64cdadfc82236bf9eec1 100644 --- a/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts +++ b/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Input, OnChanges } from "@angular/core"; +import { Directive, Input, OnChanges, Output } from "@angular/core"; import { BehaviorSubject, Observable } from "rxjs"; import { distinctUntilChanged } from "rxjs/operators"; import { BoundingBox, SxplrTemplate, SxplrAtlas } from "src/atlasComponents/sapi/sxplrTypes" @@ -60,6 +60,7 @@ export class SapiViewsCoreSpaceBoundingBox implements OnChanges{ bbox: null }) + @Output('sxplr-sapiviews-core-space-boundingbox-changed') public bbox$: Observable<{ atlas: SxplrAtlas space: SxplrTemplate diff --git a/src/features/category-acc.directive.ts b/src/features/category-acc.directive.ts index cddba9e69912494aa8a156eb802fe2b25f76ad1e..73156e7af17e6cf4e39e97fca101655eb02b07bb 100644 --- a/src/features/category-acc.directive.ts +++ b/src/features/category-acc.directive.ts @@ -118,7 +118,8 @@ export class CategoryAccDirective implements AfterContentInit, OnDestroy { return this.datasource }), ) - }) + }), + shareReplay(1), ) constructor(){ diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts index 979fea404ef798b8f0bfc510477bf423782938b0..722fd8e99735fb88a04ab0435c6a0bccaa760306 100644 --- a/src/features/entry/entry.component.ts +++ b/src/features/entry/entry.component.ts @@ -1,13 +1,13 @@ import { AfterViewInit, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { distinctUntilChanged, map, scan, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, map, scan, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators'; import { IDS, SAPI } from 'src/atlasComponents/sapi'; import { Feature } from 'src/atlasComponents/sapi/sxplrTypes'; import { FeatureBase } from '../base'; import * as userInteraction from "src/state/userInteraction" import { atlasSelection } from 'src/state'; import { CategoryAccDirective } from "../category-acc.directive" -import { BehaviorSubject, combineLatest, concat, merge, of, Subscription } from 'rxjs'; +import { combineLatest, concat, forkJoin, merge, of, Subject, Subscription } from 'rxjs'; import { DsExhausted, IsAlreadyPulling, PulledDataSource } from 'src/util/pullable'; import { TranslatedFeature } from '../list/list.directive'; @@ -33,56 +33,63 @@ const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => { }) export class EntryComponent extends FeatureBase implements AfterViewInit, OnDestroy { - private _features$ = new BehaviorSubject<TranslatedFeature[]>([]) - features$ = this._features$.pipe( - shareReplay(1) - ) - @ViewChildren(CategoryAccDirective) catAccDirs: QueryList<CategoryAccDirective> - public busyTallying$ = new BehaviorSubject<boolean>(false) - public totals$ = new BehaviorSubject<number>(null) - constructor(private sapi: SAPI, private store: Store) { super() } #subscriptions: Subscription[] = [] + #catAccDirs = new Subject<CategoryAccDirective[]>() + features$ = this.#catAccDirs.pipe( + switchMap(dirs => concat( + of([] as TranslatedFeature[]), + merge(...dirs.map((dir, idx) => + dir.datasource$.pipe( + switchMap(ds => ds.data$), + map(val => ({ val, idx })) + )) + ).pipe( + map(({ idx, val }) => ({ [idx.toString()]: val })), + scan((acc, curr) => ({ ...acc, ...curr })), + map(record => Object.values(record).flatMap(v => v)) + ) + )), + shareReplay(1), + ) - private _busy$ = new BehaviorSubject<boolean>(false) - busy$ = this._busy$.pipe( + busy$ = this.#catAccDirs.pipe( + switchMap(dirs => combineLatest( + dirs.map(dir => dir.isBusy$) + )), + map(flags => flags.some(flag => flag)), + distinctUntilChanged(), shareReplay(1) ) - ngOnDestroy(): void { - while (this.#subscriptions.length > 0) this.#subscriptions.pop().unsubscribe() - } - ngAfterViewInit(): void { - const catAccDirs$ = merge( - of(null), - this.catAccDirs.changes - ).pipe( - map(() => Array.from(this.catAccDirs)) - ) - this.#subscriptions.push( - catAccDirs$.pipe( - switchMap(dirs => combineLatest( - dirs.map(dir => dir.isBusy$) - )), - map(flags => flags.some(flag => flag)), - distinctUntilChanged(), - ).subscribe(value => { - this._busy$.next(value) - }), - catAccDirs$.pipe( - tap(() => this.busyTallying$.next(true)), - switchMap(catArrDirs => merge( - ...catArrDirs.map((dir, idx) => dir.total$.pipe( - map(val => ({ idx, val })) - )) - )), - + public busyTallying$ = this.#catAccDirs.pipe( + switchMap(arr => concat( + of(true), + forkJoin( + arr.map(dir => dir.total$) + ).pipe( + map(() => false) + ) + )), + shareReplay(1) + ) + + public totals$ = this.#catAccDirs.pipe( + switchMap(arr => concat( + of(0), + merge( + ...arr.map((dir, idx) => + dir.total$.pipe( + map(val => ({ val, idx })) + ) + ) + ).pipe( map(({ idx, val }) => ({ [idx.toString()]: val })), scan((acc, curr) => ({ ...acc, ...curr })), map(record => { @@ -92,11 +99,47 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest } return tally }), - tap(num => { - this.busyTallying$.next(false) - this.totals$.next(num) - }), - ).subscribe(), + ) + )) + ) + + ngOnDestroy(): void { + while (this.#subscriptions.length > 0) this.#subscriptions.pop().unsubscribe() + } + ngAfterViewInit(): void { + this.#subscriptions.push( + merge( + of(null), + this.catAccDirs.changes + ).pipe( + map(() => Array.from(this.catAccDirs)) + ).subscribe(dirs => this.#catAccDirs.next(dirs)), + + this.#pullAll.pipe( + debounceTime(320), + withLatestFrom(this.#catAccDirs), + switchMap(([_, dirs]) => combineLatest(dirs.map(dir => dir.datasource$))), + ).subscribe(async dss => { + await Promise.all( + dss.map(async ds => { + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await ds.pull() + } catch (e) { + if (e instanceof DsExhausted) { + console.log('exhausted') + break + } + if (e instanceof IsAlreadyPulling ) { + continue + } + throw e + } + } + }) + ) + }) ) } @@ -167,28 +210,8 @@ export class EntryComponent extends FeatureBase implements AfterViewInit, OnDest } } - async pullAll(){ - const dss = Array.from(this.catAccDirs).map(catAcc => catAcc.datasource) - - this._features$.next([]) - await Promise.all( - dss.map(async ds => { - // eslint-disable-next-line no-constant-condition - while (true) { - try { - await ds.pull() - } catch (e) { - if (e instanceof DsExhausted) { - break - } - if (e instanceof IsAlreadyPulling ) { - continue - } - throw e - } - } - }) - ) - this._features$.next(dss.flatMap(ds => ds.finalValue)) + #pullAll = new Subject() + pullAll(){ + this.#pullAll.next(null) } } diff --git a/src/features/voi-bbox.directive.ts b/src/features/voi-bbox.directive.ts index d6ed2e0aac4ca11816b5f214699ed9c07cc8a47d..f150225b9cf66f5ce2f68f26c7a936f1c30f6576 100644 --- a/src/features/voi-bbox.directive.ts +++ b/src/features/voi-bbox.directive.ts @@ -47,7 +47,7 @@ export class VoiBboxDirective implements OnDestroy { @Input() set features(feats: Feature[]){ - this.#voiFeatures = feats.filter(isVoiData) + this.#voiFeatures = (feats || []).filter(isVoiData) this.#features$.next(this.#voiFeatures) } get features(): VoiFeature[]{ diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index 136f0baf82166534a9e0d7480159e0dce58edc32..2dbd534dbd0a309ec23fcf1f6545a88bfe1b5a18 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -14,6 +14,7 @@ import { atlasAppearance, atlasSelection, userInteraction } from "src/state"; import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { EntryComponent } from "src/features/entry/entry.component"; import { TFace, TSandsPoint, getCoord } from "src/util/types"; +import { wait } from "src/util/fn"; @Component({ selector: 'iav-cmp-viewer-container', @@ -438,4 +439,14 @@ export class ViewerCmp implements OnDestroy { }) ) } + + @ViewChild('voiFeatureEntryCmp') + voiFeatureEntryCmp: EntryComponent + + async pullAllVoi(){ + if (this.voiFeatureEntryCmp){ + await wait(320) + this.voiFeatureEntryCmp.pullAll() + } + } } diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 2ec0d95591e688c67eecc4ce5604cb70975182b0..459fb19e841d22944af9cf5a8bca36512bcb6d0c 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -1124,6 +1124,7 @@ class="sxplr-pe-all mat-elevation-z8" [template]="view.selectedTemplate" [bbox]="bbox.bbox$ | async | getProperty : 'bbox'" + [attr.data-feature-length]="((voiFeatureEntryCmp.features$ | async) || []).length" #voiFeatureEntryCmp="featureEntryCmp"> </sxplr-feature-entry> @@ -1141,7 +1142,7 @@ iav-switch [iav-switch-state]="false" #voiSwitch="iavSwitch" - (iav-switch-event)="$event && voiFeatureEntryCmp.pullAll()" + (iav-switch-event)="$event && pullAllVoi()" (click)="voiSwitch.toggle()"> <ng-template [ngIf]="voiSwitch.switchState$ | async" [ngIfElse]="chevronCollapseTmpl"> @@ -1173,6 +1174,7 @@ <div sxplr-sapiviews-core-space-boundingbox + (sxplr-sapiviews-core-space-boundingbox-changed)="pullAllVoi()" [sxplr-sapiviews-core-space-boundingbox-atlas]="selectedAtlas$ | async" [sxplr-sapiviews-core-space-boundingbox-space]="templateSelected$ | async" [sxplr-sapiviews-core-space-boundingbox-spec]="viewerCtx$ | async | nehubaVCtxToBbox"