Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type the event manager and the map.updateLayer event #798

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions packages/chaire-lib-common/src/services/events/EventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,43 @@ import Event from './Event';

const prefix = '';

export type EventType = {
name: string;
arguments: { [key: string]: unknown };
};

export type EventNameKey = 'name';
export type EventArgsKey = 'arguments';

export interface EventManager {
/**
* Emit an event, with typing enabled. This is equivalent to calling
* emit(TEvent.name, TEvent.arguments) but allows compile-time validation of
* the parameters
* @param event The event descriptor
* @param args An object with the arguments of the same type as the
* EventType's
*/
emitEvent<TEvent extends EventType>(
event: TEvent[EventNameKey],
args: {
[Property in keyof TEvent[EventArgsKey]]: TEvent[EventArgsKey][Property];
}
): void;
/**
* Listen on event, with typing enabled. This is equivalent to calling
* on(TEvent.name, TEvent.arguments) but allows compile-time validation of
* the callback signature
* @param event The event descriptor
* @param callback The callback function to call, which receives in
* parameter an argument with the same type as the EventType's
*/
onEvent<TEvent extends EventType>(
event: TEvent[EventNameKey],
callback: (args: {
[Property in keyof TEvent[EventArgsKey]]: TEvent[EventArgsKey][Property];
}) => void
): void;
emit(event: string | Event, ...args: any[]): void;
emitProgress(progressName: string, completeRatio: number): void;
once(event: string | Event, callback: (data: any) => void): void;
Expand All @@ -34,6 +70,15 @@ export class EventManagerImpl implements EventManager {
this._eventManager = wrappedEventManager; // must implement emit, on, once, and removeListener methods
}

emitEvent<TEvent extends EventType>(
event: TEvent[EventNameKey],
args: {
[Property in keyof TEvent[EventArgsKey]]: TEvent[EventArgsKey][Property];
}
) {
this.emit(event, args);
}

emit(event: string | Event, ...args: any[]) {
if (typeof event === 'string') {
event = new Event(event);
Expand All @@ -59,6 +104,15 @@ export class EventManagerImpl implements EventManager {
this._eventManager.on(`${prefix}${event.eventName}`, callback);
}

onEvent<TEvent extends EventType>(
event: TEvent[EventNameKey],
callback: (args: {
[Property in keyof TEvent[EventArgsKey]]: TEvent[EventArgsKey][Property];
}) => void
) {
this.on(event, callback);
}

addListener(event: string | Event, callback: (...data: any) => void) {
this.on(event, callback);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,31 @@ describe('Events', function() {

});

test('should emit an event, with typing', function(done) {
const eventName = 'test.event';
type TestEventType = {
name: 'test.event',
arguments: {
arg1: number,
arg2: string
};
}
const arg1Val = 100;
const arg2Val = 'a string';

const callback = function(args: {
arg1: number,
arg2: string
}) {
expect(args.arg1).toEqual(arg1Val);
expect(args.arg2).toEqual(arg2Val);
eventManager.off(eventName, callback);
done();
};

eventManager.onEvent<TestEventType>(eventName, callback);
eventManager.emitEvent<TestEventType>(eventName, { arg1: arg1Val, arg2: arg2Val });

});

});
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/
import { EventManager } from '../../../services/events/EventManager';
import EventManagerImpl, { EventManager } from '../../../services/events/EventManager';
import serviceLocator from '../../../utils/ServiceLocator';

const eventMngr = new EventManagerImpl();

const mockEmit: jest.MockedFunction<(event: string | Event, ...args: any[]) => void> = jest.fn();
mockEmit.mockImplementation((_event, ...args) => {
if (args.length > 0 && typeof args[args.length - 1] === 'function') {
Expand All @@ -18,12 +20,15 @@ mockEmit.mockImplementation((_event, ...args) => {
});

const mockEmitProgress: jest.MockedFunction<(progressName: string, completeRatio: number) => void> = jest.fn();
const mockEmitEvent: jest.MockedFunction<typeof eventMngr.emitEvent> = jest.fn();

const responses: any[] = [];

const eventManagerMock = {
emit: mockEmit,
emitProgress: mockEmitProgress,
emitEvent: mockEmitEvent,
onEvent: jest.fn(),
once: jest.fn(),
on: jest.fn(),
addListener: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default abstract class GenericMapObjectCollection<
return this._idByIntegerId.get(featureIntegerId);
}

toGeojson() {
toGeojson(): GeoJSON.FeatureCollection<M> {
return {
type: 'FeatureCollection',
features: this.features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,10 @@ class MapboxLayerManager {
this._map?.addLayer(this._layersByName[layerName].layer, this.getNextLayerName(layerName));
}

updateLayer(layerName, geojson) {
updateLayer(
layerName: string,
geojson: GeoJSON.FeatureCollection | ((original: GeoJSON.FeatureCollection) => GeoJSON.FeatureCollection)
) {
const newGeojson =
typeof geojson === 'function'
? geojson(this._layersByName[layerName].source.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import Preferences from 'chaire-lib-common/lib/config/Preferences';
import { MapEventHandlerDescription } from '../IMapEventHandler';
import { getLinesInView, offsetOverlappingLines } from 'chaire-lib-common/lib/services/geodata/ManageOverlappingLines';
import { getNodesInView, manageRelocatingNodes } from 'chaire-lib-common/lib/services/geodata/RelocateNodes';
import { EventManager } from 'chaire-lib-common/lib/services/events/EventManager';
import { MapUpdateLayerEventType } from './MapEventsCallbacks';

// TODO: Make zoomLimit modifiable by user
const zoomLimit = 14; //Zoom levels smaller than this will not apply line separation
Expand Down Expand Up @@ -118,7 +120,10 @@ const applyAestheticChanges = async (boundsGL: MapboxGL.LngLatBounds, zoom: numb
return;
}

serviceLocator.eventManager.emit('map.updateLayer', 'transitPaths', layer);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'transitPaths',
data: layer
});

serviceLocator.eventManager.emit('map.updateLayers', {
transitNodes: transitNodes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2023, Polytechnique Montreal and contributors
*
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/

export type MapUpdateLayerEventType = {
name: 'map.updateLayer';
arguments: {
layerName: string;
data: GeoJSON.FeatureCollection | ((original: GeoJSON.FeatureCollection) => GeoJSON.FeatureCollection);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import AccessibilityMapStatsComponent from './AccessibilityMapStatsComponent';
import TimeOfTripComponent from '../transitRouting/widgets/TimeOfTripComponent';
import TransitRoutingBaseComponent from '../transitRouting/widgets/TransitRoutingBaseComponent';
import AccessibilityMapBatchForm from './AccessibilityMapBatchForm';
import { EventManager } from 'chaire-lib-common/lib/services/events/EventManager';
import { MapUpdateLayerEventType } from 'chaire-lib-frontend/lib/services/map/events/MapEventsCallbacks';

export interface AccessibilityMapFormProps extends WithTranslation {
addEventListeners?: () => void;
Expand Down Expand Up @@ -89,11 +91,10 @@ class AccessibilityMapForm extends ChangeEventsForm<AccessibilityMapFormProps, T
this.onScenarioCollectionUpdate = this.onScenarioCollectionUpdate.bind(this);

if (routingEngine.hasLocation()) {
serviceLocator.eventManager.emit(
'map.updateLayer',
'accessibilityMapPoints',
routingEngine.locationToGeojson()
);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPoints',
data: routingEngine.locationToGeojson()
});
}
}

Expand All @@ -108,7 +109,10 @@ class AccessibilityMapForm extends ChangeEventsForm<AccessibilityMapFormProps, T
routing.setLocation(coordinates);
//}

serviceLocator.eventManager.emit('map.updateLayer', 'accessibilityMapPoints', routing.locationToGeojson());
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPoints',
data: routing.locationToGeojson()
});

this.removePolygons();
}
Expand Down Expand Up @@ -140,12 +144,14 @@ class AccessibilityMapForm extends ChangeEventsForm<AccessibilityMapFormProps, T
}

removePolygons() {
serviceLocator.eventManager.emit('map.updateLayer', 'accessibilityMapPolygons', turfFeatureCollection([]));
serviceLocator.eventManager.emit(
'map.updateLayer',
'accessibilityMapPolygonStrokes',
turfFeatureCollection([])
);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPolygons',
data: turfFeatureCollection([])
});
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPolygonStrokes',
data: turfFeatureCollection([])
});
this.setState({
currentResult: undefined,
loading: false
Expand All @@ -157,7 +163,10 @@ class AccessibilityMapForm extends ChangeEventsForm<AccessibilityMapFormProps, T
if (routing.hasLocation()) {
routing.setLocation(coordinates, false);
// only update layer for better performance:
serviceLocator.eventManager.emit('map.updateLayer', 'accessibilityMapPoints', routing.locationToGeojson());
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPoints',
data: routing.locationToGeojson()
});
this.removePolygons();
//this.calculateRouting();
}
Expand All @@ -167,7 +176,10 @@ class AccessibilityMapForm extends ChangeEventsForm<AccessibilityMapFormProps, T
const routing = this.state.object;
// only both layer and routing engine object:
routing.setLocation(coordinates);
serviceLocator.eventManager.emit('map.updateLayer', 'accessibilityMapPoints', routing.locationToGeojson());
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPoints',
data: routing.locationToGeojson()
});
this.removePolygons();
//this.calculateRouting();
}
Expand All @@ -177,8 +189,14 @@ class AccessibilityMapForm extends ChangeEventsForm<AccessibilityMapFormProps, T

console.log('polygons calculated');

serviceLocator.eventManager.emit('map.updateLayer', 'accessibilityMapPolygons', polygons);
serviceLocator.eventManager.emit('map.updateLayer', 'accessibilityMapPolygonStrokes', strokes);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPolygons',
data: polygons
});
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'accessibilityMapPolygonStrokes',
data: strokes
});

