From da161c5d56300faf12163e3f7ea9c0bd0a26cbff Mon Sep 17 00:00:00 2001 From: Daniel von Atzigen Date: Thu, 7 Nov 2024 08:27:22 +0100 Subject: [PATCH 1/9] Rewrite search effects --- .../asset-search-detail.component.html | 9 +- .../asset-search-detail.component.ts | 4 +- .../asset-search-filter-list.component.ts | 2 +- .../asset-search-refine.component.ts | 4 +- .../asset-search-results.component.html | 2 +- .../asset-search-results.component.ts | 4 +- .../asset-viewer-page.component.ts | 14 +- .../src/lib/components/map/map.component.ts | 2 +- .../src/lib/services/asset-search.service.ts | 12 +- .../asset-search/asset-search.actions.ts | 54 +-- .../asset-search/asset-search.effects.ts | 321 ++++++++---------- .../asset-search/asset-search.reducer.ts | 96 +++--- .../asset-search/asset-search.selector.ts | 2 + 13 files changed, 247 insertions(+), 279 deletions(-) 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); From 7aa86690738317c8970330a06a1f504b8e294caf Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Mon, 11 Nov 2024 15:03:25 +0100 Subject: [PATCH 2/9] Working State of routing, not robust --- .../asset-search/asset-search.effects.ts | 87 +++++++++++++++++-- .../asset-search/asset-search.reducer.ts | 1 + 2 files changed, 82 insertions(+), 6 deletions(-) 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 dec25f9f..3c576b09 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,13 +1,14 @@ import { inject, Injectable } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -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 { AppState, assetsPageMatcher } from '@asset-sg/client-shared'; +import { deepEqual, isNotNull, isNull, ORD } from '@asset-sg/core'; +import { AssetSearchQuery, LV95, Polygon } from '@asset-sg/shared'; import { UntilDestroy } from '@ngneat/until-destroy'; import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { ROUTER_NAVIGATED } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; -import { combineLatest, filter, map, of, switchMap, take, withLatestFrom } from 'rxjs'; +import { combineLatest, filter, map, of, shareReplay, switchMap, take, withLatestFrom } from 'rxjs'; import { AllStudyService } from '../../services/all-study.service'; import { AssetSearchService } from '../../services/asset-search.service'; @@ -19,6 +20,7 @@ import { selectAssetSearchIsInitialized, selectAssetSearchNoActiveFilters, selectAssetSearchQuery, + selectAssetSearchResultData, selectCurrentAssetDetail, selectStudies, } from './asset-search.selector'; @@ -58,6 +60,69 @@ export class AssetSearchEffects { ) ); + 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); + console.log('queryParams$', query, assetId); + return { query, assetId }; + }), + shareReplay() + ); + + public readSearchQueryParams$ = createEffect(() => + this.queryParams$.pipe( + withLatestFrom( + this.store.select(selectAssetSearchQuery), + this.store.select(selectCurrentAssetDetail), + this.store.select(selectAssetSearchResultData).pipe(map((r) => r.length > 0)) + ), + filter(([params, storeQuery, storeDetail]) => { + console.log( + 'filter', + params.query, + storeQuery, + params.assetId, + storeDetail, + deepEqual(params.query, storeQuery), + params.assetId != storeDetail?.assetId + ); + return !deepEqual(params.query, storeQuery) || params.assetId != storeDetail?.assetId; + }), + map(([params, storeQuery, storeDetail, searchResultsLoaded]) => { + console.log('map', params.query, storeQuery, storeDetail, searchResultsLoaded); + const paramsEmpty = Object.values(params.query).every((v) => v == null); + if (paramsEmpty) { + if (searchResultsLoaded) { + console.log('mergeQuery', params.query, params.assetId); + return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); + } else { + console.log('search', storeQuery); + return actions.runInitialSearch({ query: storeQuery, assetId: params.assetId }); + } + } else { + console.log('search params', params.query); + return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); + } + }) + ) + ); + public updateParamsFromQuery$ = createEffect( () => combineLatest([ @@ -65,12 +130,13 @@ export class AssetSearchEffects { this.store.select(selectCurrentAssetDetail), this.store.select(selectAssetDetailLoadingState), this.store.select(selectAssetSearchIsInitialized), + // this.route.queryParams, ]).pipe( filter(([_query, _asset, _state, isInitialized]) => isInitialized), withLatestFrom(this.authService.isInitialized$), filter(([_search, isInitialized]) => isInitialized), map(([search]) => search), - switchMap(([query, asset, assetLoadingState]) => { + switchMap(([query, asset, assetLoadingState, _initialized]) => { const params: Params = {}; updatePlainParam(params, QUERY_PARAM_MAPPING.text, query.text); updateArrayParam( @@ -90,7 +156,14 @@ export class AssetSearchEffects { if (assetLoadingState !== LoadingState.Loading) { updatePlainParam(params, QUERY_PARAM_MAPPING.assetId, asset?.assetId); } - return this.router.navigate([], { queryParams: params, queryParamsHandling: 'merge' }); + return of(params); + }), + withLatestFrom(this.queryParams$), + map(([builtParams, actualParams]) => { + if (!deepEqual(builtParams['query'], actualParams.query)) { + return this.router.navigate([], { queryParams: builtParams, queryParamsHandling: 'merge' }); + } + return of(null); }) ), { dispatch: false } @@ -191,6 +264,8 @@ export class AssetSearchEffects { public closeDetailOnUpdateSearch$ = createEffect(() => { return this.actions$.pipe( ofType(actions.search), + withLatestFrom(this.store.select(selectAssetDetailLoadingState)), + filter(([, loadingState]) => loadingState != LoadingState.Loading), map(() => actions.clearSelectedAsset()) ); }); 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 8d658153..bad4fdd9 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 @@ -143,6 +143,7 @@ export const assetSearchReducer = createReducer( (state): AssetSearchState => ({ ...initialState, stats: state.stats, + isInitialized: state.isInitialized, }) ), on(appSharedStateActions.openPanel, (state): AssetSearchState => ({ ...state })) From 1fe563520df8b3bc9b38997537e3226583a65c17 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Mon, 11 Nov 2024 17:23:31 +0100 Subject: [PATCH 3/9] Working State of routing --- .../asset-search/asset-search.effects.ts | 74 ++++++------------- 1 file changed, 21 insertions(+), 53 deletions(-) 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 3c576b09..90087264 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,5 +1,5 @@ import { inject, Injectable } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { ActivatedRoute, NavigationStart, Params, Router } from '@angular/router'; import { AuthService } from '@asset-sg/auth'; import { AppState, assetsPageMatcher } from '@asset-sg/client-shared'; import { deepEqual, isNotNull, isNull, ORD } from '@asset-sg/core'; @@ -8,7 +8,7 @@ import { UntilDestroy } from '@ngneat/until-destroy'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { ROUTER_NAVIGATED } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; -import { combineLatest, filter, map, of, shareReplay, switchMap, take, withLatestFrom } from 'rxjs'; +import { combineLatest, filter, map, of, pairwise, switchMap, take, withLatestFrom } from 'rxjs'; import { AllStudyService } from '../../services/all-study.service'; import { AssetSearchService } from '../../services/asset-search.service'; @@ -30,35 +30,24 @@ import { export class AssetSearchEffects { private readonly store = inject(Store); private readonly actions$ = inject(Actions); - private readonly router = inject(Router); + // private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly assetSearchService = inject(AssetSearchService); private readonly allStudyService = inject(AllStudyService); private readonly authService = inject(AuthService); + private isLatestPage = false; - public updateQueryFromParams$ = createEffect(() => - this.actions$.pipe( - 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 }); - }) - ) - ); + constructor(private router: Router) { + this.router.events + .pipe( + filter((event) => event instanceof NavigationStart), + pairwise() + ) + .subscribe((events) => { + this.isLatestPage = (events[1] as NavigationStart).restoredState == null; + console.log(this.isLatestPage); + }); + } public queryParams$ = this.actions$.pipe( ofType(ROUTER_NAVIGATED), @@ -79,10 +68,8 @@ export class AssetSearchEffects { query.geometryCodes = readArrayParam(params, QUERY_PARAM_MAPPING.geometryCodes); query.languageItemCodes = readArrayParam(params, QUERY_PARAM_MAPPING.languageItemCodes); query.workgroupIds = readArrayParam(params, QUERY_PARAM_MAPPING.workgroupIds); - console.log('queryParams$', query, assetId); return { query, assetId }; - }), - shareReplay() + }) ); public readSearchQueryParams$ = createEffect(() => @@ -93,30 +80,18 @@ export class AssetSearchEffects { this.store.select(selectAssetSearchResultData).pipe(map((r) => r.length > 0)) ), filter(([params, storeQuery, storeDetail]) => { - console.log( - 'filter', - params.query, - storeQuery, - params.assetId, - storeDetail, - deepEqual(params.query, storeQuery), - params.assetId != storeDetail?.assetId - ); return !deepEqual(params.query, storeQuery) || params.assetId != storeDetail?.assetId; }), map(([params, storeQuery, storeDetail, searchResultsLoaded]) => { - console.log('map', params.query, storeQuery, storeDetail, searchResultsLoaded); const paramsEmpty = Object.values(params.query).every((v) => v == null); + const storeEmpty = Object.values(storeQuery).every((v) => v == null); if (paramsEmpty) { - if (searchResultsLoaded) { - console.log('mergeQuery', params.query, params.assetId); - return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); + if ((!storeEmpty || storeDetail) && this.isLatestPage) { + return actions.runInitialSearch({ query: storeQuery, assetId: storeDetail?.assetId }); } else { - console.log('search', storeQuery); - return actions.runInitialSearch({ query: storeQuery, assetId: params.assetId }); + return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); } } else { - console.log('search params', params.query); return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); } }) @@ -156,14 +131,7 @@ export class AssetSearchEffects { if (assetLoadingState !== LoadingState.Loading) { updatePlainParam(params, QUERY_PARAM_MAPPING.assetId, asset?.assetId); } - return of(params); - }), - withLatestFrom(this.queryParams$), - map(([builtParams, actualParams]) => { - if (!deepEqual(builtParams['query'], actualParams.query)) { - return this.router.navigate([], { queryParams: builtParams, queryParamsHandling: 'merge' }); - } - return of(null); + return this.router.navigate([], { queryParams: params, queryParamsHandling: 'merge' }); }) ), { dispatch: false } From 20919b2b48bffaaa6d35e9bfa38768d588629a98 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Tue, 12 Nov 2024 08:09:46 +0100 Subject: [PATCH 4/9] Cleanup --- .../asset-search/asset-search.actions.ts | 4 +- .../asset-search/asset-search.effects.ts | 42 +++++++------------ .../asset-search/asset-search.reducer.ts | 2 +- 3 files changed, 18 insertions(+), 30 deletions(-) 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 cf7a302b..60a2e954 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 @@ -4,8 +4,8 @@ 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', +export const runCombinedSearch = createAction( + '[Asset Search] Run Combined Search', props<{ assetId: number | undefined; query: AssetSearchQuery; 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 90087264..05638116 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,5 +1,5 @@ import { inject, Injectable } from '@angular/core'; -import { ActivatedRoute, NavigationStart, Params, Router } from '@angular/router'; +import { NavigationStart, Params, Router } from '@angular/router'; import { AuthService } from '@asset-sg/auth'; import { AppState, assetsPageMatcher } from '@asset-sg/client-shared'; import { deepEqual, isNotNull, isNull, ORD } from '@asset-sg/core'; @@ -8,7 +8,7 @@ import { UntilDestroy } from '@ngneat/until-destroy'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { ROUTER_NAVIGATED } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; -import { combineLatest, filter, map, of, pairwise, switchMap, take, 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'; @@ -20,7 +20,6 @@ import { selectAssetSearchIsInitialized, selectAssetSearchNoActiveFilters, selectAssetSearchQuery, - selectAssetSearchResultData, selectCurrentAssetDetail, selectStudies, } from './asset-search.selector'; @@ -30,8 +29,6 @@ import { export class AssetSearchEffects { private readonly store = inject(Store); private readonly actions$ = inject(Actions); - // private readonly router = inject(Router); - private readonly route = inject(ActivatedRoute); private readonly assetSearchService = inject(AssetSearchService); private readonly allStudyService = inject(AllStudyService); private readonly authService = inject(AuthService); @@ -39,13 +36,9 @@ export class AssetSearchEffects { constructor(private router: Router) { this.router.events - .pipe( - filter((event) => event instanceof NavigationStart), - pairwise() - ) - .subscribe((events) => { - this.isLatestPage = (events[1] as NavigationStart).restoredState == null; - console.log(this.isLatestPage); + .pipe(filter((event): event is NavigationStart => event instanceof NavigationStart)) + .subscribe((event) => { + this.isLatestPage = event.restoredState == null; }); } @@ -74,25 +67,21 @@ export class AssetSearchEffects { public readSearchQueryParams$ = createEffect(() => this.queryParams$.pipe( - withLatestFrom( - this.store.select(selectAssetSearchQuery), - this.store.select(selectCurrentAssetDetail), - this.store.select(selectAssetSearchResultData).pipe(map((r) => r.length > 0)) - ), + withLatestFrom(this.store.select(selectAssetSearchQuery), this.store.select(selectCurrentAssetDetail)), filter(([params, storeQuery, storeDetail]) => { return !deepEqual(params.query, storeQuery) || params.assetId != storeDetail?.assetId; }), - map(([params, storeQuery, storeDetail, searchResultsLoaded]) => { + map(([params, storeQuery, storeDetail]) => { const paramsEmpty = Object.values(params.query).every((v) => v == null); - const storeEmpty = Object.values(storeQuery).every((v) => v == null); + const storeQueryEmpty = Object.values(storeQuery).every((v) => v == null); if (paramsEmpty) { - if ((!storeEmpty || storeDetail) && this.isLatestPage) { - return actions.runInitialSearch({ query: storeQuery, assetId: storeDetail?.assetId }); + if ((!storeQueryEmpty || storeDetail) && this.isLatestPage) { + return actions.runCombinedSearch({ query: storeQuery, assetId: storeDetail?.assetId }); } else { - return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); + return actions.runCombinedSearch({ query: params.query, assetId: params.assetId }); } } else { - return actions.runInitialSearch({ query: params.query, assetId: params.assetId }); + return actions.runCombinedSearch({ query: params.query, assetId: params.assetId }); } }) ) @@ -105,13 +94,12 @@ export class AssetSearchEffects { this.store.select(selectCurrentAssetDetail), this.store.select(selectAssetDetailLoadingState), this.store.select(selectAssetSearchIsInitialized), - // this.route.queryParams, ]).pipe( filter(([_query, _asset, _state, isInitialized]) => isInitialized), withLatestFrom(this.authService.isInitialized$), filter(([_search, isInitialized]) => isInitialized), map(([search]) => search), - switchMap(([query, asset, assetLoadingState, _initialized]) => { + switchMap(([query, asset, assetLoadingState]) => { const params: Params = {}; updatePlainParam(params, QUERY_PARAM_MAPPING.text, query.text); updateArrayParam( @@ -139,14 +127,14 @@ export class AssetSearchEffects { public initializeAsset$ = createEffect(() => this.actions$.pipe( - ofType(actions.runInitialSearch), + ofType(actions.runCombinedSearch), map(({ assetId }) => (assetId == null ? actions.clearSelectedAsset() : actions.selectAsset({ assetId }))) ) ); public initializeQuery$ = createEffect(() => this.actions$.pipe( - ofType(actions.runInitialSearch), + ofType(actions.runCombinedSearch), map(({ query }) => actions.search({ query })) ) ); 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 bad4fdd9..43b4eb3a 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 @@ -63,7 +63,7 @@ const initialState: AssetSearchState = { export const assetSearchReducer = createReducer( initialState, on( - actions.runInitialSearch, + actions.runCombinedSearch, (state): AssetSearchState => ({ ...state, isInitialized: true, From 7c8063f712826aa76ea274ebae2222349bc3ed55 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Tue, 12 Nov 2024 11:36:10 +0100 Subject: [PATCH 5/9] Cleanup --- .../asset-search/asset-search.effects.ts | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) 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 05638116..5925dffd 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 @@ -8,7 +8,7 @@ import { UntilDestroy } from '@ngneat/until-destroy'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { ROUTER_NAVIGATED } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; -import { combineLatest, filter, map, of, switchMap, take, withLatestFrom } from 'rxjs'; +import { filter, map, of, startWith, switchMap, take, withLatestFrom } from 'rxjs'; import { AllStudyService } from '../../services/all-study.service'; import { AssetSearchService } from '../../services/asset-search.service'; @@ -21,6 +21,7 @@ import { selectAssetSearchNoActiveFilters, selectAssetSearchQuery, selectCurrentAssetDetail, + selectSearchLoadingState, selectStudies, } from './asset-search.selector'; @@ -32,15 +33,7 @@ export class AssetSearchEffects { private readonly assetSearchService = inject(AssetSearchService); private readonly allStudyService = inject(AllStudyService); private readonly authService = inject(AuthService); - private isLatestPage = false; - - constructor(private router: Router) { - this.router.events - .pipe(filter((event): event is NavigationStart => event instanceof NavigationStart)) - .subscribe((event) => { - this.isLatestPage = event.restoredState == null; - }); - } + private readonly router = inject(Router); public queryParams$ = this.actions$.pipe( ofType(ROUTER_NAVIGATED), @@ -65,41 +58,54 @@ export class AssetSearchEffects { }) ); - public readSearchQueryParams$ = createEffect(() => + public isMostRecentPage$ = this.router.events.pipe( + filter((event): event is NavigationStart => event instanceof NavigationStart), + map((event) => event.restoredState == null), + startWith(false) + ); + + public readSearchQueryParams1$ = createEffect(() => this.queryParams$.pipe( - withLatestFrom(this.store.select(selectAssetSearchQuery), this.store.select(selectCurrentAssetDetail)), + withLatestFrom( + this.store.select(selectAssetSearchQuery), + this.store.select(selectCurrentAssetDetail), + this.isMostRecentPage$ + ), filter(([params, storeQuery, storeDetail]) => { return !deepEqual(params.query, storeQuery) || params.assetId != storeDetail?.assetId; }), - map(([params, storeQuery, storeDetail]) => { - const paramsEmpty = Object.values(params.query).every((v) => v == null); - const storeQueryEmpty = Object.values(storeQuery).every((v) => v == null); - if (paramsEmpty) { - if ((!storeQueryEmpty || storeDetail) && this.isLatestPage) { - return actions.runCombinedSearch({ query: storeQuery, assetId: storeDetail?.assetId }); - } else { - return actions.runCombinedSearch({ query: params.query, assetId: params.assetId }); - } - } else { - return actions.runCombinedSearch({ query: params.query, assetId: params.assetId }); + map(([params, storeQuery, storeDetail, isMostRecentPage]) => { + const hasNoQueryParams = Object.values(params.query).every((v) => v == null); + const hasQueryOrAssetIdInStore = !Object.values(storeQuery).every((v) => v == null) || storeDetail; + // We only use the values from the store if all of the below are true: + // - There are no Url query params + // - There are query or assetId values in the store + // - The current page is the most recent page + if (hasNoQueryParams && hasQueryOrAssetIdInStore && isMostRecentPage) { + return actions.runCombinedSearch({ query: storeQuery, assetId: storeDetail?.assetId }); } + return actions.runCombinedSearch({ query: params.query, assetId: params.assetId }); }) ) ); 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), + this.actions$.pipe( + ofType(actions.search, actions.resetSearch, actions.setSelectedAsset, actions.clearSelectedAsset), + withLatestFrom( + this.store.select(selectAssetSearchQuery), + this.store.select(selectCurrentAssetDetail), + this.store.select(selectAssetDetailLoadingState), + this.store.select(selectSearchLoadingState), + this.store.select(selectAssetSearchIsInitialized) + ), + filter(([_, _query, _asset, _assetLoadingState, _searchLoadingState, isInitialized]) => isInitialized), withLatestFrom(this.authService.isInitialized$), filter(([_search, isInitialized]) => isInitialized), map(([search]) => search), - switchMap(([query, asset, assetLoadingState]) => { + + switchMap(([_, query, asset, assetLoadingState, searchLoadingState]) => { const params: Params = {}; updatePlainParam(params, QUERY_PARAM_MAPPING.text, query.text); updateArrayParam( @@ -119,7 +125,16 @@ export class AssetSearchEffects { if (assetLoadingState !== LoadingState.Loading) { updatePlainParam(params, QUERY_PARAM_MAPPING.assetId, asset?.assetId); } - return this.router.navigate([], { queryParams: params, queryParamsHandling: 'merge' }); + + const isLoading = searchLoadingState === LoadingState.Loading || assetLoadingState === LoadingState.Loading; + if (!isLoading) { + return this.router.navigate([], { + queryParams: params, + queryParamsHandling: 'merge', + }); + } else { + return of(null); + } }) ), { dispatch: false } From e7f1fcb293297d6c72aa7ead2e7a7fa39e7e1a65 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Tue, 12 Nov 2024 14:03:34 +0100 Subject: [PATCH 6/9] Fixes after merge --- .../client-asset-sg/src/environments/environment.ts | 2 +- .../lib/state/asset-search/asset-search.effects.ts | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/client-asset-sg/src/environments/environment.ts b/apps/client-asset-sg/src/environments/environment.ts index 32dd082d..acd3627a 100644 --- a/apps/client-asset-sg/src/environments/environment.ts +++ b/apps/client-asset-sg/src/environments/environment.ts @@ -1,6 +1,6 @@ import { CompileTimeEnvironment } from './environment-type'; export const environment: CompileTimeEnvironment = { - ngrxStoreLoggerEnabled: false, + ngrxStoreLoggerEnabled: true, hideDisclaimer: true, }; 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 dd24743f..c2b5b059 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 @@ -81,8 +81,9 @@ export class AssetSearchEffects { return !deepEqual(params.query, storeQuery) || params.assetId != storeDetail?.assetId; }), map(([params, storeQuery, storeDetail, isMostRecentPage]) => { - const hasNoQueryParams = Object.values(params.query).every((v) => v == null); - const hasQueryOrAssetIdInStore = !Object.values(storeQuery).every((v) => v == null) || storeDetail; + const hasNoQueryParams = Object.values(params.query).every((v) => v == null || v == false); + const hasQueryOrAssetIdInStore = + !Object.values(storeQuery).every((v) => v == null || v == false) || storeDetail; // We only use the values from the store if all of the below are true: // - There are no Url query params // - There are query or assetId values in the store @@ -266,12 +267,8 @@ const QUERY_PARAM_MAPPING = { categories: 'search[categories]', }; -const updatePlainParam = (params: Params, name: string, value: string | number | boolean | undefined): void => { - if (value == null) { - delete params[name]; - return; - } - params[name] = value; +const updatePlainParam = (params: Params, name: string, value: string | number | undefined): void => { + params[name] = value == null || value === '' ? null : value; }; const updateDateParam = (params: Params, name: string, value: Date | undefined): void => { From 54cad86e8c9f0f0ff0dcfa5eb5b4cc2294d918cf Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Wed, 13 Nov 2024 08:24:51 +0100 Subject: [PATCH 7/9] Remove circular dependencies --- apps/client-asset-sg/src/app/app.component.ts | 11 ++++++++--- apps/client-asset-sg/src/app/app.module.ts | 8 +++----- .../src/app/components/app-bar/app-bar.component.ts | 3 +-- .../redirect-to-lang/redirect-to-lang.component.ts | 2 +- .../splash-screen/splash-screen.component.ts | 3 +-- .../src/app/services/http.interceptor.ts | 3 +-- apps/client-asset-sg/src/app/state/app.effects.ts | 3 +-- .../asset-viewer-page/asset-viewer-page.component.ts | 9 +++++++-- .../lib/state/asset-search/asset-search.effects.ts | 4 +--- libs/client-shared/src/index.ts | 5 +++++ .../disclaimer-dialog.component.html | 0 .../disclaimer-dialog.component.scss | 0 .../disclaimer-dialog/disclaimer-dialog.component.ts | 10 ++++++++-- .../src/lib}/environments/environment-type.ts | 0 .../src/lib}/environments/environment.prod.ts | 0 .../src/lib}/environments/environment.ts | 0 .../src/lib}/features/auth/auth.module.ts | 6 +++--- .../src/lib}/features/auth/auth.service.ts | 5 +++-- .../src/lib}/features/auth/error.service.ts | 0 19 files changed, 43 insertions(+), 29 deletions(-) rename {apps/client-asset-sg/src/app => libs/client-shared/src/lib}/components/disclaimer-dialog/disclaimer-dialog.component.html (100%) rename {apps/client-asset-sg/src/app => libs/client-shared/src/lib}/components/disclaimer-dialog/disclaimer-dialog.component.scss (100%) rename {apps/client-asset-sg/src/app => libs/client-shared/src/lib}/components/disclaimer-dialog/disclaimer-dialog.component.ts (65%) rename {apps/client-asset-sg/src => libs/client-shared/src/lib}/environments/environment-type.ts (100%) rename {apps/client-asset-sg/src => libs/client-shared/src/lib}/environments/environment.prod.ts (100%) rename {apps/client-asset-sg/src => libs/client-shared/src/lib}/environments/environment.ts (100%) rename {apps/client-asset-sg/src/app => libs/client-shared/src/lib}/features/auth/auth.module.ts (87%) rename {apps/client-asset-sg/src/app => libs/client-shared/src/lib}/features/auth/auth.service.ts (96%) rename {apps/client-asset-sg/src/app => libs/client-shared/src/lib}/features/auth/error.service.ts (100%) diff --git a/apps/client-asset-sg/src/app/app.component.ts b/apps/client-asset-sg/src/app/app.component.ts index d8498673..981899f4 100644 --- a/apps/client-asset-sg/src/app/app.component.ts +++ b/apps/client-asset-sg/src/app/app.component.ts @@ -1,13 +1,18 @@ import { HttpClient } from '@angular/common/http'; import { Component, inject } from '@angular/core'; -import { AppPortalService, appSharedStateActions, setCssCustomProperties } from '@asset-sg/client-shared'; +import { + AppPortalService, + appSharedStateActions, + AuthService, + AuthState, + ErrorService, + setCssCustomProperties, +} from '@asset-sg/client-shared'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { Store } from '@ngrx/store'; import { WINDOW } from 'ngx-window-token'; import { debounceTime, fromEvent, startWith, switchMap } from 'rxjs'; import { assert } from 'tsafe'; -import { AuthService, AuthState } from './features/auth/auth.service'; -import { ErrorService } from './features/auth/error.service'; import { AppState } from './state/app-state'; const fullHdWidth = 1920; diff --git a/apps/client-asset-sg/src/app/app.module.ts b/apps/client-asset-sg/src/app/app.module.ts index 2c9b146a..4b0bedb2 100644 --- a/apps/client-asset-sg/src/app/app.module.ts +++ b/apps/client-asset-sg/src/app/app.module.ts @@ -19,14 +19,17 @@ import { AlertModule, AnchorComponent, assetsPageMatcher, + AuthModule, ButtonComponent, CanCreateDirective, CURRENT_LANG, currentLangFactory, + ErrorService, icons, LanguageSelectorComponent, TranslateTsLoader, } from '@asset-sg/client-shared'; +import { environment } from '@asset-sg/client-shared'; import { storeLogger } from '@asset-sg/core'; import { provideSvgIcons, SvgIconComponent } from '@ngneat/svg-icon'; import { EffectsModule } from '@ngrx/effects'; @@ -36,16 +39,12 @@ import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-transla import { ForModule } from '@rx-angular/template/for'; import { LetModule } from '@rx-angular/template/let'; import { PushModule } from '@rx-angular/template/push'; -import { environment } from '../environments/environment'; import { adminGuard } from './app-guards'; import { AppComponent } from './app.component'; import { AppBarComponent, MenuBarComponent, NotFoundComponent, RedirectToLangComponent } from './components'; -import { DisclaimerDialogComponent } from './components/disclaimer-dialog/disclaimer-dialog.component'; import { MenuBarItemComponent } from './components/menu-bar-item/menu-bar-item.component'; import { SplashScreenComponent } from './components/splash-screen/splash-screen.component'; -import { AuthModule } from './features/auth/auth.module'; -import { ErrorService } from './features/auth/error.service'; import { appTranslations } from './i18n'; import { HttpInterceptor } from './services/http.interceptor'; import { AppSharedStateEffects } from './state'; @@ -62,7 +61,6 @@ registerLocaleData(locale_deCH, 'de-CH'); MenuBarComponent, MenuBarItemComponent, SplashScreenComponent, - DisclaimerDialogComponent, ], imports: [ CommonModule, diff --git a/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.ts b/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.ts index 5fb5805f..43901fda 100644 --- a/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.ts +++ b/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.ts @@ -11,12 +11,11 @@ import { ViewChild, } from '@angular/core'; import { Router } from '@angular/router'; -import { appSharedStateActions, fromAppShared } from '@asset-sg/client-shared'; +import { appSharedStateActions, AuthService, fromAppShared } from '@asset-sg/client-shared'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { Store } from '@ngrx/store'; import * as O from 'fp-ts/Option'; import { EMPTY, filter, map, Observable, Subject, switchMap } from 'rxjs'; -import { AuthService } from '../../features/auth/auth.service'; import { AppState } from '../../state/app-state'; import { Version } from './version'; diff --git a/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts b/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts index b48ec478..b5be7ddb 100644 --- a/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts +++ b/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; +import { AuthService } from '@asset-sg/client-shared'; import { UntilDestroy } from '@ngneat/until-destroy'; -import { AuthService } from '../../features/auth/auth.service'; @UntilDestroy() @Component({ diff --git a/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts b/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts index 3d0d743b..41fc163f 100644 --- a/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts +++ b/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts @@ -1,7 +1,6 @@ import { Component, inject } from '@angular/core'; -import { CURRENT_LANG } from '@asset-sg/client-shared'; +import { AuthService, AuthState, CURRENT_LANG } from '@asset-sg/client-shared'; import { TranslateService } from '@ngx-translate/core'; -import { AuthService, AuthState } from '../../features/auth/auth.service'; @Component({ selector: 'app-splash-screen', diff --git a/apps/client-asset-sg/src/app/services/http.interceptor.ts b/apps/client-asset-sg/src/app/services/http.interceptor.ts index b8c15669..904ea5d6 100644 --- a/apps/client-asset-sg/src/app/services/http.interceptor.ts +++ b/apps/client-asset-sg/src/app/services/http.interceptor.ts @@ -1,12 +1,11 @@ import { HttpErrorResponse, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http'; import { inject, Injectable, OnDestroy } from '@angular/core'; import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; -import { AlertType, fromAppShared, showAlert } from '@asset-sg/client-shared'; +import { AlertType, AuthService, AuthState, fromAppShared, showAlert } from '@asset-sg/client-shared'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { catchError, EMPTY, from, Observable, Subscription, switchMap } from 'rxjs'; -import { AuthService, AuthState } from '../features/auth/auth.service'; @Injectable() export class HttpInterceptor implements HttpInterceptor, OnDestroy { diff --git a/apps/client-asset-sg/src/app/state/app.effects.ts b/apps/client-asset-sg/src/app/state/app.effects.ts index 5f088aed..a20934a6 100644 --- a/apps/client-asset-sg/src/app/state/app.effects.ts +++ b/apps/client-asset-sg/src/app/state/app.effects.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { NavigationEnd, Router, RouterStateSnapshot } from '@angular/router'; -import { appSharedStateActions, fromAppShared } from '@asset-sg/client-shared'; +import { appSharedStateActions, AuthService, fromAppShared } from '@asset-sg/client-shared'; import { ORD } from '@asset-sg/core'; import { eqLangRight, Lang } from '@asset-sg/shared'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -10,7 +10,6 @@ import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import * as E from 'fp-ts/Either'; import { combineLatest, distinctUntilChanged, filter, map, switchMap, take } from 'rxjs'; -import { AuthService } from '../features/auth/auth.service'; import { AppSharedStateService } from './app-shared-state.service'; import { AppState } from './app-state'; 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 713e9116..6acb9603 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,7 +10,13 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { AppPortalService, AppState, LifecycleHooks, LifecycleHooksDirective } from '@asset-sg/client-shared'; +import { + AppPortalService, + AppState, + AuthService, + LifecycleHooks, + LifecycleHooksDirective, +} from '@asset-sg/client-shared'; import { AssetEditDetail } from '@asset-sg/shared'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { Store } from '@ngrx/store'; @@ -33,7 +39,6 @@ import { take, withLatestFrom, } from 'rxjs'; -import { AuthService } from '../../../../../../apps/client-asset-sg/src/app/features/auth/auth.service'; import * as actions from '../../state/asset-search/asset-search.actions'; import { LoadingState } from '../../state/asset-search/asset-search.reducer'; 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 c2b5b059..0a4be95e 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,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { NavigationStart, Params, Router } from '@angular/router'; -import { AppState, assetsPageMatcher } from '@asset-sg/client-shared'; +import { AppState, assetsPageMatcher, AuthService } from '@asset-sg/client-shared'; import { deepEqual, isNotNull, isNull, ORD } from '@asset-sg/core'; import { AssetSearchQuery, LV95, Polygon } from '@asset-sg/shared'; import { UntilDestroy } from '@ngneat/until-destroy'; @@ -8,8 +8,6 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { ROUTER_NAVIGATED, RouterNavigatedPayload } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; import { filter, map, of, startWith, switchMap, take, withLatestFrom } from 'rxjs'; - -import { AuthService } from '../../../../../../apps/client-asset-sg/src/app/features/auth/auth.service'; import { AllStudyService } from '../../services/all-study.service'; import { AssetSearchService } from '../../services/asset-search.service'; diff --git a/libs/client-shared/src/index.ts b/libs/client-shared/src/index.ts index 79691443..9d8bf528 100644 --- a/libs/client-shared/src/index.ts +++ b/libs/client-shared/src/index.ts @@ -17,9 +17,14 @@ export * from './lib/features/alert/alert.model'; export * from './lib/features/alert/alert.module'; export * from './lib/features/alert/alert.reducer'; export * from './lib/features/alert/alert.selectors'; +export * from './lib/features/auth/auth.module'; +export * from './lib/features/auth/auth.service'; +export * from './lib/features/auth/error.service'; export * from './lib/directives/admin-only.directive'; export * from './lib/directives/can-create.directive'; export * from './lib/directives/can-delete.directive'; export * from './lib/directives/can-show.directive'; export * from './lib/directives/can-update.directive'; +export * from './lib/environments/environment'; +export * from './lib/environments/environment-type'; diff --git a/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.html b/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.html similarity index 100% rename from apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.html rename to libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.html diff --git a/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.scss b/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.scss similarity index 100% rename from apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.scss rename to libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.scss diff --git a/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.ts b/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts similarity index 65% rename from apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.ts rename to libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts index 423ce49a..a55029a9 100644 --- a/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.ts +++ b/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts @@ -1,15 +1,21 @@ +import { AsyncPipe } from '@angular/common'; import { Component, inject } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; -import { CURRENT_LANG } from '@asset-sg/client-shared'; +import { MatDialogActions, MatDialogContent, MatDialogRef } from '@angular/material/dialog'; +import { MatDivider } from '@angular/material/divider'; import { TranslateService } from '@ngx-translate/core'; import { map } from 'rxjs'; +import { ButtonComponent } from '../../components/button'; +import { LanguageSelectorComponent } from '../../components/language-selector'; +import { CURRENT_LANG } from '../../utils'; const LEGAL_BASE_URL = 'https://www.swissgeol.ch/datenschutz'; @Component({ + standalone: true, selector: 'asset-sg-disclaimer-dialog', templateUrl: './disclaimer-dialog.component.html', styleUrl: './disclaimer-dialog.component.scss', + imports: [LanguageSelectorComponent, MatDivider, MatDialogActions, MatDialogContent, ButtonComponent, AsyncPipe], }) export class DisclaimerDialogComponent { public text = ''; diff --git a/apps/client-asset-sg/src/environments/environment-type.ts b/libs/client-shared/src/lib/environments/environment-type.ts similarity index 100% rename from apps/client-asset-sg/src/environments/environment-type.ts rename to libs/client-shared/src/lib/environments/environment-type.ts diff --git a/apps/client-asset-sg/src/environments/environment.prod.ts b/libs/client-shared/src/lib/environments/environment.prod.ts similarity index 100% rename from apps/client-asset-sg/src/environments/environment.prod.ts rename to libs/client-shared/src/lib/environments/environment.prod.ts diff --git a/apps/client-asset-sg/src/environments/environment.ts b/libs/client-shared/src/lib/environments/environment.ts similarity index 100% rename from apps/client-asset-sg/src/environments/environment.ts rename to libs/client-shared/src/lib/environments/environment.ts diff --git a/apps/client-asset-sg/src/app/features/auth/auth.module.ts b/libs/client-shared/src/lib/features/auth/auth.module.ts similarity index 87% rename from apps/client-asset-sg/src/app/features/auth/auth.module.ts rename to libs/client-shared/src/lib/features/auth/auth.module.ts index 4a66ecda..018ddb43 100644 --- a/apps/client-asset-sg/src/app/features/auth/auth.module.ts +++ b/libs/client-shared/src/lib/features/auth/auth.module.ts @@ -5,15 +5,15 @@ import { NgModule } from '@angular/core'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { AnchorComponent, ButtonComponent, icons } from '@asset-sg/client-shared'; import { provideSvgIcons } from '@ngneat/svg-icon'; import { TranslateModule } from '@ngx-translate/core'; import { ForModule } from '@rx-angular/template/for'; import { LetModule } from '@rx-angular/template/let'; import { PushModule } from '@rx-angular/template/push'; import { OAuthModule } from 'angular-oauth2-oidc'; +import { AnchorComponent, ButtonComponent } from '../../components'; -import { HttpInterceptor } from '../../services/http.interceptor'; +import { icons } from '../../icons'; import { ErrorService } from './error.service'; @NgModule({ @@ -44,6 +44,6 @@ import { ErrorService } from './error.service'; }), ], exports: [OAuthModule], - providers: [provideSvgIcons(icons), ErrorService, HttpInterceptor], + providers: [provideSvgIcons(icons), ErrorService], }) export class AuthModule {} diff --git a/apps/client-asset-sg/src/app/features/auth/auth.service.ts b/libs/client-shared/src/lib/features/auth/auth.service.ts similarity index 96% rename from apps/client-asset-sg/src/app/features/auth/auth.service.ts rename to libs/client-shared/src/lib/features/auth/auth.service.ts index 1a29017f..324dfca3 100644 --- a/apps/client-asset-sg/src/app/features/auth/auth.service.ts +++ b/libs/client-shared/src/lib/features/auth/auth.service.ts @@ -2,7 +2,6 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; -import { ApiError, appSharedStateActions, AppState } from '@asset-sg/client-shared'; import { ORD } from '@asset-sg/core'; import { User, UserSchema } from '@asset-sg/shared/v2'; import * as RD from '@devexperts/remote-data-ts'; @@ -10,8 +9,10 @@ import { Store } from '@ngrx/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { plainToInstance } from 'class-transformer'; import { BehaviorSubject, map, Observable, startWith } from 'rxjs'; -import { environment } from '../../../environments/environment'; import { DisclaimerDialogComponent } from '../../components/disclaimer-dialog/disclaimer-dialog.component'; +import { environment } from '../../environments/environment'; +import { appSharedStateActions, AppState } from '../../state'; +import { ApiError } from '../../utils'; @Injectable({ providedIn: 'root' }) export class AuthService { diff --git a/apps/client-asset-sg/src/app/features/auth/error.service.ts b/libs/client-shared/src/lib/features/auth/error.service.ts similarity index 100% rename from apps/client-asset-sg/src/app/features/auth/error.service.ts rename to libs/client-shared/src/lib/features/auth/error.service.ts From 88c6dd2d0221a6e596a971da15721f3467ecbc7b Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Wed, 13 Nov 2024 08:39:52 +0100 Subject: [PATCH 8/9] Remove circular dependencies --- .../client-asset-sg/src}/environments/environment.prod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {libs/client-shared/src/lib => apps/client-asset-sg/src}/environments/environment.prod.ts (63%) diff --git a/libs/client-shared/src/lib/environments/environment.prod.ts b/apps/client-asset-sg/src/environments/environment.prod.ts similarity index 63% rename from libs/client-shared/src/lib/environments/environment.prod.ts rename to apps/client-asset-sg/src/environments/environment.prod.ts index 8b5f7283..bffa65d1 100644 --- a/libs/client-shared/src/lib/environments/environment.prod.ts +++ b/apps/client-asset-sg/src/environments/environment.prod.ts @@ -1,4 +1,4 @@ -import { CompileTimeEnvironment } from './environment-type'; +import { CompileTimeEnvironment } from '@asset-sg/client-shared'; export const environment: CompileTimeEnvironment = { ngrxStoreLoggerEnabled: false, From 51e5ff07203acc43bf507f8ef00ae464f5735e9f Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Wed, 13 Nov 2024 09:34:25 +0100 Subject: [PATCH 9/9] Add config service --- apps/client-asset-sg/src/app/app.component.ts | 4 ++++ apps/client-asset-sg/src/app/app.module.ts | 2 +- .../src}/environments/environment-type.ts | 0 .../src/environments/environment.prod.ts | 2 +- .../src}/environments/environment.ts | 2 +- libs/client-shared/src/index.ts | 2 -- .../disclaimer-dialog.component.ts | 12 ++++++++++-- .../src/lib/features/auth/auth.service.ts | 5 +++-- .../src/lib/services/config.service.ts | 14 ++++++++++++++ libs/client-shared/src/lib/services/index.ts | 1 + 10 files changed, 35 insertions(+), 9 deletions(-) rename {libs/client-shared/src/lib => apps/client-asset-sg/src}/environments/environment-type.ts (100%) rename {libs/client-shared/src/lib => apps/client-asset-sg/src}/environments/environment.ts (81%) create mode 100644 libs/client-shared/src/lib/services/config.service.ts diff --git a/apps/client-asset-sg/src/app/app.component.ts b/apps/client-asset-sg/src/app/app.component.ts index 981899f4..a2679677 100644 --- a/apps/client-asset-sg/src/app/app.component.ts +++ b/apps/client-asset-sg/src/app/app.component.ts @@ -5,6 +5,7 @@ import { appSharedStateActions, AuthService, AuthState, + ConfigService, ErrorService, setCssCustomProperties, } from '@asset-sg/client-shared'; @@ -13,6 +14,7 @@ import { Store } from '@ngrx/store'; import { WINDOW } from 'ngx-window-token'; import { debounceTime, fromEvent, startWith, switchMap } from 'rxjs'; import { assert } from 'tsafe'; +import { environment } from '../environments/environment'; import { AppState } from './state/app-state'; const fullHdWidth = 1920; @@ -31,8 +33,10 @@ export class AppComponent { readonly errorService = inject(ErrorService); readonly authService = inject(AuthService); private readonly store = inject(Store); + private readonly configService = inject(ConfigService); constructor() { + this.configService.setHideDisclaimer(environment.hideDisclaimer); this._httpClient .get>('api/oauth-config/config') .pipe(switchMap(async (config) => await this.authService.initialize(config))) diff --git a/apps/client-asset-sg/src/app/app.module.ts b/apps/client-asset-sg/src/app/app.module.ts index 4b0bedb2..5f274936 100644 --- a/apps/client-asset-sg/src/app/app.module.ts +++ b/apps/client-asset-sg/src/app/app.module.ts @@ -29,7 +29,6 @@ import { LanguageSelectorComponent, TranslateTsLoader, } from '@asset-sg/client-shared'; -import { environment } from '@asset-sg/client-shared'; import { storeLogger } from '@asset-sg/core'; import { provideSvgIcons, SvgIconComponent } from '@ngneat/svg-icon'; import { EffectsModule } from '@ngrx/effects'; @@ -40,6 +39,7 @@ import { ForModule } from '@rx-angular/template/for'; import { LetModule } from '@rx-angular/template/let'; import { PushModule } from '@rx-angular/template/push'; +import { environment } from '../environments/environment'; import { adminGuard } from './app-guards'; import { AppComponent } from './app.component'; import { AppBarComponent, MenuBarComponent, NotFoundComponent, RedirectToLangComponent } from './components'; diff --git a/libs/client-shared/src/lib/environments/environment-type.ts b/apps/client-asset-sg/src/environments/environment-type.ts similarity index 100% rename from libs/client-shared/src/lib/environments/environment-type.ts rename to apps/client-asset-sg/src/environments/environment-type.ts diff --git a/apps/client-asset-sg/src/environments/environment.prod.ts b/apps/client-asset-sg/src/environments/environment.prod.ts index bffa65d1..8b5f7283 100644 --- a/apps/client-asset-sg/src/environments/environment.prod.ts +++ b/apps/client-asset-sg/src/environments/environment.prod.ts @@ -1,4 +1,4 @@ -import { CompileTimeEnvironment } from '@asset-sg/client-shared'; +import { CompileTimeEnvironment } from './environment-type'; export const environment: CompileTimeEnvironment = { ngrxStoreLoggerEnabled: false, diff --git a/libs/client-shared/src/lib/environments/environment.ts b/apps/client-asset-sg/src/environments/environment.ts similarity index 81% rename from libs/client-shared/src/lib/environments/environment.ts rename to apps/client-asset-sg/src/environments/environment.ts index acd3627a..32dd082d 100644 --- a/libs/client-shared/src/lib/environments/environment.ts +++ b/apps/client-asset-sg/src/environments/environment.ts @@ -1,6 +1,6 @@ import { CompileTimeEnvironment } from './environment-type'; export const environment: CompileTimeEnvironment = { - ngrxStoreLoggerEnabled: true, + ngrxStoreLoggerEnabled: false, hideDisclaimer: true, }; diff --git a/libs/client-shared/src/index.ts b/libs/client-shared/src/index.ts index 9d8bf528..6d1136b9 100644 --- a/libs/client-shared/src/index.ts +++ b/libs/client-shared/src/index.ts @@ -26,5 +26,3 @@ export * from './lib/directives/can-create.directive'; export * from './lib/directives/can-delete.directive'; export * from './lib/directives/can-show.directive'; export * from './lib/directives/can-update.directive'; -export * from './lib/environments/environment'; -export * from './lib/environments/environment-type'; diff --git a/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts b/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts index a55029a9..4ddf9d7a 100644 --- a/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts +++ b/libs/client-shared/src/lib/components/disclaimer-dialog/disclaimer-dialog.component.ts @@ -2,7 +2,7 @@ import { AsyncPipe } from '@angular/common'; import { Component, inject } from '@angular/core'; import { MatDialogActions, MatDialogContent, MatDialogRef } from '@angular/material/dialog'; import { MatDivider } from '@angular/material/divider'; -import { TranslateService } from '@ngx-translate/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { map } from 'rxjs'; import { ButtonComponent } from '../../components/button'; import { LanguageSelectorComponent } from '../../components/language-selector'; @@ -15,7 +15,15 @@ const LEGAL_BASE_URL = 'https://www.swissgeol.ch/datenschutz'; selector: 'asset-sg-disclaimer-dialog', templateUrl: './disclaimer-dialog.component.html', styleUrl: './disclaimer-dialog.component.scss', - imports: [LanguageSelectorComponent, MatDivider, MatDialogActions, MatDialogContent, ButtonComponent, AsyncPipe], + imports: [ + LanguageSelectorComponent, + MatDivider, + MatDialogActions, + MatDialogContent, + ButtonComponent, + AsyncPipe, + TranslateModule, + ], }) export class DisclaimerDialogComponent { public text = ''; diff --git a/libs/client-shared/src/lib/features/auth/auth.service.ts b/libs/client-shared/src/lib/features/auth/auth.service.ts index 324dfca3..e1da50f7 100644 --- a/libs/client-shared/src/lib/features/auth/auth.service.ts +++ b/libs/client-shared/src/lib/features/auth/auth.service.ts @@ -10,7 +10,7 @@ import { OAuthService } from 'angular-oauth2-oidc'; import { plainToInstance } from 'class-transformer'; import { BehaviorSubject, map, Observable, startWith } from 'rxjs'; import { DisclaimerDialogComponent } from '../../components/disclaimer-dialog/disclaimer-dialog.component'; -import { environment } from '../../environments/environment'; +import { ConfigService } from '../../services'; import { appSharedStateActions, AppState } from '../../state'; import { ApiError } from '../../utils'; @@ -21,6 +21,7 @@ export class AuthService { private readonly store = inject(Store); private readonly router = inject(Router); private readonly dialogService = inject(MatDialog); + private readonly configService = inject(ConfigService); private readonly _state$ = new BehaviorSubject(AuthState.Ongoing); private readonly _isInitialized$ = new BehaviorSubject(false); @@ -62,7 +63,7 @@ export class AuthService { if (this._state$.value === AuthState.Ongoing) { this._state$.next(AuthState.Success); - if (!environment.hideDisclaimer) { + if (!this.configService.getHideDisclaimer()) { this.dialogService.open(DisclaimerDialogComponent, { width: '500px', disableClose: true, diff --git a/libs/client-shared/src/lib/services/config.service.ts b/libs/client-shared/src/lib/services/config.service.ts new file mode 100644 index 00000000..4901e483 --- /dev/null +++ b/libs/client-shared/src/lib/services/config.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class ConfigService { + private hideDisclaimer = false; + + public setHideDisclaimer(value: boolean): void { + this.hideDisclaimer = value; + } + + public getHideDisclaimer(): boolean { + return this.hideDisclaimer; + } +} diff --git a/libs/client-shared/src/lib/services/index.ts b/libs/client-shared/src/lib/services/index.ts index f23dcc11..a0aa85ec 100644 --- a/libs/client-shared/src/lib/services/index.ts +++ b/libs/client-shared/src/lib/services/index.ts @@ -1,3 +1,4 @@ export * from './app-portal.service'; export * from './dateid-module'; export * from './window.service'; +export * from './config.service';