diff --git a/CHANGELOG.md b/CHANGELOG.md index 8156b5892f..90b0e5b697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### ✨ Features and improvements +- ⚠️ Moved the `addSrouceType` to be a part of the global maplibregl object instead of being per map object ([#3420](https://github.com/maplibre/maplibre-gl-js/pull/3420)) - ⚠️ Removed callback usage from `map.loadImage` in continue to below change ([#3422](https://github.com/maplibre/maplibre-gl-js/pull/3422)) - ⚠️ Changed the `GeoJSONSource`'s `getClusterExpansionZoom`, `getClusterChildren`, `getClusterLeaves` methods to return a `Promise` instead of a callback usage ([#3421](https://github.com/maplibre/maplibre-gl-js/pull/3421)) - ⚠️ Changed the `setRTLTextPlugin` function to return a promise instead of using callback ([#3418](https://github.com/maplibre/maplibre-gl-js/pull/3418)) this also changed how the RTL pluing code is handled internally by splitting the main thread and worker thread code. diff --git a/src/index.ts b/src/index.ts index f075d70e26..facb7c2e73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ import {RasterDEMTileSource} from './source/raster_dem_tile_source'; import {RasterTileSource} from './source/raster_tile_source'; import {VectorTileSource} from './source/vector_tile_source'; import {VideoSource} from './source/video_source'; - +import {addSourceType, type SourceClass} from './source/source'; const version = packageJSON.version; export type * from '@maplibre/maplibre-gl-style-spec'; @@ -230,6 +230,15 @@ class MapLibreGL { static removeProtocol(customProtocol: string) { delete config.REGISTERED_PROTOCOLS[customProtocol]; } + + /** + * Adds a [custom source type](#Custom Sources), making it available for use with + * {@link Map#addSource}. + * @param name - The name of the source type; source definition objects use this name in the `{type: ...}` field. + * @param SourceType - A {@link SourceClass} - which is a constructor for the {@link Source} interface. + * @returns a promise that is resolved when the source type is ready or with an error argument if there is an error. + */ + static addSourceType = (name: string, sourceType: SourceClass) => addSourceType(name, sourceType); } //This gets automatically stripped out in production builds. diff --git a/src/source/source.test.ts b/src/source/source.test.ts new file mode 100644 index 0000000000..4acb7321d4 --- /dev/null +++ b/src/source/source.test.ts @@ -0,0 +1,51 @@ +import {Dispatcher} from '../util/dispatcher'; +import {SourceClass, addSourceType, create} from './source'; + +describe('addSourceType', () => { + test('adds factory function without a worker url does not dispatch to worker', async () => { + const sourceType = jest.fn().mockImplementation(function (id) { this.id = id; }) as SourceClass; + + // expect no call to load worker source + const spy = jest.spyOn(Dispatcher.prototype, 'broadcast'); + + await addSourceType('foo', sourceType); + expect(spy).not.toHaveBeenCalled(); + + create('id', {type: 'foo'} as any, null, null); + expect(sourceType).toHaveBeenCalled(); + }); + + test('create a custom source without an id throws', async () => { + const sourceType = jest.fn() as SourceClass; + + // expect no call to load worker source + const spy = jest.spyOn(Dispatcher.prototype, 'broadcast'); + + await addSourceType('foo2', sourceType); + expect(spy).not.toHaveBeenCalled(); + + expect(() => create('id', {type: 'foo2'} as any, null, null)).toThrow(); + expect(sourceType).toHaveBeenCalled(); + }); + + test('triggers workers to load worker source code', async () => { + const sourceType = function () {} as any as SourceClass; + sourceType.workerSourceURL = 'worker-source.js' as any as URL; + + const spy = jest.spyOn(Dispatcher.prototype, 'broadcast'); + + await addSourceType('bar', sourceType); + expect(spy).toHaveBeenCalledWith('loadWorkerSource', 'worker-source.js'); + }); + + test('refuses to add new type over existing name', async () => { + const sourceType = function () {} as any as SourceClass; + await expect(addSourceType('canvas', sourceType)).rejects.toThrow(); + await expect(addSourceType('geojson', sourceType)).rejects.toThrow(); + await expect(addSourceType('image', sourceType)).rejects.toThrow(); + await expect(addSourceType('raster', sourceType)).rejects.toThrow(); + await expect(addSourceType('raster-dem', sourceType)).rejects.toThrow(); + await expect(addSourceType('vector', sourceType)).rejects.toThrow(); + await expect(addSourceType('video', sourceType)).rejects.toThrow(); + }); +}); diff --git a/src/source/source.ts b/src/source/source.ts index 622dddc5ac..400412b164 100644 --- a/src/source/source.ts +++ b/src/source/source.ts @@ -5,9 +5,10 @@ import {GeoJSONSource} from '../source/geojson_source'; import {VideoSource} from '../source/video_source'; import {ImageSource} from '../source/image_source'; import {CanvasSource} from '../source/canvas_source'; +import {Dispatcher} from '../util/dispatcher'; +import {getGlobalWorkerPool} from '../util/global_worker_pool'; import type {SourceSpecification} from '@maplibre/maplibre-gl-style-spec'; -import type {Dispatcher} from '../util/dispatcher'; import type {Event, Evented} from '../util/evented'; import type {Map} from '../ui/map'; import type {Tile} from './tile'; @@ -159,7 +160,7 @@ export const create = (id: string, specification: SourceSpecification | CanvasSo return source; }; -export const getSourceType = (name: string): SourceClass => { +const getSourceType = (name: string): SourceClass => { switch (name) { case 'geojson': return GeoJSONSource; @@ -179,6 +180,21 @@ export const getSourceType = (name: string): SourceClass => { return registeredSources[name]; }; -export const setSourceType = (name: string, type: SourceClass) => { +const setSourceType = (name: string, type: SourceClass) => { registeredSources[name] = type; }; + +export const addSourceType = async (name: string, SourceType: SourceClass): Promise => { + if (getSourceType(name)) { + throw new Error(`A source type called "${name}" already exists.`); + } + + setSourceType(name, SourceType); + + if (!SourceType.workerSourceURL) { + return; + } + const dispatcher = new Dispatcher(getGlobalWorkerPool(), 'add-custom-source-dispatcher'); + await dispatcher.broadcast('loadWorkerSource', SourceType.workerSourceURL.toString()); + dispatcher.remove(); +}; diff --git a/src/source/source_cache.test.ts b/src/source/source_cache.test.ts index 804dee9e2c..be65bac25e 100644 --- a/src/source/source_cache.test.ts +++ b/src/source/source_cache.test.ts @@ -1,5 +1,5 @@ import {SourceCache} from './source_cache'; -import {Source, setSourceType} from './source'; +import {Source, addSourceType} from './source'; import {Tile} from './tile'; import {OverscaledTileID} from './tile_id'; import {Transform} from '../geo/transform'; @@ -75,7 +75,7 @@ function createSource(id: string, sourceOptions: any, _dispatcher: any, eventedP return source; } -setSourceType('mock-source-type', createSource as any); +addSourceType('mock-source-type', createSource as any); function createSourceCache(options?, used?) { const sc = new SourceCache('id', extend({ diff --git a/src/style/style.test.ts b/src/style/style.test.ts index 4d4a306545..e59273003b 100644 --- a/src/style/style.test.ts +++ b/src/style/style.test.ts @@ -13,7 +13,6 @@ import {fakeServer, type FakeServer} from 'nise'; import {EvaluationParameters} from './evaluation_parameters'; import {LayerSpecification, GeoJSONSourceSpecification, FilterSpecification, SourceSpecification} from '@maplibre/maplibre-gl-style-spec'; -import {SourceClass} from '../source/source'; import {GeoJSONSource} from '../source/geojson_source'; function createStyleJSON(properties?) { @@ -2458,52 +2457,6 @@ describe('Style#query*Features', () => { }); }); -describe('Style#addSourceType', () => { - test('adds factory function', done => { - const style = new Style(getStubMap()); - const sourceType = function () {} as any as SourceClass; - - // expect no call to load worker source - style.dispatcher.broadcast = function (type) { - if (type === 'loadWorkerSource') { - done('test failed'); - } - return Promise.resolve({} as any); - }; - - style.addSourceType('foo', sourceType, (arg1, arg2) => { - expect(arg1).toBeNull(); - expect(arg2).toBeNull(); - done(); - }); - }); - - test('triggers workers to load worker source code', done => { - const style = new Style(getStubMap()); - const sourceType = function () {} as any as SourceClass; - sourceType.workerSourceURL = 'worker-source.js' as any as URL; - - style.dispatcher.broadcast = (type, params) => { - if (type === 'loadWorkerSource') { - expect(params).toBe('worker-source.js'); - done(); - return Promise.resolve({} as any); - } - }; - - style.addSourceType('bar', sourceType, (err) => { expect(err).toBeFalsy(); }); - }); - - test('refuses to add new type over existing name', done => { - const style = new Style(getStubMap()); - const sourceType = function () {} as any as SourceClass; - style.addSourceType('canvas', sourceType, (err) => { - expect(err).toBeTruthy(); - done(); - }); - }); -}); - describe('Style#hasTransitions', () => { test('returns false when the style is loading', () => { const style = new Style(getStubMap()); diff --git a/src/style/style.ts b/src/style/style.ts index 75fe5105be..ffbc37e053 100644 --- a/src/style/style.ts +++ b/src/style/style.ts @@ -13,8 +13,7 @@ import {ResourceType} from '../util/request_manager'; import {browser} from '../util/browser'; import {Dispatcher} from '../util/dispatcher'; import {validateStyle, emitValidationErrors as _emitValidationErrors} from './validate_style'; -import {getSourceType, setSourceType, Source} from '../source/source'; -import type {SourceClass} from '../source/source'; +import {Source} from '../source/source'; import {QueryRenderedFeaturesOptions, QuerySourceFeatureOptions, queryRenderedFeatures, queryRenderedSymbols, querySourceFeatures} from '../source/query_features'; import {SourceCache} from '../source/source_cache'; import {GeoJSONSource} from '../source/geojson_source'; @@ -39,7 +38,6 @@ const emitValidationErrors = (evented: Evented, errors?: ReadonlyArray<{ import type {Map} from '../ui/map'; import type {Transform} from '../geo/transform'; import type {StyleImage} from './style_image'; -import type {Callback} from '../types/callback'; import type {EvaluationParameters} from './evaluation_parameters'; import type {Placement} from '../symbol/placement'; import type {GetResourceResponse, RequestParameters} from '../util/ajax'; @@ -1395,20 +1393,6 @@ export class Style extends Evented { return sourceCache ? querySourceFeatures(sourceCache, params) : []; } - addSourceType(name: string, SourceType: SourceClass, callback: Callback) { - if (getSourceType(name)) { - return callback(new Error(`A source type called "${name}" already exists.`)); - } - - setSourceType(name, SourceType); - - if (!SourceType.workerSourceURL) { - return callback(null, null); - } - - this.dispatcher.broadcast('loadWorkerSource', SourceType.workerSourceURL.toString()).then(() => callback()).catch(callback); - } - getLight() { return this.light.getLight(); } diff --git a/src/ui/map.ts b/src/ui/map.ts index 8b213f9e68..bdaa06209f 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -27,7 +27,7 @@ import {TaskQueue} from '../util/task_queue'; import {throttle} from '../util/throttle'; import {webpSupported} from '../util/webp_supported'; import {PerformanceMarkers, PerformanceUtils} from '../util/performance'; -import {Source, SourceClass} from '../source/source'; +import {Source} from '../source/source'; import {StyleLayer} from '../style/style_layer'; import type {RequestTransformFunction} from '../util/request_manager'; @@ -56,7 +56,6 @@ import type { TerrainSpecification } from '@maplibre/maplibre-gl-style-spec'; -import {Callback} from '../types/callback'; import type {ControlPosition, IControl} from './control/control'; import type {MapGeoJSONFeature} from '../util/vectortile_to_geojson'; import {Terrain} from '../render/terrain'; @@ -2043,18 +2042,6 @@ export class Map extends Camera { return true; } - /** - * Adds a [custom source type](#Custom Sources), making it available for use with - * {@link Map#addSource}. - * @param name - The name of the source type; source definition objects use this name in the `{type: ...}` field. - * @param SourceType - A {@link Source} constructor. - * @param callback - Called when the source type is ready or with an error argument if there is an error. - */ - addSourceType(name: string, SourceType: SourceClass, callback: Callback) { - this._lazyInitEmptyStyle(); - return this.style.addSourceType(name, SourceType, callback); - } - /** * Removes a source from the map's style. *