Skip to content

Commit

Permalink
feat: handle real time events for events
Browse files Browse the repository at this point in the history
Refs: #26
  • Loading branch information
MaSch0212 committed Jun 22, 2024
1 parent 66a273e commit 7be3fcc
Show file tree
Hide file tree
Showing 49 changed files with 490 additions and 294 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createAction, on, props } from '@ngrx/store';
import { produce } from 'immer';

import { PlayerEventTimeslotRegistrationChanged } from '../../../models/realtime-events';
import { Reducers } from '../../utils';
import { EVENTS_ACTION_SCOPE } from '../consts';
import { eventEntityAdapter, EventsFeatureState } from '../events.state';

export const eventTimeslotRegistrationChangedAction = createAction(
`[${EVENTS_ACTION_SCOPE}] Event Timeslot Registration Changed`,
props<PlayerEventTimeslotRegistrationChanged>()
);

export const eventTimeslotRegistrationChangedReducers: Reducers<EventsFeatureState> = [
on(eventTimeslotRegistrationChangedAction, (state, event) =>
eventEntityAdapter.mapOne(
{
id: event.eventId,
map: produce(draft => {
const timeslot = draft.timeslots.find(t => t.id === event.eventTimeslotId);
if (!timeslot) return;
if (event.isRegistered) {
if (!timeslot.playerIds.includes(event.userId)) {
timeslot.playerIds.push(event.userId);
}
} else {
timeslot.playerIds = timeslot.playerIds.filter(p => p !== event.userId);
}
}),
},
state
)
),
];
11 changes: 6 additions & 5 deletions src/client/src/app/+state/events/actions/load-event.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ import { EVENTS_ACTION_SCOPE } from '../consts';
import { selectEventsActionState } from '../events.selectors';
import { EventsFeatureState, eventEntityAdapter } from '../events.state';

export const loadEventAction = createHttpAction<{ eventId: string; reload?: boolean }, Event>()(
EVENTS_ACTION_SCOPE,
'Load Event'
);
export const loadEventAction = createHttpAction<
{ eventId: string; reload?: boolean; silent?: boolean },
Event
>()(EVENTS_ACTION_SCOPE, 'Load Event');

export const loadEventReducers: Reducers<EventsFeatureState> = [
on(loadEventAction.success, (state, { response }) =>
eventEntityAdapter.upsertOne(response, state)
),
handleHttpAction('loadOne', loadEventAction, {
condition: (s, p) => p.silent !== true,
startCondition: (s, p) => !s.entities[p.eventId] || p.reload === true,
}),
];

export const loadEventEffects: Effects = {
loadEvent$: createFunctionalEffect.dispatching((api = inject(EventAdministrationService)) =>
onHttpAction(loadEventAction, selectEventsActionState('loadOne')).pipe(
onHttpAction(loadEventAction, selectEventsActionState('loadOne'), p => !!p.props.silent).pipe(
switchMap(({ props }) => toHttpAction(getEvent(api, props), loadEventAction, props))
)
),
Expand Down
12 changes: 6 additions & 6 deletions src/client/src/app/+state/events/actions/load-events.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { selectEventsActionState, selectEventsContinuationToken } from '../event
import { EventsFeatureState, eventEntityAdapter } from '../events.state';

type _Response = { events: Event[]; continuationToken: string | null };
export const loadEventsAction = createHttpAction<{ reload?: boolean }, _Response>()(
EVENTS_ACTION_SCOPE,
'Load Events'
);
export const loadEventsAction = createHttpAction<
{ reload?: boolean; silent?: boolean },
_Response
>()(EVENTS_ACTION_SCOPE, 'Load Events');

export const loadEventsReducers: Reducers<EventsFeatureState> = [
on(loadEventsAction.success, (state, { props, response }) =>
Expand All @@ -29,13 +29,13 @@ export const loadEventsReducers: Reducers<EventsFeatureState> = [
})
)
),
handleHttpAction('load', loadEventsAction),
handleHttpAction('load', loadEventsAction, { condition: (s, p) => !p.silent }),
];

