import defaultValue from '../../Core/defaultValue.js'; import defined from '../../Core/defined.js'; import defineProperties from '../../Core/defineProperties.js'; import DeveloperError from '../../Core/DeveloperError.js'; import Event from '../../Core/Event.js'; import wrapFunction from '../../Core/wrapFunction.js'; import CzmlDataSource from '../../DataSources/CzmlDataSource.js'; import GeoJsonDataSource from '../../DataSources/GeoJsonDataSource.js'; import KmlDataSource from '../../DataSources/KmlDataSource.js'; import getElement from '../getElement.js'; /** * 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. * @exports viewerDragDropMixin * @namespace * @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 {DefaultProxy} [options.proxy] The proxy to be used for KML network links. * * @exception {DeveloperError} Element with id 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. * var 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 = defaultValue(options, defaultValue.EMPTY_OBJECT); //Local variables to be closed over by defineProperties. var dropEnabled = true; var flyToOnDrop = defaultValue(options.flyToOnDrop, true); var dropError = new Event(); var clearOnDrop = defaultValue(options.clearOnDrop, true); var dropTarget = defaultValue(options.dropTarget, viewer.container); var clampToGround = defaultValue(options.clampToGround, true); var proxy = options.proxy; dropTarget = getElement(dropTarget); defineProperties(viewer, { /** * Gets or sets the element to serve as the drop target. * @memberof viewerDragDropMixin.prototype * @type {Element} */ dropTarget : { //TODO See https://github.com/AnalyticalGraphicsInc/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 {DefaultProxy} */ 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(); } var files = event.dataTransfer.files; var length = files.length; for (var i = 0; i < length; i++) { var file = files[i]; var 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) { var 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) { var scene = viewer.scene; return function(evt) { var fileName = file.name; try { var 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 }); } 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); } }).otherwise(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;