diff --git a/src/client/src/app/+state/maps/maps.utils.ts b/src/client/src/app/+state/maps/maps.utils.ts index 3174a80..ee5ddca 100644 --- a/src/client/src/app/+state/maps/maps.utils.ts +++ b/src/client/src/app/+state/maps/maps.utils.ts @@ -1,11 +1,13 @@ -import { effect } from '@angular/core'; +import { effect, Signal } from '@angular/core'; import { Store } from '@ngrx/store'; import { loadMapsAction } from './maps.actions'; import { selectMapsActionState } from './maps.selectors'; import { injectEx, OptionalInjector } from '../../utils/angular.utils'; -export function keepMapsLoaded(options?: OptionalInjector & { reload?: boolean }) { +export function keepMapsLoaded( + options?: OptionalInjector & { reload?: boolean; enabled?: Signal } +) { const store = injectEx(Store, options); const actionState = store.selectSignal(selectMapsActionState('load')); @@ -15,7 +17,7 @@ export function keepMapsLoaded(options?: OptionalInjector & { reload?: boolean } effect( () => { - if (actionState().state === 'none') { + if (options?.enabled?.() !== false && actionState().state === 'none') { store.dispatch(loadMapsAction({ reload: false })); } }, diff --git a/src/client/src/app/+state/player-events/actions/load-player-event.action.ts b/src/client/src/app/+state/player-events/actions/load-player-event.action.ts index 67137d6..86f1f19 100644 --- a/src/client/src/app/+state/player-events/actions/load-player-event.action.ts +++ b/src/client/src/app/+state/player-events/actions/load-player-event.action.ts @@ -1,6 +1,6 @@ import { inject } from '@angular/core'; import { on } from '@ngrx/store'; -import { switchMap } from 'rxjs'; +import { mergeMap } from 'rxjs'; import { EventsService } from '../../../api/services'; import { parsePlayerEvent, PlayerEvent } from '../../../models/parsed-models'; @@ -13,7 +13,7 @@ import { selectPlayerEventsActionState } from '../player-events.selectors'; import { PlayerEventsFeatureState, playerEventEntityAdapter } from '../player-events.state'; export const loadPlayerEventAction = createHttpAction< - { eventId: string; reload?: boolean }, + { eventId: string; reload?: boolean; silent?: boolean }, PlayerEvent >()(PLAYER_EVENTS_ACTION_SCOPE, 'Load Player Event'); @@ -22,14 +22,19 @@ export const loadPlayerEventReducers: Reducers = [ playerEventEntityAdapter.upsertOne(response, state) ), handleHttpAction('loadOne', loadPlayerEventAction, { + condition: (s, p) => p.silent !== true, startCondition: (s, p) => !s.entities[p.eventId] || p.reload === true, }), ]; export const loadPlayerEventEffects: Effects = { loadPlayerEvent$: createFunctionalEffect.dispatching((api = inject(EventsService)) => - onHttpAction(loadPlayerEventAction, selectPlayerEventsActionState('loadOne')).pipe( - switchMap(({ props }) => + onHttpAction( + loadPlayerEventAction, + selectPlayerEventsActionState('loadOne'), + p => !!p.props.silent + ).pipe( + mergeMap(({ props }) => toHttpAction(getPlayerEvent(api, props), loadPlayerEventAction, props) ) ) diff --git a/src/client/src/app/+state/player-events/actions/player-event-removed.action.ts b/src/client/src/app/+state/player-events/actions/player-event-removed.action.ts new file mode 100644 index 0000000..4471893 --- /dev/null +++ b/src/client/src/app/+state/player-events/actions/player-event-removed.action.ts @@ -0,0 +1,16 @@ +import { createAction, on, props } from '@ngrx/store'; + +import { Reducers } from '../../utils'; +import { PLAYER_EVENTS_ACTION_SCOPE } from '../consts'; +import { playerEventEntityAdapter, PlayerEventsFeatureState } from '../player-events.state'; + +export const playerEventRemovedAction = createAction( + `[${PLAYER_EVENTS_ACTION_SCOPE}] Player Event Removed`, + props<{ eventId: string }>() +); + +export const playerEventRemovedReducers: Reducers = [ + on(playerEventRemovedAction, (state, { eventId }) => + playerEventEntityAdapter.removeOne(eventId, state) + ), +]; diff --git a/src/client/src/app/+state/player-events/actions/reset-player-events.action.ts b/src/client/src/app/+state/player-events/actions/reset-player-events.action.ts new file mode 100644 index 0000000..5f3765a --- /dev/null +++ b/src/client/src/app/+state/player-events/actions/reset-player-events.action.ts @@ -0,0 +1,19 @@ +import { createAction, on } from '@ngrx/store'; +import { produce } from 'immer'; + +import { initialActionState } from '../../action-state'; +import { Reducers } from '../../utils'; +import { PLAYER_EVENTS_ACTION_SCOPE } from '../consts'; +import { playerEventEntityAdapter, PlayerEventsFeatureState } from '../player-events.state'; + +export const resetPlayerEventsAction = createAction(`[${PLAYER_EVENTS_ACTION_SCOPE}] Reset`); + +export const resetPlayerEventsReducers: Reducers = [ + on(resetPlayerEventsAction, state => + playerEventEntityAdapter.removeAll( + produce(state, draft => { + draft.actionStates.load = initialActionState; + }) + ) + ), +]; diff --git a/src/client/src/app/+state/player-events/player-events.actions.ts b/src/client/src/app/+state/player-events/player-events.actions.ts index 9f31ff8..6457dff 100644 --- a/src/client/src/app/+state/player-events/player-events.actions.ts +++ b/src/client/src/app/+state/player-events/player-events.actions.ts @@ -1,3 +1,5 @@ export { loadPlayerEventAction } from './actions/load-player-event.action'; export { loadPlayerEventsAction } from './actions/load-player-events.action'; +export { playerEventRemovedAction } from './actions/player-event-removed.action'; +export { resetPlayerEventsAction } from './actions/reset-player-events.action'; export { updateEventRegistrationAction } from './actions/update-event-registration.action'; diff --git a/src/client/src/app/+state/player-events/player-events.effects.ts b/src/client/src/app/+state/player-events/player-events.effects.ts index 9a664ec..f05cb77 100644 --- a/src/client/src/app/+state/player-events/player-events.effects.ts +++ b/src/client/src/app/+state/player-events/player-events.effects.ts @@ -1,10 +1,56 @@ -import { loadPlayerEventEffects } from './actions/load-player-event.action'; +import { inject } from '@angular/core'; +import { toObservable } from '@angular/core/rxjs-interop'; +import { Store } from '@ngrx/store'; +import { EMPTY, filter, map, mergeMap, of, skip, withLatestFrom } from 'rxjs'; + +import { loadPlayerEventAction, loadPlayerEventEffects } from './actions/load-player-event.action'; import { loadPlayerEventsEffects } from './actions/load-player-events.action'; import { updateEventRegistrationEffects } from './actions/update-event-registration.action'; +import { playerEventRemovedAction, resetPlayerEventsAction } from './player-events.actions'; +import { playerEventSelectors } from './player-events.selectors'; +import { RealtimeEventsService } from '../../services/realtime-events.service'; +import { createFunctionalEffect } from '../functional-effect'; import { Effects } from '../utils'; export const PlayerEventsFeatureEffects: Effects[] = [ loadPlayerEventsEffects, loadPlayerEventEffects, updateEventRegistrationEffects, + { + playerEventUpdated$: createFunctionalEffect.dispatching((store = inject(Store)) => + inject(RealtimeEventsService).playerEventChanged.pipe( + withLatestFrom(store.select(playerEventSelectors.selectEntities)), + mergeMap(([{ eventId, changeType }, entities]) => { + if (changeType === 'updated') { + return eventId in entities + ? of(loadPlayerEventAction({ eventId, reload: true, silent: true })) + : EMPTY; + } else if (changeType === 'created') { + return of(loadPlayerEventAction({ eventId, reload: true, silent: true })); + } else if (changeType === 'deleted') { + return of(playerEventRemovedAction({ eventId })); + } + return EMPTY; + }) + ) + ), + playerEventRegistrationUpdated$: createFunctionalEffect.dispatching((store = inject(Store)) => + inject(RealtimeEventsService).playerEventRegistrationChanged.pipe( + withLatestFrom(store.select(playerEventSelectors.selectEntities)), + mergeMap(([{ eventId }, entities]) => + eventId in entities + ? of(loadPlayerEventAction({ eventId, reload: true, silent: true })) + : EMPTY + ) + ) + ), + + onServerReconnect$: createFunctionalEffect.dispatching(() => + toObservable(inject(RealtimeEventsService).isConnected).pipe( + skip(1), + filter(x => x), + map(() => resetPlayerEventsAction()) + ) + ), + }, ]; diff --git a/src/client/src/app/+state/player-events/player-events.reducer.ts b/src/client/src/app/+state/player-events/player-events.reducer.ts index 7d296f0..6ac1698 100644 --- a/src/client/src/app/+state/player-events/player-events.reducer.ts +++ b/src/client/src/app/+state/player-events/player-events.reducer.ts @@ -3,6 +3,8 @@ import { produce } from 'immer'; import { loadPlayerEventReducers } from './actions/load-player-event.action'; import { loadPlayerEventsReducers } from './actions/load-player-events.action'; +import { playerEventRemovedReducers } from './actions/player-event-removed.action'; +import { resetPlayerEventsReducers } from './actions/reset-player-events.action'; import { updateEventRegistrationReducers } from './actions/update-event-registration.action'; import { PlayerEventsFeatureState, @@ -23,6 +25,8 @@ export const playerEventsReducer = createReducer( ...loadPlayerEventsReducers, ...loadPlayerEventReducers, + ...playerEventRemovedReducers, + ...resetPlayerEventsReducers, ...updateEventRegistrationReducers, on(addEventAction.success, (state, { response }) => diff --git a/src/client/src/app/components/events/event-timeslot-dialog/event-timeslot-dialog.component.ts b/src/client/src/app/components/events/event-timeslot-dialog/event-timeslot-dialog.component.ts index 79fd838..e2665c4 100644 --- a/src/client/src/app/components/events/event-timeslot-dialog/event-timeslot-dialog.component.ts +++ b/src/client/src/app/components/events/event-timeslot-dialog/event-timeslot-dialog.component.ts @@ -4,6 +4,7 @@ import { computed, effect, inject, + Injector, input, signal, untracked, @@ -57,6 +58,7 @@ import { hasTouchScreen } from '../../../utils/user-agent.utils'; export class EventTimeslotDialogComponent { private readonly _store = inject(Store); private readonly _formBuilder = inject(FormBuilder); + private readonly _injector = inject(Injector); public readonly event = input.required(); public readonly timeslot = input(null); @@ -125,7 +127,7 @@ export class EventTimeslotDialogComponent { } public open() { - keepMapsLoaded(); + keepMapsLoaded({ injector: this._injector, enabled: this.visible }); this.visible.set(true); }