@cesium/widgets
Version:
A widgets library for use with CesiumJS. CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
315 lines (289 loc) • 9.58 kB
JavaScript
import {
CzmlDataSource,
Frozen,
defined,
DeveloperError,
Event,
GeoJsonDataSource,
getElement,
GpxDataSource,
KmlDataSource,
wrapFunction,
} from "@cesium/engine";
/**
* A mixin which adds default drag and drop support for CZML files to the Viewer widget.
* Rather than being called directly, this function is normally passed as
* a parameter to {@link Viewer#extend}, as shown in the example below.
* @function viewerDragDropMixin
* @param {Viewer} viewer The viewer instance.
* @param {object} [options] Object with the following properties:
* @param {Element|string} [options.dropTarget=viewer.container] The DOM element which will serve as the drop target.
* @param {boolean} [options.clearOnDrop=true] When true, dropping files will clear all existing data sources first, when false, new data sources will be loaded after the existing ones.
* @param {boolean} [options.flyToOnDrop=true] When true, dropping files will fly to the data source once it is loaded.
* @param {boolean} [options.clampToGround=true] When true, datasources are clamped to the ground.
* @param {Proxy} [options.proxy] The proxy to be used for KML network links.
*
* @exception {DeveloperError} Element with id <options.dropTarget> does not exist in the document.
* @exception {DeveloperError} dropTarget is already defined by another mixin.
* @exception {DeveloperError} dropEnabled is already defined by another mixin.
* @exception {DeveloperError} dropError is already defined by another mixin.
* @exception {DeveloperError} clearOnDrop is already defined by another mixin.
*
* @example
* // Add basic drag and drop support and pop up an alert window on error.
* const viewer = new Cesium.Viewer('cesiumContainer');
* viewer.extend(Cesium.viewerDragDropMixin);
* viewer.dropError.addEventListener(function(viewerArg, source, error) {
* window.alert('Error processing ' + source + ':' + error);
* });
*/
function viewerDragDropMixin(viewer, options) {
//>>includeStart('debug', pragmas.debug);
if (!defined(viewer)) {
throw new DeveloperError("viewer is required.");
}
if (viewer.hasOwnProperty("dropTarget")) {
throw new DeveloperError("dropTarget is already defined by another mixin.");
}
if (viewer.hasOwnProperty("dropEnabled")) {
throw new DeveloperError(
"dropEnabled is already defined by another mixin.",
);
}
if (viewer.hasOwnProperty("dropError")) {
throw new DeveloperError("dropError is already defined by another mixin.");
}
if (viewer.hasOwnProperty("clearOnDrop")) {
throw new DeveloperError(
"clearOnDrop is already defined by another mixin.",
);
}
if (viewer.hasOwnProperty("flyToOnDrop")) {
throw new DeveloperError(
"flyToOnDrop is already defined by another mixin.",
);
}
//>>includeEnd('debug');
options = options ?? Frozen.EMPTY_OBJECT;
//Local variables to be closed over by defineProperties.
let dropEnabled = true;
let flyToOnDrop = options.flyToOnDrop ?? true;
const dropError = new Event();
let clearOnDrop = options.clearOnDrop ?? true;
let dropTarget = options.dropTarget ?? viewer.container;
let clampToGround = options.clampToGround ?? true;
let proxy = options.proxy;
dropTarget = getElement(dropTarget);
Object.defineProperties(viewer, {
/**
* Gets or sets the element to serve as the drop target.
* @memberof viewerDragDropMixin.prototype
* @type {Element}
*/
dropTarget: {
//TODO See https://github.com/CesiumGS/cesium/issues/832
get: function () {
return dropTarget;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
if (!defined(value)) {
throw new DeveloperError("value is required.");
}
//>>includeEnd('debug');
unsubscribe(dropTarget, handleDrop);
dropTarget = value;
subscribe(dropTarget, handleDrop);
},
},
/**
* Gets or sets a value indicating if drag and drop support is enabled.
* @memberof viewerDragDropMixin.prototype
* @type {Element}
*/
dropEnabled: {
get: function () {
return dropEnabled;
},
set: function (value) {
if (value !== dropEnabled) {
if (value) {
subscribe(dropTarget, handleDrop);
} else {
unsubscribe(dropTarget, handleDrop);
}
dropEnabled = value;
}
},
},
/**
* Gets the event that will be raised when an error is encountered during drop processing.
* @memberof viewerDragDropMixin.prototype
* @type {Event}
*/
dropError: {
get: function () {
return dropError;
},
},
/**
* Gets or sets a value indicating if existing data sources should be cleared before adding the newly dropped sources.
* @memberof viewerDragDropMixin.prototype
* @type {boolean}
*/
clearOnDrop: {
get: function () {
return clearOnDrop;
},
set: function (value) {
clearOnDrop = value;
},
},
/**
* Gets or sets a value indicating if the camera should fly to the data source after it is loaded.
* @memberof viewerDragDropMixin.prototype
* @type {boolean}
*/
flyToOnDrop: {
get: function () {
return flyToOnDrop;
},
set: function (value) {
flyToOnDrop = value;
},
},
/**
* Gets or sets the proxy to be used for KML.
* @memberof viewerDragDropMixin.prototype
* @type {Proxy}
*/
proxy: {
get: function () {
return proxy;
},
set: function (value) {
proxy = value;
},
},
/**
* Gets or sets a value indicating if the datasources should be clamped to the ground
* @memberof viewerDragDropMixin.prototype
* @type {boolean}
*/
clampToGround: {
get: function () {
return clampToGround;
},
set: function (value) {
clampToGround = value;
},
},
});
function handleDrop(event) {
stop(event);
if (clearOnDrop) {
viewer.entities.removeAll();
viewer.dataSources.removeAll();
}
const files = event.dataTransfer.files;
const length = files.length;
for (let i = 0; i < length; i++) {
const file = files[i];
const reader = new FileReader();
reader.onload = createOnLoadCallback(viewer, file, proxy, clampToGround);
reader.onerror = createDropErrorCallback(viewer, file);
reader.readAsText(file);
}
}
//Enable drop by default;
subscribe(dropTarget, handleDrop);
//Wrap the destroy function to make sure all events are unsubscribed from
viewer.destroy = wrapFunction(viewer, viewer.destroy, function () {
viewer.dropEnabled = false;
});
//Specs need access to handleDrop
viewer._handleDrop = handleDrop;
}
function stop(event) {
event.stopPropagation();
event.preventDefault();
}
function unsubscribe(dropTarget, handleDrop) {
const currentTarget = dropTarget;
if (defined(currentTarget)) {
currentTarget.removeEventListener("drop", handleDrop, false);
currentTarget.removeEventListener("dragenter", stop, false);
currentTarget.removeEventListener("dragover", stop, false);
currentTarget.removeEventListener("dragexit", stop, false);
}
}
function subscribe(dropTarget, handleDrop) {
dropTarget.addEventListener("drop", handleDrop, false);
dropTarget.addEventListener("dragenter", stop, false);
dropTarget.addEventListener("dragover", stop, false);
dropTarget.addEventListener("dragexit", stop, false);
}
function createOnLoadCallback(viewer, file, proxy, clampToGround) {
const scene = viewer.scene;
return function (evt) {
const fileName = file.name;
try {
let loadPromise;
if (/\.czml$/i.test(fileName)) {
loadPromise = CzmlDataSource.load(JSON.parse(evt.target.result), {
sourceUri: fileName,
});
} else if (
/\.geojson$/i.test(fileName) ||
/\.json$/i.test(fileName) ||
/\.topojson$/i.test(fileName)
) {
loadPromise = GeoJsonDataSource.load(JSON.parse(evt.target.result), {
sourceUri: fileName,
clampToGround: clampToGround,
});
} else if (/\.(kml|kmz)$/i.test(fileName)) {
loadPromise = KmlDataSource.load(file, {
sourceUri: fileName,
proxy: proxy,
camera: scene.camera,
canvas: scene.canvas,
clampToGround: clampToGround,
screenOverlayContainer: viewer.container,
});
} else if (/\.gpx$/i.test(fileName)) {
loadPromise = GpxDataSource.load(file, {
sourceUri: fileName,
proxy: proxy,
});
} else {
viewer.dropError.raiseEvent(
viewer,
fileName,
`Unrecognized file: ${fileName}`,
);
return;
}
if (defined(loadPromise)) {
viewer.dataSources
.add(loadPromise)
.then(function (dataSource) {
if (viewer.flyToOnDrop) {
viewer.flyTo(dataSource);
}
})
.catch(function (error) {
viewer.dropError.raiseEvent(viewer, fileName, error);
});
}
} catch (error) {
viewer.dropError.raiseEvent(viewer, fileName, error);
}
};
}
function createDropErrorCallback(viewer, file) {
return function (evt) {
viewer.dropError.raiseEvent(viewer, file.name, evt.target.error);
};
}
export default viewerDragDropMixin;