this.setState({
geojsonDownloadUrl: DownloadsUtils.generateJsonDownloadUrl(polygons),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import Button from '../../parts/Button';
import ButtonCell from '../../parts/ButtonCell';
import ButtonList from '../../parts/ButtonList';
import TransitLineButton from '../line/TransitLineButton';
import { EventManager } from 'chaire-lib-common/lib/services/events/EventManager';
import { MapUpdateLayerEventType } from 'chaire-lib-frontend/lib/services/map/events/MapEventsCallbacks';

interface AgencyButtonProps extends WithTranslation {
agency: Agency;
Expand Down Expand Up @@ -62,11 +64,10 @@ const TransitAgencyButton: React.FunctionComponent<AgencyButtonProps> = (props:
// reload paths
await serviceLocator.collectionManager.get('paths').loadFromServer(serviceLocator.socketEventManager);
serviceLocator.collectionManager.refresh('paths');
serviceLocator.eventManager.emit(
'map.updateLayer',
'transitPaths',
serviceLocator.collectionManager.get('paths').toGeojson()
);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'transitPaths',
data: serviceLocator.collectionManager.get('paths').toGeojson()
});
await serviceLocator.collectionManager.get('lines').loadFromServer(serviceLocator.socketEventManager);
serviceLocator.collectionManager.refresh('lines');
}
Expand Down Expand Up @@ -95,11 +96,10 @@ const TransitAgencyButton: React.FunctionComponent<AgencyButtonProps> = (props:
serviceLocator.collectionManager.refresh('paths');
serviceLocator.collectionManager.refresh('lines');
serviceLocator.collectionManager.refresh('agencies');
serviceLocator.eventManager.emit(
'map.updateLayer',
'transitPaths',
serviceLocator.collectionManager.get('paths').toGeojson()
);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'transitPaths',
data: serviceLocator.collectionManager.get('paths').toGeojson()
});
serviceLocator.eventManager.emit('progress', { name: 'SavingAgency', progress: 1.0 });
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { SaveableObjectForm, SaveableObjectState } from 'chaire-lib-frontend/lib
import SelectedObjectButtons from 'chaire-lib-frontend/lib/components/pageParts/SelectedObjectButtons';
import CollectionDownloadButtons from 'chaire-lib-frontend/lib/components/pageParts/CollectionDownloadButtons';
import Agency from 'transition-common/lib/services/agency/Agency';
import { EventManager } from 'chaire-lib-common/lib/services/events/EventManager';
import { MapUpdateLayerEventType } from 'chaire-lib-frontend/lib/services/map/events/MapEventsCallbacks';

const timezoneZoneChoices: { label: string; value: string }[] = [];
for (let i = 0, countI = timezones.length; i < countI; i++) {
Expand Down Expand Up @@ -77,11 +79,10 @@ class TransitAgencyEdit extends SaveableObjectForm<Agency, AgencyFormProps, Agen
try {
await serviceLocator.collectionManager.get('paths').loadFromServer(serviceLocator.socketEventManager);
serviceLocator.collectionManager.refresh('paths');
serviceLocator.eventManager.emit(
'map.updateLayer',
'transitPaths',
serviceLocator.collectionManager.get('paths').toGeojson()
);
(serviceLocator.eventManager as EventManager).emitEvent<MapUpdateLayerEventType>('map.updateLayer', {
layerName: 'transitPaths',
data: serviceLocator.collectionManager.get('paths').toGeojson()
});
serviceLocator.collectionManager
.get('lines')
.loadFromServer(serviceLocator.socketEventManager, serviceLocator.collectionManager);
Expand Down
Loading
Loading