export const loadEventsEffects: Effects = {
loadEvents$: createFunctionalEffect.dispatching(
(store = inject(Store), api = inject(EventAdministrationService)) =>
onHttpAction(loadEventsAction, selectEventsActionState('load')).pipe(
onHttpAction(loadEventsAction, selectEventsActionState('load'), p => !!p.props.silent).pipe(
withLatestFrom(store.select(selectEventsContinuationToken)),
switchMap(([{ props }, continuationToken]) =>
toHttpAction(getEvents(api, props, continuationToken), loadEventsAction, props)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createAction, on, props } from '@ngrx/store';
import { produce } from 'immer';

import { initialActionState } from '../../action-state';
import { Reducers } from '../../utils';
import { EVENTS_ACTION_SCOPE } from '../consts';
import { EventsFeatureState } from '../events.state';

export const resetEventsActionStateAction = createAction(
`[${EVENTS_ACTION_SCOPE}] Reset Action State`,
props<{ scope: keyof EventsFeatureState['actionStates'] }>()
);

export const resetEventsActionStateReducers: Reducers<EventsFeatureState> = [
on(
resetEventsActionStateAction,
produce((state, { scope }) => {
state.actionStates[scope] = initialActionState;
})
),
];
2 changes: 2 additions & 0 deletions src/client/src/app/+state/events/events.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { addEventPreconfigAction } from './actions/add-event-preconfig.action';
export { addEventTimeslotAction } from './actions/add-event-timeslot.action';
export { addEventAction } from './actions/add-event.action';
export { commitEventAction } from './actions/commit-event.action';
export { eventTimeslotRegistrationChangedAction } from './actions/event-timeslot-registration-changed.action';
export { addPlayerToEventPreconfigurationAction } from './actions/add-player-to-preconfig.action';
export { buildEventInstancesAction } from './actions/build-event-instances.action';
export { loadEventAction } from './actions/load-event.action';
Expand All @@ -10,5 +11,6 @@ export { removeEventAction } from './actions/remove-event.action';
export { removeEventPreconfigAction } from './actions/remove-event-preconfig.action';
export { removeEventTimeslotAction } from './actions/remove-event-timeslot.action';
export { removePlayerFromPreconfigAction } from './actions/remove-player-from-preconfig.action';
export { resetEventsActionStateAction } from './actions/reset-events-action-state.action';
export { startEventAction } from './actions/start-event.action';
export { updateEventTimeslotAction } from './actions/update-event-timeslot.action';
53 changes: 51 additions & 2 deletions src/client/src/app/+state/events/events.effects.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { inject } from '@angular/core';
import { mergeMap, of, EMPTY, map, merge } from 'rxjs';

import { addEventPreconfigEffects } from './actions/add-event-preconfig.action';
import { addEventTimeslotEffects } from './actions/add-event-timeslot.action';
import { addEventEffects } from './actions/add-event.action';
import { addPlayerToEventPreconfigurationEffects } from './actions/add-player-to-preconfig.action';
import { buildEventInstancesEffects } from './actions/build-event-instances.action';
import { commitEventEffects } from './actions/commit-event.action';
import { loadEventEffects } from './actions/load-event.action';
import { loadEventAction, loadEventEffects } from './actions/load-event.action';
import { loadEventsEffects } from './actions/load-events.action';
import { removeEventPreconfigEffects } from './actions/remove-event-preconfig.action';
import { removeEventTimeslotEffects } from './actions/remove-event-timeslot.action';
import { removeEventEffects } from './actions/remove-event.action';
import { removeEventAction, removeEventEffects } from './actions/remove-event.action';
import { removePlayerFromPreconfigEffects } from './actions/remove-player-from-preconfig.action';
import { startEventEffects } from './actions/start-event.action';
import { updateEventTimeslotEffects } from './actions/update-event-timeslot.action';
import {
eventTimeslotRegistrationChangedAction,
resetEventsActionStateAction,
} from './events.actions';
import { RealtimeEventsService } from '../../services/realtime-events.service';
import { createFunctionalEffect } from '../functional-effect';
import { Effects } from '../utils';

export const eventsFeatureEffects: Effects[] = [
Expand All @@ -29,4 +38,44 @@ export const eventsFeatureEffects: Effects[] = [
startEventEffects,
commitEventEffects,
updateEventTimeslotEffects,
{
eventUpdated$: createFunctionalEffect.dispatching((events = inject(RealtimeEventsService)) =>
merge(
events.eventChanged,
events.eventTimeslotChanged,
events.eventPreconfigurationChanged,
events.eventInstancesChanged
).pipe(
mergeMap(event => {
const eventId = event.eventId;
let changeType = 'changeType' in event ? event.changeType : 'updated';
if (changeType === 'deleted' && 'eventTimeslotId' in event) {
changeType = 'updated';
}
if (changeType === 'updated' || changeType === 'created') {
return of(loadEventAction({ eventId, reload: true, silent: true }));
} else if (changeType === 'deleted') {
return of(removeEventAction.success({ eventId }, undefined));
}
return EMPTY;
})
)
),
eventTimeslotRegistrationChanged$: createFunctionalEffect.dispatching(() =>
inject(RealtimeEventsService).playerEventTimeslotRegistrationChanged.pipe(
map(event => eventTimeslotRegistrationChangedAction(event))
)
),

onServerReconnected$: createFunctionalEffect.dispatching(() =>
inject(RealtimeEventsService).onReconnected$.pipe(
mergeMap(() =>
of(
resetEventsActionStateAction({ scope: 'load' }),
resetEventsActionStateAction({ scope: 'loadOne' })
)
)
)
),
},
];
6 changes: 5 additions & 1 deletion src/client/src/app/+state/events/events.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { addEventReducers } from './actions/add-event.action';
import { addPlayerToEventPreconfigurationReducers } from './actions/add-player-to-preconfig.action';
import { buildEventInstancesReducers } from './actions/build-event-instances.action';
import { commitEventReducers } from './actions/commit-event.action';
import { eventTimeslotRegistrationChangedReducers } from './actions/event-timeslot-registration-changed.action';
import { loadEventReducers } from './actions/load-event.action';
import { loadEventsReducers } from './actions/load-events.action';
import { removeEventPreconfigReducers } from './actions/remove-event-preconfig.action';
import { removeEventTimeslotReducers } from './actions/remove-event-timeslot.action';
import { removeEventReducers } from './actions/remove-event.action';
import { removePlayerFromPreconfigReducers } from './actions/remove-player-from-preconfig.action';
import { resetEventsActionStateReducers } from './actions/reset-events-action-state.action';
import { startEventReducers } from './actions/start-event.action';
import { updateEventTimeslotReducers } from './actions/update-event-timeslot.action';
import { EventsFeatureState, initialEventsFeatureState } from './events.state';
Expand All @@ -24,13 +26,15 @@ export const eventsReducer = createReducer<EventsFeatureState>(
...addEventReducers,
...addPlayerToEventPreconfigurationReducers,
...buildEventInstancesReducers,
...commitEventReducers,
...eventTimeslotRegistrationChangedReducers,
...loadEventReducers,
...loadEventsReducers,
...removeEventPreconfigReducers,
...removeEventTimeslotReducers,
...removeEventReducers,
...removePlayerFromPreconfigReducers,
...resetEventsActionStateReducers,
...startEventReducers,
...commitEventReducers,
...updateEventTimeslotReducers
);
39 changes: 39 additions & 0 deletions src/client/src/app/+state/events/events.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DestroyRef, effect, Signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Store } from '@ngrx/store';
import { filter } from 'rxjs';

import { loadEventsAction, loadEventAction } from './events.actions';
import { selectEventsActionState } from './events.selectors';
import { injectEx, OptionalInjector } from '../../utils/angular.utils';

export function keepEventsLoaded(options?: OptionalInjector) {
const store = injectEx(Store, options);
store.dispatch(loadEventsAction({ reload: false }));
store
.select(selectEventsActionState('load'))
.pipe(
filter(x => x.state === 'none'),
takeUntilDestroyed(injectEx(DestroyRef, options))
)
.subscribe(() => store.dispatch(loadEventsAction({ reload: true, silent: true })));
}

export function keepEventLoaded(eventId: Signal<string>, options?: OptionalInjector) {
const store = injectEx(Store, options);

effect(() => store.dispatch(loadEventAction({ eventId: eventId(), reload: false })), {
...options,
allowSignalWrites: true,
});

store
.select(selectEventsActionState('loadOne'))
.pipe(
filter(x => x.state === 'none'),
takeUntilDestroyed(injectEx(DestroyRef, options))
)
.subscribe(() =>
store.dispatch(loadEventAction({ eventId: eventId(), reload: true, silent: true }))
);
}
12 changes: 6 additions & 6 deletions src/client/src/app/+state/maps/actions/load-maps.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { MAPS_ACTION_SCOPE } from '../consts';
import { selectMapsActionState } from '../maps.selectors';
import { mapsEntityAdapter, MapsFeatureState } from '../maps.state';

export const loadMapsAction = createHttpAction<{ reload?: boolean }, MinigolfMap[]>()(
MAPS_ACTION_SCOPE,
'Load Maps'
);
export const loadMapsAction = createHttpAction<
{ reload?: boolean; silent?: boolean },
MinigolfMap[]
>()(MAPS_ACTION_SCOPE, 'Load Maps');

export const loadMapsReducers: Reducers<MapsFeatureState> = [
on(loadMapsAction.success, (state, { props, response }) =>
Expand All @@ -24,12 +24,12 @@ export const loadMapsReducers: Reducers<MapsFeatureState> = [
props.reload ? mapsEntityAdapter.removeAll(state) : state
)
),
handleHttpAction('load', loadMapsAction),
handleHttpAction('load', loadMapsAction, { condition: (s, p) => !p.silent }),
];

export const loadMapsEffects: Effects = {
loadMaps$: createFunctionalEffect.dispatching((api = inject(MapAdministrationService)) =>
onHttpAction(loadMapsAction, selectMapsActionState('load')).pipe(
onHttpAction(loadMapsAction, selectMapsActionState('load'), p => !!p.props.silent).pipe(
switchMap(({ props }) => toHttpAction(getMaps(api, props), loadMapsAction, props))
)
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createAction, on, props } from '@ngrx/store';
import { produce } from 'immer';

import { initialActionState } from '../../action-state';
import { Reducers } from '../../utils';
import { MAPS_ACTION_SCOPE } from '../consts';
import { MapsFeatureState } from '../maps.state';

export const resetMapsActionStateAction = createAction(
`[${MAPS_ACTION_SCOPE}] Reset Action State`,
props<{ scope: keyof MapsFeatureState['actionStates'] }>()
);

export const resetMapsActionStateReducers: Reducers<MapsFeatureState> = [
on(
resetMapsActionStateAction,
produce((state, { scope }) => {
state.actionStates[scope] = initialActionState;
})
),
];
19 changes: 0 additions & 19 deletions src/client/src/app/+state/maps/actions/reset-maps.action.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/client/src/app/+state/maps/maps.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export { addMapAction } from './actions/add-map.action';
export { loadMapAction } from './actions/load-map.action';
export { loadMapsAction } from './actions/load-maps.action';
export { removeMapAction } from './actions/remove-map.action';
export { resetMapsAction } from './actions/reset-maps.action';
export { resetMapsActionStateAction as resetMapActionStateAction } from './actions/reset-maps-action-state.action';
export { updateMapAction } from './actions/update-map.action';
Loading

0 comments on commit 7be3fcc

Please sign in to comment.