diff --git a/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html b/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html index 966584d4..feb1bddf 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html +++ b/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html @@ -4,17 +4,14 @@
- - +
diff --git a/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.ts b/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.ts index 95f64f77..592a6ca7 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.ts +++ b/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.ts @@ -39,8 +39,8 @@ export class AssetSearchDetailComponent { public loadingState = this.store.select(selectAssetDetailLoadingState); - public resetAssetDetail() { - this.store.dispatch(actions.resetAssetDetail()); + public clearSelectedAsset() { + this.store.dispatch(actions.clearSelectedAsset()); } public searchForReferenceAsset(assetId: number) { diff --git a/libs/asset-viewer/src/lib/components/asset-search-filter-list/asset-search-filter-list.component.ts b/libs/asset-viewer/src/lib/components/asset-search-filter-list/asset-search-filter-list.component.ts index bc03ba71..c13ec4e7 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-filter-list/asset-search-filter-list.component.ts +++ b/libs/asset-viewer/src/lib/components/asset-search-filter-list/asset-search-filter-list.component.ts @@ -23,7 +23,7 @@ export class AssetSearchFilterListComponent { activeValues.add(filter.value); } this.store.dispatch( - actions.search({ + actions.mergeQuery({ query: { [filter.queryKey]: activeValues.size > 0 ? [...activeValues] : undefined }, }) ); diff --git a/libs/asset-viewer/src/lib/components/asset-search-refine/asset-search-refine.component.ts b/libs/asset-viewer/src/lib/components/asset-search-refine/asset-search-refine.component.ts index 5d0e25db..55811982 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-refine/asset-search-refine.component.ts +++ b/libs/asset-viewer/src/lib/components/asset-search-refine/asset-search-refine.component.ts @@ -93,7 +93,7 @@ export class AssetSearchRefineComponent implements OnInit, OnDestroy, AfterViewI } public removePolygon() { - this.store.dispatch(actions.removePolygon()); + this.store.dispatch(actions.clearPolygon()); } public toggleDrawPolygon() { @@ -108,7 +108,7 @@ export class AssetSearchRefineComponent implements OnInit, OnDestroy, AfterViewI public updateSearch(filterConfiguration: Partial) { if (this.isFiltersOpen) { - this.store.dispatch(actions.search({ query: filterConfiguration })); + this.store.dispatch(actions.mergeQuery({ query: filterConfiguration })); } } diff --git a/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.html b/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.html index 48a41684..b3f8d5af 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.html +++ b/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.html @@ -4,7 +4,7 @@ {{ "search.searchResults" | translate }}:
- diff --git a/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.ts b/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.ts index 3f19ad40..8d1fc8c8 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.ts +++ b/libs/asset-viewer/src/lib/components/asset-search-results/asset-search-results.component.ts @@ -68,8 +68,8 @@ export class AssetSearchResultsComponent implements OnInit, OnDestroy { this._store.dispatch(actions.assetClicked({ assetId })); } - public toggleResultsOpen(isCurrentlyOpen: boolean) { - this._store.dispatch(actions.manualToggleResult()); + public toggleResultsOpen() { + this._store.dispatch(actions.toggleResults()); } public onScroll(event: Event) { diff --git a/libs/asset-viewer/src/lib/components/asset-viewer-page/asset-viewer-page.component.ts b/libs/asset-viewer/src/lib/components/asset-viewer-page/asset-viewer-page.component.ts index 37056699..3c63eb65 100644 --- a/libs/asset-viewer/src/lib/components/asset-viewer-page/asset-viewer-page.component.ts +++ b/libs/asset-viewer/src/lib/components/asset-viewer-page/asset-viewer-page.component.ts @@ -10,6 +10,7 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; +import { AuthService } from '@asset-sg/auth'; import { AppPortalService, AppState, LifecycleHooks, LifecycleHooksDirective } from '@asset-sg/client-shared'; import { AssetEditDetail } from '@asset-sg/shared'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -22,6 +23,7 @@ import { asyncScheduler, combineLatest, filter, + identity, map, Observable, observeOn, @@ -29,6 +31,7 @@ import { share, Subject, switchMap, + take, withLatestFrom, } from 'rxjs'; @@ -92,9 +95,14 @@ export class AssetViewerPageComponent implements OnInit, OnDestroy { public assetsForPicker$: Observable; public highlightedAssetId: number | null = null; + private readonly authService = inject(AuthService); + public ngOnInit() { - this._store.dispatch(actions.initializeSearch()); this._store.dispatch(actions.openFilters()); + + this.authService.isInitialized$.pipe(filter(identity), take(1)).subscribe(() => { + this._store.dispatch(actions.initialize()); + }); this._appPortalService.setAppBarPortalContent(null); } @@ -154,8 +162,8 @@ export class AssetViewerPageComponent implements OnInit, OnDestroy { .pipe( map( flow( - O.map((text) => actions.search({ query: { text } })), - O.getOrElseW(() => actions.clearSearchText()) + O.map((text) => actions.mergeQuery({ query: { text } })), + O.getOrElseW(() => actions.mergeQuery({ query: { text: '' } })) ) ) ) diff --git a/libs/asset-viewer/src/lib/components/map/map.component.ts b/libs/asset-viewer/src/lib/components/map/map.component.ts index 33a7051f..bc4a2fa3 100644 --- a/libs/asset-viewer/src/lib/components/map/map.component.ts +++ b/libs/asset-viewer/src/lib/components/map/map.component.ts @@ -189,7 +189,7 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { ) .subscribe(([polygon, _]) => this.store.dispatch( - searchActions.search({ + searchActions.mergeQuery({ query: { polygon: polygon }, }) ) diff --git a/libs/asset-viewer/src/lib/services/asset-search.service.ts b/libs/asset-viewer/src/lib/services/asset-search.service.ts index f996c80a..fc7d27ea 100644 --- a/libs/asset-viewer/src/lib/services/asset-search.service.ts +++ b/libs/asset-viewer/src/lib/services/asset-search.service.ts @@ -25,15 +25,15 @@ export class AssetSearchService { ); } - public loadAssetDetailData(assetId: number): Observable { + public searchStats(searchQuery: AssetSearchQuery): Observable { return this._httpClient - .get(`/api/asset-edit/${assetId}`) - .pipe(map((res) => (AssetEditDetail.decode(res) as E.Right).right)); + .post('/api/assets/search/stats', searchQuery) + .pipe(map((res) => plainToInstance(AssetSearchStatsDTO, res))); } - public updateSearchResultStats(searchQuery: AssetSearchQuery): Observable { + public fetchAssetEditDetail(assetId: number): Observable { return this._httpClient - .post('/api/assets/search/stats', searchQuery) - .pipe(map((res) => plainToInstance(AssetSearchStatsDTO, res))); + .get(`/api/asset-edit/${assetId}`) + .pipe(map((res) => (AssetEditDetail.decode(res) as E.Right).right)); } } diff --git a/libs/asset-viewer/src/lib/state/asset-search/asset-search.actions.ts b/libs/asset-viewer/src/lib/state/asset-search/asset-search.actions.ts index 1f7a2880..cf7a302b 100644 --- a/libs/asset-viewer/src/lib/state/asset-search/asset-search.actions.ts +++ b/libs/asset-viewer/src/lib/state/asset-search/asset-search.actions.ts @@ -1,41 +1,53 @@ import { AssetEditDetail, AssetSearchQuery, AssetSearchResult, AssetSearchStats } from '@asset-sg/shared'; +import { AssetId } from '@asset-sg/shared/v2'; import { createAction, props } from '@ngrx/store'; import { AllStudyDTOs } from '../../models'; +export const initialize = createAction('[Asset Search] Initialize'); +export const runInitialSearch = createAction( + '[Asset Search] Run Initial Search', + props<{ + assetId: number | undefined; + query: AssetSearchQuery; + }>() +); export const search = createAction( '[Asset Search] Search', props<{ - query: Partial; + query: AssetSearchQuery; }>() ); -export const updateStats = createAction( - '[Asset Search] Update search stats', +export const mergeQuery = createAction( + '[Asset Search] Merge Query', props<{ - searchStats: AssetSearchStats; + query: AssetSearchQuery; }>() ); -export const updateSearchResults = createAction( - '[Asset Search] Update search results', +export const executeSearch = createAction('[Asset Search] Execute Search', props<{ query: AssetSearchQuery }>()); + +export const updateResults = createAction( + '[Asset Search] Update Results', props<{ - searchResults: AssetSearchResult; + results: AssetSearchResult; }>() ); -export const clearSearchText = createAction('[Asset Search] Clear search text'); -export const resetSearch = createAction('[Asset Search] Reset Search'); -export const removePolygon = createAction('[Asset Search] Remove polygon'); -export const initializeSearch = createAction('[Asset Search] Initialize search'); -export const assetClicked = createAction('[Asset Search] Asset clicked', props<{ assetId: number }>()); -export const showAssetDetail = createAction('[Asset Search] Show Asset Detail', props<{ assetId: number }>()); -export const updateAssetDetail = createAction( - '[Asset Search] Update Asset Detail', - props<{ assetDetail: AssetEditDetail }>() +export const updateStats = createAction( + '[Asset Search] Update Stats', + props<{ + stats: AssetSearchStats; + }>() ); -export const resetAssetDetail = createAction('[Asset Search] Reset Asset Detail'); +export const resetSearch = createAction('[Asset Search] Reset Search'); + +export const assetClicked = createAction('[Asset Search] Asset Clicked', props<{ assetId: number }>()); +export const selectAsset = createAction('[Asset Search] Select Asset', props<{ assetId: AssetId }>()); +export const setSelectedAsset = createAction('[Asset Search] Set Selected Asset', props<{ asset: AssetEditDetail }>()); +export const clearSelectedAsset = createAction('[Asset Search] Clear Selected Asset'); +export const clearPolygon = createAction('[Asset Search] Clear Polygon'); +export const setStudies = createAction('[Asset Search] Set Studies', props<{ studies: AllStudyDTOs }>()); + export const openFilters = createAction('[Asset Search] Open Filters'); export const closeFilters = createAction('[Asset Search] Close Filters'); export const openResults = createAction('[Asset Search] Open Results'); export const closeResults = createAction('[Asset Search] Close Results'); -export const setStudies = createAction('[Asset Search] Load Studies', props<{ studies: AllStudyDTOs }>()); -export const loadSearch = createAction('[Asset Search] Load Search', props<{ query: AssetSearchQuery }>()); -export const updateQueryParams = createAction('[Asset Search] Update Query Params'); -export const manualToggleResult = createAction('[Asset Search] Manual Toggle Params'); +export const toggleResults = createAction('[Asset Search] Toggle Results'); diff --git a/libs/asset-viewer/src/lib/state/asset-search/asset-search.effects.ts b/libs/asset-viewer/src/lib/state/asset-search/asset-search.effects.ts index f0c7aa5f..dec25f9f 100644 --- a/libs/asset-viewer/src/lib/state/asset-search/asset-search.effects.ts +++ b/libs/asset-viewer/src/lib/state/asset-search/asset-search.effects.ts @@ -1,24 +1,24 @@ import { inject, Injectable } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { AppState, assetsPageMatcher, fromAppShared } from '@asset-sg/client-shared'; -import { deepEqual, isDecodeError, isNotNull, ORD } from '@asset-sg/core'; -import { AssetSearchQuery, AssetSearchResult, LV95, Polygon } from '@asset-sg/shared'; -import * as RD from '@devexperts/remote-data-ts'; +import { AppState } from '@asset-sg/client-shared'; +import { isNotNull, ORD, isNull } from '@asset-sg/core'; +import { AssetSearchQuery, LV95, Polygon } from '@asset-sg/shared'; +import { AuthService } from '@asset-sg/auth'; import { UntilDestroy } from '@ngneat/until-destroy'; -import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects'; -import { ROUTER_NAVIGATED } from '@ngrx/router-store'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import * as D from 'io-ts/Decoder'; -import { EMPTY, filter, map, merge, of, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs'; +import { combineLatest, filter, map, of, switchMap, take, withLatestFrom } from 'rxjs'; import { AllStudyService } from '../../services/all-study.service'; import { AssetSearchService } from '../../services/asset-search.service'; import * as actions from './asset-search.actions'; +import { LoadingState } from './asset-search.reducer'; import { + selectAssetDetailLoadingState, + selectAssetSearchIsInitialized, selectAssetSearchNoActiveFilters, selectAssetSearchQuery, - selectAssetSearchResultData, selectCurrentAssetDetail, selectStudies, } from './asset-search.selector'; @@ -32,203 +32,168 @@ export class AssetSearchEffects { private readonly route = inject(ActivatedRoute); private readonly assetSearchService = inject(AssetSearchService); private readonly allStudyService = inject(AllStudyService); + private readonly authService = inject(AuthService); - constructor() { - merge(this.store.select(fromAppShared.selectRDReferenceData)) - .pipe( - filter(RD.isFailure), - map((e) => e.error), - filter(isDecodeError) - ) - .subscribe((e) => { - console.error('DecodeError', D.draw(e.cause)); - }); - } - - public loadSearch$ = createEffect(() => + public updateQueryFromParams$ = createEffect(() => this.actions$.pipe( - ofType(actions.search, actions.resetSearch, actions.removePolygon), - withLatestFrom(this.store.select(selectAssetSearchQuery)), - map(([_, query]) => actions.loadSearch({ query })) + ofType(actions.initialize), + switchMap(() => this.route.queryParams.pipe(take(1))), + map((params) => { + const query: AssetSearchQuery = {}; + const assetId = readNumberParam(params, QUERY_PARAM_MAPPING.assetId); + query.text = readStringParam(params, QUERY_PARAM_MAPPING.text); + query.polygon = readPolygonParam(params, QUERY_PARAM_MAPPING.polygon); + query.authorId = readNumberParam(params, QUERY_PARAM_MAPPING.authorId); + const min = readDateParam(params, QUERY_PARAM_MAPPING.createDate.min); + const max = readDateParam(params, QUERY_PARAM_MAPPING.createDate.max); + query.createDate = min && max ? { min, max } : undefined; + query.manCatLabelItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.manCatLabelItemCodes); + query.assetKindItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.assetKindItemCodes); + query.usageCodes = readArrayParam(params, QUERY_PARAM_MAPPING.usageCodes); + query.geometryCodes = readArrayParam(params, QUERY_PARAM_MAPPING.geometryCodes); + query.languageItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.languageItemCodes); + query.workgroupIds = readArrayParam(params, QUERY_PARAM_MAPPING.workgroupIds); + return actions.runInitialSearch({ assetId, query }); + }) ) ); - public assetResults$ = createEffect(() => + public updateParamsFromQuery$ = createEffect( + () => + combineLatest([ + this.store.select(selectAssetSearchQuery), + this.store.select(selectCurrentAssetDetail), + this.store.select(selectAssetDetailLoadingState), + this.store.select(selectAssetSearchIsInitialized), + ]).pipe( + filter(([_query, _asset, _state, isInitialized]) => isInitialized), + withLatestFrom(this.authService.isInitialized$), + filter(([_search, isInitialized]) => isInitialized), + map(([search]) => search), + switchMap(([query, asset, assetLoadingState]) => { + const params: Params = {}; + updatePlainParam(params, QUERY_PARAM_MAPPING.text, query.text); + updateArrayParam( + params, + QUERY_PARAM_MAPPING.polygon, + query.polygon?.map(({ x, y }) => `${x}:${y}`) + ); + updatePlainParam(params, QUERY_PARAM_MAPPING.authorId, query.authorId); + updateDateParam(params, QUERY_PARAM_MAPPING.createDate.min, query.createDate?.min); + updateDateParam(params, QUERY_PARAM_MAPPING.createDate.max, query.createDate?.max); + updateArrayParam(params, QUERY_PARAM_MAPPING.manCatLabelItemCodes, query.manCatLabelItemCodes); + updateArrayParam(params, QUERY_PARAM_MAPPING.assetKindItemCodes, query.assetKindItemCodes); + updateArrayParam(params, QUERY_PARAM_MAPPING.usageCodes, query.usageCodes); + updateArrayParam(params, QUERY_PARAM_MAPPING.geometryCodes, query.geometryCodes); + updateArrayParam(params, QUERY_PARAM_MAPPING.languageItemCodes, query.languageItemCodes); + updateArrayParam(params, QUERY_PARAM_MAPPING.workgroupIds, query.workgroupIds); + if (assetLoadingState !== LoadingState.Loading) { + updatePlainParam(params, QUERY_PARAM_MAPPING.assetId, asset?.assetId); + } + return this.router.navigate([], { queryParams: params, queryParamsHandling: 'merge' }); + }) + ), + { dispatch: false } + ); + + public initializeAsset$ = createEffect(() => this.actions$.pipe( - ofType(actions.loadSearch), - switchMap(({ query }) => - this.assetSearchService - .search(query) - .pipe(map((searchResults: AssetSearchResult) => actions.updateSearchResults({ searchResults }))) - ) + ofType(actions.runInitialSearch), + map(({ assetId }) => (assetId == null ? actions.clearSelectedAsset() : actions.selectAsset({ assetId }))) ) ); - public assetStats$ = createEffect(() => + public initializeQuery$ = createEffect(() => this.actions$.pipe( - ofType(actions.loadSearch), - switchMap(({ query }) => - this.assetSearchService - .updateSearchResultStats(query) - .pipe(map((searchStats) => actions.updateStats({ searchStats }))) - ) + ofType(actions.runInitialSearch), + map(({ query }) => actions.search({ query })) ) ); - public toggleAssetDetail$ = createEffect(() => { - return this.actions$.pipe( - ofType(actions.assetClicked), - withLatestFrom(this.store.select(selectCurrentAssetDetail)), - switchMap(([{ assetId }, currentAssetDetail]) => - assetId !== currentAssetDetail?.assetId - ? this.assetSearchService - .loadAssetDetailData(assetId) - .pipe(map((assetDetail) => actions.updateAssetDetail({ assetDetail }))) - : of(actions.resetAssetDetail()) - ) - ); - }); - - public showAssetDetail$ = createEffect(() => { - return this.actions$.pipe( - ofType(actions.showAssetDetail), - withLatestFrom(this.store.select(selectCurrentAssetDetail)), - switchMap(([{ assetId }, currentAssetDetail]) => - assetId !== currentAssetDetail?.assetId - ? this.assetSearchService - .loadAssetDetailData(assetId) - .pipe(map((assetDetail) => actions.updateAssetDetail({ assetDetail }))) - : EMPTY - ) - ); - }); - - public closeDetailOnUpdateSearch$ = createEffect(() => { - return this.actions$.pipe( - ofType(actions.search, actions.resetSearch, actions.removePolygon), - map(() => actions.resetAssetDetail()) - ); - }); - - public openSearchResults$ = createEffect(() => { - return this.actions$.pipe( - ofType(actions.updateSearchResults), - takeUntil(this.actions$.pipe(ofType(actions.manualToggleResult))), - withLatestFrom(this.store.select(selectAssetSearchNoActiveFilters)), - map(([_, showStudies]) => (showStudies ? actions.closeResults() : actions.openResults())) - ); - }); + public mergeQuery$ = createEffect(() => + this.actions$.pipe( + ofType(actions.mergeQuery), + withLatestFrom(this.store.select(selectAssetSearchQuery)), + map(([{ query: nextQuery }, currentQuery]) => { + return actions.search({ query: { ...currentQuery, ...nextQuery } }); + }) + ) + ); - public closeSearchResults$ = createEffect(() => { - return this.actions$.pipe( - ofType(actions.updateSearchResults), - withLatestFrom(this.store.select(selectAssetSearchNoActiveFilters)), - filter(([_, showStudies]) => showStudies), - map(() => actions.closeResults()) - ); - }); + public loadSelectedAsset$ = createEffect(() => + this.actions$.pipe( + ofType(actions.selectAsset), + switchMap(({ assetId }) => this.assetSearchService.fetchAssetEditDetail(assetId)), + map((asset) => actions.setSelectedAsset({ asset })) + ) + ); public loadStudies$ = createEffect(() => { return this.actions$.pipe( - ofType(actions.initializeSearch), - switchMap(() => this.store.select(selectStudies)), - filter((x) => !x), + ofType(actions.initialize), + switchMap(() => this.store.select(selectStudies).pipe(take(1))), + filter(isNull), switchMap(() => this.allStudyService.getAllStudies().pipe(ORD.fromFilteredSuccess)), map((studies) => actions.setStudies({ studies })) ); }); - /** - * Query Parameter Interactions - */ - public queryParams$ = this.actions$.pipe( - ofType(ROUTER_NAVIGATED), - filter((x) => assetsPageMatcher(x.payload.routerState.root.firstChild.url) !== null), - map(({ payload }) => { - const params = payload.routerState.root.queryParams; - const query: AssetSearchQuery = {}; - const assetId: number | undefined = readNumberParam(params, QUERY_PARAM_MAPPING.assetId); - query.text = readStringParam(params, QUERY_PARAM_MAPPING.text); - query.polygon = readPolygonParam(params, QUERY_PARAM_MAPPING.polygon); - query.authorId = readNumberParam(params, QUERY_PARAM_MAPPING.authorId); - const min = readDateParam(params, QUERY_PARAM_MAPPING.createDate.min); - const max = readDateParam(params, QUERY_PARAM_MAPPING.createDate.max); - query.createDate = min && max ? { min, max } : undefined; - query.manCatLabelItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.manCatLabelItemCodes); - query.assetKindItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.assetKindItemCodes); - query.usageCodes = readArrayParam(params, QUERY_PARAM_MAPPING.usageCodes); - query.geometryCodes = readArrayParam(params, QUERY_PARAM_MAPPING.geometryCodes); - query.languageItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.languageItemCodes); - query.workgroupIds = readArrayParam(params, QUERY_PARAM_MAPPING.workgroupIds); - return { query, assetId }; - }), - shareReplay() + public triggerSearchExecution$ = createEffect(() => + this.actions$.pipe( + ofType(actions.search, actions.resetSearch, actions.clearPolygon), + switchMap((action) => { + if ('query' in action) { + return of(action.query); + } + return this.store.select(selectAssetSearchQuery).pipe(take(1)); + }), + map((query) => actions.executeSearch({ query })) + ) ); - public readSearchQueryParams$ = createEffect(() => - this.queryParams$.pipe( - withLatestFrom( - this.store.select(selectAssetSearchQuery), - this.store.select(selectAssetSearchResultData).pipe(map((r) => r.length > 0)) - ), - filter(([params, storeQuery]) => !deepEqual(params.query, storeQuery)), - map(([params, storeQuery, searchResultsLoaded]) => { - const paramsEmpty = Object.values(params.query).every((v) => v == null); - if (paramsEmpty) { - if (searchResultsLoaded) { - return actions.updateQueryParams(); - } else { - return actions.loadSearch({ query: storeQuery }); - } - } else { - return actions.search({ query: params.query }); - } - }) + public loadResults$ = createEffect(() => + this.actions$.pipe( + ofType(actions.executeSearch), + switchMap(({ query }) => this.assetSearchService.search(query)), + map((results) => actions.updateResults({ results })) ) ); - public readAssetIdQueryParam$ = createEffect(() => - this.queryParams$.pipe( - withLatestFrom(this.store.select(selectCurrentAssetDetail)), - filter(([params, storeAssetDetail]) => params.assetId !== storeAssetDetail?.assetId), - map(([params, storeAssetDetail]) => { - const assetId = storeAssetDetail?.assetId ?? params.assetId; - return assetId === undefined ? actions.resetAssetDetail() : actions.showAssetDetail({ assetId }); - }) + public loadStats$ = createEffect(() => + this.actions$.pipe( + ofType(actions.executeSearch), + switchMap(({ query }) => this.assetSearchService.searchStats(query)), + map((stats) => actions.updateStats({ stats })) ) ); - public updateQueryParams$ = createEffect( - () => - this.actions$.pipe( - ofType(actions.updateQueryParams, actions.updateAssetDetail, actions.resetAssetDetail, actions.loadSearch), - concatLatestFrom(() => [ - this.store.select(selectAssetSearchQuery), - this.store.select(selectCurrentAssetDetail), - this.queryParams$, - ]), - switchMap(([_, query, assetDetail, queryParams]) => { - const params: Params = { assetId: assetDetail?.assetId ?? queryParams.assetId }; - updatePlainParam(params, QUERY_PARAM_MAPPING.text, query.text); - updateArrayParam( - params, - QUERY_PARAM_MAPPING.polygon, - query.polygon?.map(({ x, y }) => `${x}:${y}`) - ); - updatePlainParam(params, QUERY_PARAM_MAPPING.authorId, query.authorId); - updateDateParam(params, QUERY_PARAM_MAPPING.createDate.min, query.createDate?.min); - updateDateParam(params, QUERY_PARAM_MAPPING.createDate.max, query.createDate?.max); - updateArrayParam(params, QUERY_PARAM_MAPPING.manCatLabelItemCodes, query.manCatLabelItemCodes); - updateArrayParam(params, QUERY_PARAM_MAPPING.assetKindItemCodes, query.assetKindItemCodes); - updateArrayParam(params, QUERY_PARAM_MAPPING.usageCodes, query.usageCodes); - updateArrayParam(params, QUERY_PARAM_MAPPING.geometryCodes, query.geometryCodes); - updateArrayParam(params, QUERY_PARAM_MAPPING.languageItemCodes, query.languageItemCodes); - updateArrayParam(params, QUERY_PARAM_MAPPING.workgroupIds, query.workgroupIds); - return this.router.navigate([], { queryParams: params }); - }) - ), - { dispatch: false } + public toggleResultsTable$ = createEffect(() => + this.actions$.pipe( + ofType(actions.updateResults), + map(({ results }) => results.page.total !== 0), + withLatestFrom(this.store.select(selectAssetSearchNoActiveFilters)), + map(([hasResults, hasNoFilters]) => + !hasResults || hasNoFilters ? actions.closeResults() : actions.openResults() + ) + ) ); + + public handleAssetClick$ = createEffect(() => { + return this.actions$.pipe( + ofType(actions.assetClicked), + withLatestFrom(this.store.select(selectCurrentAssetDetail)), + map(([{ assetId }, currentAssetDetail]) => + assetId === currentAssetDetail?.assetId ? actions.clearSelectedAsset() : actions.selectAsset({ assetId }) + ) + ); + }); + + public closeDetailOnUpdateSearch$ = createEffect(() => { + return this.actions$.pipe( + ofType(actions.search), + map(() => actions.clearSelectedAsset()) + ); + }); } const QUERY_PARAM_MAPPING = { @@ -249,11 +214,7 @@ const QUERY_PARAM_MAPPING = { }; const updatePlainParam = (params: Params, name: string, value: string | number | undefined): void => { - if (value == null) { - delete params[name]; - return; - } - params[name] = value; + params[name] = value == null || value === '' ? null : value; }; const updateDateParam = (params: Params, name: string, value: Date | undefined): void => { diff --git a/libs/asset-viewer/src/lib/state/asset-search/asset-search.reducer.ts b/libs/asset-viewer/src/lib/state/asset-search/asset-search.reducer.ts index f8b518fe..8d658153 100644 --- a/libs/asset-viewer/src/lib/state/asset-search/asset-search.reducer.ts +++ b/libs/asset-viewer/src/lib/state/asset-search/asset-search.reducer.ts @@ -15,6 +15,7 @@ export interface AssetSearchState { query: AssetSearchQuery; results: AssetSearchResult; stats: AssetSearchStats; + isInitialized: boolean; currentAsset: AssetEditDetail | undefined; resultsLoadingState: LoadingState; filterLoadingState: LoadingState; @@ -29,17 +30,8 @@ export interface AppStateWithAssetSearch extends AppState { } const initialState: AssetSearchState = { - query: { - text: undefined, - polygon: undefined, - authorId: undefined, - createDate: undefined, - manCatLabelItemCodes: undefined, - assetKindItemCodes: undefined, - usageCodes: undefined, - geometryCodes: undefined, - languageItemCodes: undefined, - }, + isInitialized: false, + query: {}, results: { page: { size: 0, @@ -70,6 +62,13 @@ const initialState: AssetSearchState = { export const assetSearchReducer = createReducer( initialState, + on( + actions.runInitialSearch, + (state): AssetSearchState => ({ + ...state, + isInitialized: true, + }) + ), on( actions.search, (state, { query }): AssetSearchState => ({ @@ -81,81 +80,70 @@ export const assetSearchReducer = createReducer( }) ), on( - actions.loadSearch, - (state): AssetSearchState => ({ - ...state, - resultsLoadingState: LoadingState.Loading, - filterLoadingState: LoadingState.Loading, - }) - ), - on( - actions.updateSearchResults, - (state, { searchResults }): AssetSearchState => ({ + actions.updateResults, + (state, { results }): AssetSearchState => ({ ...state, - results: { - page: searchResults.page, - data: searchResults.data, - }, + results, resultsLoadingState: LoadingState.Loaded, }) ), on( actions.updateStats, - (state, { searchStats }): AssetSearchState => ({ + (state, { stats }): AssetSearchState => ({ ...state, - stats: searchStats, + stats, filterLoadingState: LoadingState.Loaded, }) ), on( - actions.removePolygon, - (state): AssetSearchState => ({ - ...state, - query: { - ...state.query, - polygon: undefined, - }, - }) - ), - on( - actions.resetSearch, - (state): AssetSearchState => ({ - ...initialState, - stats: state.stats, - }) - ), - on( - actions.assetClicked, + actions.selectAsset, (state): AssetSearchState => ({ ...state, assetDetailLoadingState: LoadingState.Loading, }) ), on( - actions.updateAssetDetail, - (state, { assetDetail }): AssetSearchState => ({ + actions.setSelectedAsset, + (state, { asset }): AssetSearchState => ({ ...state, - currentAsset: assetDetail, + currentAsset: asset, assetDetailLoadingState: LoadingState.Loaded, }) ), on( - actions.resetAssetDetail, + actions.clearSelectedAsset, (state): AssetSearchState => ({ ...state, currentAsset: initialState.currentAsset, assetDetailLoadingState: initialState.assetDetailLoadingState, }) ), - on(appSharedStateActions.openPanel, (state): AssetSearchState => ({ ...state })), - on(actions.openFilters, (state): AssetSearchState => ({ ...state, isFiltersOpen: true })), - on(actions.closeFilters, (state): AssetSearchState => ({ ...state, isFiltersOpen: false })), on( appSharedStateActions.toggleSearchFilter, (state): AssetSearchState => ({ ...state, isFiltersOpen: !state.isFiltersOpen }) ), + on(actions.openFilters, (state): AssetSearchState => ({ ...state, isFiltersOpen: true })), + on(actions.closeFilters, (state): AssetSearchState => ({ ...state, isFiltersOpen: false })), on(actions.openResults, (state): AssetSearchState => ({ ...state, isResultsOpen: true })), on(actions.closeResults, (state): AssetSearchState => ({ ...state, isResultsOpen: false })), - on(actions.manualToggleResult, (state): AssetSearchState => ({ ...state, isResultsOpen: !state.isResultsOpen })), - on(actions.setStudies, (state, { studies }): AssetSearchState => ({ ...state, studies })) + on(actions.toggleResults, (state): AssetSearchState => ({ ...state, isResultsOpen: !state.isResultsOpen })), + on(actions.setStudies, (state, { studies }): AssetSearchState => ({ ...state, studies })), + on( + actions.clearPolygon, + (state): AssetSearchState => ({ + ...state, + query: { + ...state.query, + polygon: undefined, + }, + }) + ), + on( + actions.resetSearch, + (state): AssetSearchState => ({ + ...initialState, + stats: state.stats, + }) + ), + on(appSharedStateActions.openPanel, (state): AssetSearchState => ({ ...state })) ); diff --git a/libs/asset-viewer/src/lib/state/asset-search/asset-search.selector.ts b/libs/asset-viewer/src/lib/state/asset-search/asset-search.selector.ts index c9ac9849..15b9c9d4 100644 --- a/libs/asset-viewer/src/lib/state/asset-search/asset-search.selector.ts +++ b/libs/asset-viewer/src/lib/state/asset-search/asset-search.selector.ts @@ -47,6 +47,8 @@ export const selectAssetDetailLoadingState = createSelector( export const selectAssetSearchQuery = createSelector(assetSearchFeature, (state) => state.query); +export const selectAssetSearchIsInitialized = createSelector(assetSearchFeature, (state) => state.isInitialized); + export const selectAssetSearchResultData = createSelector(assetSearchFeature, (state) => state.results.data); export const selectAssetSearchPolygon = createSelector(assetSearchFeature, (state) => state.query.polygon);