Skip to content

Commit 059210b

Browse files
committed
feat: Implement TS
1 parent fbb22cc commit 059210b

18 files changed

+476
-282
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,44 @@
1-
import { bind, next } from '@ember/runloop';
2-
import EmberObject, { set } from '@ember/object';
1+
import { next } from '@ember/runloop';
32
import { Promise as RsvpPromise } from 'rsvp';
3+
import mapboxgl, { Map as MapboxMap, MapboxOptions, ErrorEvent } from 'mapbox-gl';
4+
import { tracked } from '@glimmer/tracking';
5+
import { action } from '@ember/object';
46

5-
class MapboxLoaderCancelledError extends Error {}
6-
class MapboxSupportError extends Error {
7+
export type MapboxGL = typeof mapboxgl;
8+
export class MapboxLoaderCancelledError extends Error {}
9+
export class MapboxSupportError extends Error {
710
isMapboxSupportError = true;
811
}
9-
class MapboxError extends Error {
10-
constructor(ev) {
12+
export class MapboxError extends Error {
13+
event: ErrorEvent;
14+
constructor(ev: ErrorEvent) {
1115
super(ev.error?.message ?? 'unknown mapbox error');
12-
1316
this.event = ev;
1417
}
1518
}
1619

17-
export default EmberObject.extend({
18-
map: null,
19-
error: null,
20-
MapboxGl: null,
21-
isLoaded: false,
22-
23-
_accessToken: null,
24-
_mapOptions: null,
25-
_extOnMapLoaded: null,
26-
_isCancelled: false,
27-
_isLoading: false,
28-
29-
load(accessToken, options, onMapLoaded) {
20+
export default class MapboxLoader {
21+
map: MapboxMap | undefined;
22+
MapboxGl: MapboxGL | undefined;
23+
24+
@tracked error:
25+
| MapboxLoaderCancelledError
26+
| MapboxSupportError
27+
| MapboxError
28+
| undefined;
29+
@tracked isLoaded = false;
30+
31+
_accessToken: string | undefined;
32+
_mapOptions: MapboxOptions | undefined;
33+
_extOnMapLoaded: ((map: MapboxMap) => void) | undefined;
34+
_isCancelled = false;
35+
_isLoading = false;
36+
37+
load(
38+
accessToken: string,
39+
options: MapboxOptions,
40+
onMapLoaded?: (map: MapboxMap) => void
41+
) {
3042
if (this.isLoaded || this._isLoading || this._isCancelled) {
3143
return;
3244
}
@@ -37,29 +49,30 @@ export default EmberObject.extend({
3749
this._extOnMapLoaded = onMapLoaded;
3850

3951
import('mapbox-gl')
40-
.then(bind(this, this._onModule))
41-
.then(bind(this, this._onMapLoaded))
42-
.then(bind(this, this._onComplete))
43-
.catch(bind(this, this._onError));
44-
},
52+
.then(this._onModule)
53+
.then(this._onMapLoaded)
54+
.then(this._onComplete)
55+
.catch(this._onError);
56+
}
4557

4658
cancel() {
4759
this._isCancelled = true;
4860

49-
if (this.map !== null) {
61+
if (this.map !== undefined) {
5062
// some map users may be late doing cleanup (seen with mapbox-draw-gl),
5163
// so don't remove the map until the next tick
5264
next(this.map, this.map.remove);
5365
}
54-
},
66+
}
5567

56-
_onModule(MapboxGl) {
68+
@action
69+
_onModule({ default: MapboxModule }: { default: MapboxGL }) {
5770
if (this._isCancelled) {
5871
throw new MapboxLoaderCancelledError();
5972
}
6073

61-
this.MapboxGl = MapboxGl.default;
62-
this.MapboxGl.accessToken = this._accessToken;
74+
this.MapboxGl = MapboxModule;
75+
this.MapboxGl.accessToken = this._accessToken!;
6376

6477
if (!this.MapboxGl.supported()) {
6578
throw new MapboxSupportError(
@@ -76,7 +89,7 @@ export default EmberObject.extend({
7689
map.off('error', listeners.onError);
7790
resolve();
7891
},
79-
onError(ev) {
92+
onError(ev: ErrorEvent) {
8093
map.off('load', listeners.onLoad);
8194
map.off('error', listeners.onError);
8295

@@ -87,42 +100,43 @@ export default EmberObject.extend({
87100
map.on('load', listeners.onLoad);
88101
map.on('error', listeners.onError);
89102
});
90-
},
103+
}
91104

105+
@action
92106
_onMapLoaded() {
93107
if (this._isCancelled) {
94108
throw new MapboxLoaderCancelledError();
95109
}
96110

97-
if (typeof this._extOnMapLoaded === 'function') {
111+
if (typeof this._extOnMapLoaded === 'function' && this.map !== undefined) {
98112
return this._extOnMapLoaded(this.map);
99113
}
100114

101115
return null;
102-
},
116+
}
103117

118+
@action
104119
_onComplete() {
105120
this._isLoading = false;
106121

107122
if (this._isCancelled) {
108123
return;
109124
}
110125

111-
set(this, 'isLoaded', true);
112-
},
126+
this.isLoaded = true;
127+
}
113128

114-
_onError(err) {
129+
_onError(err: MapboxLoaderCancelledError | MapboxSupportError | MapboxError) {
115130
this._isLoading = false;
116131

117132
if (err instanceof MapboxLoaderCancelledError) {
118133
return;
119134
}
120135

121136
if (this._isCancelled) {
122-
console.error(err); // eslint-disable-line no-console
123137
return;
124138
}
125139

126-
set(this, 'error', err);
127-
},
128-
});
140+
this.error = err;
141+
}
142+
}

addon/components/mapbox-gl/index.js addon/components/mapbox-gl/index.ts

+33-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { action } from '@ember/object';
44
import { inject as service } from '@ember/service';
55
import { tracked } from '@glimmer/tracking';
66
import { getOwner } from '@ember/application';
7-
import { pick } from 'lodash-es';
7+
import { Map as MapboxMap } from 'mapbox-gl';
8+
import type MapCacheService from 'ember-mapbox-gl/services/map-cache';
89

910
/**
1011
* Component that creates a new [mapbox-gl-js instance](https://www.mapbox.com/mapbox-gl-js/api/#map):
@@ -43,38 +44,53 @@ import { pick } from 'lodash-es';
4344
* @yield {Component} map.source
4445
*/
4546

46-
export default class MapboxGlComponent extends Component {
47-
@service mapCache;
48-
@tracked _loader;
47+
interface MapboxGlArgs {
48+
initOptions: {};
49+
mapLoaded?: (map: MapboxMap) => void;
50+
mapReloaded?: (map: MapboxMap, metadata: {}) => void;
51+
cacheKey?: string | false;
52+
cacheMetadata?: {};
53+
}
54+
55+
export default class MapboxGlComponent extends Component<MapboxGlArgs> {
56+
@service declare mapCache: MapCacheService;
57+
58+
@tracked _loader: MapboxLoader;
4959

50-
constructor() {
51-
super(...arguments);
52-
this._loader = MapboxLoader.create();
60+
constructor(owner: any, args: MapboxGlArgs) {
61+
super(owner, args);
62+
this._loader = new MapboxLoader();
5363
}
5464

5565
@action
56-
loadMap(element) {
66+
loadMap(element: HTMLElement) {
5767
const cacheKey = this.args.cacheKey;
58-
if (cacheKey && this.mapCache.has(cacheKey)) {
68+
69+
if (cacheKey && this.mapCache.hasMap(cacheKey)) {
5970
let {
60-
map: mapLoader,
71+
mapLoader: mapLoader,
6172
element: mapContainer,
6273
metadata,
63-
} = this.mapCache.get(cacheKey);
74+
} = this.mapCache.getMap(cacheKey)!;
6475
this._loader = mapLoader;
6576

6677
// Append the map html element into component
6778
element.appendChild(mapContainer);
68-
mapLoader.map.resize();
69-
this.args.mapReloaded?.(mapLoader.map, metadata);
79+
80+
if (mapLoader.map) {
81+
mapLoader.map.resize();
82+
this.args.mapReloaded?.(mapLoader.map, metadata);
83+
}
84+
7085
// Save new options after sending mapReloaded event
71-
this.mapCache.set(
86+
this.mapCache.setMap(
7287
cacheKey,
7388
mapLoader,
7489
mapContainer,
7590
this.args.cacheMetadata
7691
);
7792
} else {
93+
//@ts-expect-error
7894
let config = getOwner(this).resolveRegistration('config:environment');
7995
const { accessToken, map } = config['mapbox-gl'];
8096
const options = { ...map, ...this.args.initOptions };
@@ -88,14 +104,14 @@ export default class MapboxGlComponent extends Component {
88104
}
89105

90106
@action
91-
mapLoaded(map) {
107+
mapLoaded(map: MapboxMap) {
92108
// Add map instance and DOM element to cache
93109
const cacheKey = this.args.cacheKey;
94110
if (cacheKey) {
95-
this.mapCache.set(
111+
this.mapCache.setMap(
96112
cacheKey,
97113
this._loader,
98-
map._container,
114+
map.getContainer(),
99115
this.args.cacheMetadata
100116
);
101117
}

addon/components/mapbox-gl/layer/index.js addon/components/mapbox-gl/layer/index.ts

+58-18
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ import { guidFor } from '@ember/object/internals';
44
import { helper } from '@ember/component/helper';
55
import { action } from '@ember/object';
66
import { inject as service } from '@ember/service';
7+
import type LayersCacheService from 'ember-mapbox-gl/services/layers-cache';
8+
import {
9+
Map as MapboxMap,
10+
AnyLayout,
11+
AnyPaint,
12+
CustomLayerInterface,
13+
AnyLayer,
14+
} from 'mapbox-gl';
715

8-
const onUpdateArgsHelper = helper(function ([updateLayer, layer]) {
9-
updateLayer(layer);
10-
});
16+
type SupportedLayers = Exclude<AnyLayer, CustomLayerInterface>;
1117

1218
/**
1319
* Adds a data source to the map.
@@ -47,8 +53,26 @@ const onUpdateArgsHelper = helper(function ([updateLayer, layer]) {
4753
* If this argument is omitted, the layer will be appended to the end of the layers array.
4854
* @argument {string} before
4955
*/
50-
export default class MapboxGlLayerComponent extends Component {
51-
@service layersCache;
56+
57+
const onUpdateArgsHelper = helper(function ([updateLayer, layer]: [
58+
(layer: SupportedLayers) => boolean,
59+
SupportedLayers
60+
]) {
61+
return updateLayer(layer);
62+
});
63+
64+
interface MapboxGlLayerArgs {
65+
map: MapboxMap;
66+
layer: SupportedLayers;
67+
before?: string;
68+
cacheKey?: string | false;
69+
_sourceId?: string;
70+
onDidInsert?: (layer: SupportedLayers) => void;
71+
onDidUpdate?: (layer: SupportedLayers) => void;
72+
}
73+
74+
export default class MapboxGlLayerComponent extends Component<MapboxGlLayerArgs> {
75+
@service declare layersCache: LayersCacheService;
5276

5377
onUpdateArgs = onUpdateArgsHelper;
5478

@@ -78,7 +102,7 @@ export default class MapboxGlLayerComponent extends Component {
78102

79103
get _layer() {
80104
// do this to pick up other properties like filter, re, metadata, source-layer, minzoom, maxzoom, etc
81-
let layer = {
105+
let layer: SupportedLayers = {
82106
...this.args.layer,
83107
id: this._layerId,
84108
type: this._layerType,
@@ -88,16 +112,17 @@ export default class MapboxGlLayerComponent extends Component {
88112
};
89113
// Remove undefined keys
90114
Object.keys(layer).forEach(
91-
(key) => layer[key] === undefined && delete layer[key]
115+
(key: keyof SupportedLayers) =>
116+
layer[key] === undefined && delete layer[key]
92117
);
93118
return layer;
94119
}
95120

96-
constructor() {
97-
super(...arguments);
121+
constructor(owner: any, args: MapboxGlLayerArgs) {
122+
super(owner, args);
98123

99124
const layer = this._layer;
100-
const { map, before, cacheKey } = this.args;
125+
const { map, before, cacheKey } = args;
101126

102127
if (cacheKey && map.getLayer(layer.id)) {
103128
map.setLayoutProperty(layer.id, 'visibility', 'visible');
@@ -106,31 +131,46 @@ export default class MapboxGlLayerComponent extends Component {
106131
}
107132

108133
this.layersCache.push(map, layer.id);
134+
this.args.onDidInsert?.(layer);
109135
}
110136

111137
@action
112-
updateLayer(layer) {
113-
for (const k in layer.layout) {
114-
this.args.map.setLayoutProperty(layer.id, k, layer.layout[k]);
138+
updateLayer(layer: SupportedLayers): boolean {
139+
if (layer.layout) {
140+
Object.keys(layer.layout).forEach((key: keyof AnyLayout) => {
141+
this.args.map.setLayoutProperty(layer.id, key, layer.layout![key]);
142+
});
115143
}
116144

117-
for (const k in layer.paint) {
118-
this.args.map.setPaintProperty(layer.id, k, layer.paint[k]);
145+
if (layer.paint) {
146+
Object.keys(layer.paint).forEach((key: keyof AnyPaint) => {
147+
this.args.map.setPaintProperty(layer.id, key, layer.paint![key]);
148+
});
119149
}
120150

121151
if ('filter' in layer) {
122152
this.args.map.setFilter(layer.id, layer.filter);
123153
}
124154

125-
this.args.map.setLayerZoomRange(layer.id, layer.minzoom, layer.maxzoom);
155+
if (layer.minzoom || layer.maxzoom) {
156+
let mapLayer = this.args.map.getLayer(layer.id) as SupportedLayers;
157+
this.args.map.setLayerZoomRange(
158+
layer.id,
159+
layer.minzoom ?? mapLayer.minzoom ?? 0,
160+
layer.maxzoom ?? mapLayer.maxzoom ?? 24
161+
);
162+
}
163+
164+
this.args.onDidUpdate?.(layer);
165+
return true;
126166
}
127167

128168
willDestroy() {
129-
super.willDestroy(...arguments);
169+
super.willDestroy();
130170

131171
let { map, cacheKey } = this.args;
132172

133-
let layerCounter = this.layersCache.get(map, this._layer.id);
173+
let layerCounter = this.layersCache.getCounter(map, this._layer.id);
134174

135175
// Only if there's one instance of the layer, remove it
136176
if (layerCounter === 1) {

0 commit comments

Comments
 (0)