viewerDragDropMixin.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import defaultValue from "../../Core/defaultValue.js";
  2. import defined from "../../Core/defined.js";
  3. import DeveloperError from "../../Core/DeveloperError.js";
  4. import Event from "../../Core/Event.js";
  5. import wrapFunction from "../../Core/wrapFunction.js";
  6. import CzmlDataSource from "../../DataSources/CzmlDataSource.js";
  7. import GeoJsonDataSource from "../../DataSources/GeoJsonDataSource.js";
  8. import KmlDataSource from "../../DataSources/KmlDataSource.js";
  9. import getElement from "../getElement.js";
  10. /**
  11. * A mixin which adds default drag and drop support for CZML files to the Viewer widget.
  12. * Rather than being called directly, this function is normally passed as
  13. * a parameter to {@link Viewer#extend}, as shown in the example below.
  14. * @function viewerDragDropMixin
  15. * @param {Viewer} viewer The viewer instance.
  16. * @param {Object} [options] Object with the following properties:
  17. * @param {Element|String} [options.dropTarget=viewer.container] The DOM element which will serve as the drop target.
  18. * @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.
  19. * @param {Boolean} [options.flyToOnDrop=true] When true, dropping files will fly to the data source once it is loaded.
  20. * @param {Boolean} [options.clampToGround=true] When true, datasources are clamped to the ground.
  21. * @param {Proxy} [options.proxy] The proxy to be used for KML network links.
  22. *
  23. * @exception {DeveloperError} Element with id <options.dropTarget> does not exist in the document.
  24. * @exception {DeveloperError} dropTarget is already defined by another mixin.
  25. * @exception {DeveloperError} dropEnabled is already defined by another mixin.
  26. * @exception {DeveloperError} dropError is already defined by another mixin.
  27. * @exception {DeveloperError} clearOnDrop is already defined by another mixin.
  28. *
  29. * @example
  30. * // Add basic drag and drop support and pop up an alert window on error.
  31. * var viewer = new Cesium.Viewer('cesiumContainer');
  32. * viewer.extend(Cesium.viewerDragDropMixin);
  33. * viewer.dropError.addEventListener(function(viewerArg, source, error) {
  34. * window.alert('Error processing ' + source + ':' + error);
  35. * });
  36. */
  37. function viewerDragDropMixin(viewer, options) {
  38. //>>includeStart('debug', pragmas.debug);
  39. if (!defined(viewer)) {
  40. throw new DeveloperError("viewer is required.");
  41. }
  42. if (viewer.hasOwnProperty("dropTarget")) {
  43. throw new DeveloperError("dropTarget is already defined by another mixin.");
  44. }
  45. if (viewer.hasOwnProperty("dropEnabled")) {
  46. throw new DeveloperError(
  47. "dropEnabled is already defined by another mixin."
  48. );
  49. }
  50. if (viewer.hasOwnProperty("dropError")) {
  51. throw new DeveloperError("dropError is already defined by another mixin.");
  52. }
  53. if (viewer.hasOwnProperty("clearOnDrop")) {
  54. throw new DeveloperError(
  55. "clearOnDrop is already defined by another mixin."
  56. );
  57. }
  58. if (viewer.hasOwnProperty("flyToOnDrop")) {
  59. throw new DeveloperError(
  60. "flyToOnDrop is already defined by another mixin."
  61. );
  62. }
  63. //>>includeEnd('debug');
  64. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  65. //Local variables to be closed over by defineProperties.
  66. var dropEnabled = true;
  67. var flyToOnDrop = defaultValue(options.flyToOnDrop, true);
  68. var dropError = new Event();
  69. var clearOnDrop = defaultValue(options.clearOnDrop, true);
  70. var dropTarget = defaultValue(options.dropTarget, viewer.container);
  71. var clampToGround = defaultValue(options.clampToGround, true);
  72. var proxy = options.proxy;
  73. dropTarget = getElement(dropTarget);
  74. Object.defineProperties(viewer, {
  75. /**
  76. * Gets or sets the element to serve as the drop target.
  77. * @memberof viewerDragDropMixin.prototype
  78. * @type {Element}
  79. */
  80. dropTarget: {
  81. //TODO See https://github.com/CesiumGS/cesium/issues/832
  82. get: function () {
  83. return dropTarget;
  84. },
  85. set: function (value) {
  86. //>>includeStart('debug', pragmas.debug);
  87. if (!defined(value)) {
  88. throw new DeveloperError("value is required.");
  89. }
  90. //>>includeEnd('debug');
  91. unsubscribe(dropTarget, handleDrop);
  92. dropTarget = value;
  93. subscribe(dropTarget, handleDrop);
  94. },
  95. },
  96. /**
  97. * Gets or sets a value indicating if drag and drop support is enabled.
  98. * @memberof viewerDragDropMixin.prototype
  99. * @type {Element}
  100. */
  101. dropEnabled: {
  102. get: function () {
  103. return dropEnabled;
  104. },
  105. set: function (value) {
  106. if (value !== dropEnabled) {
  107. if (value) {
  108. subscribe(dropTarget, handleDrop);
  109. } else {
  110. unsubscribe(dropTarget, handleDrop);
  111. }
  112. dropEnabled = value;
  113. }
  114. },
  115. },
  116. /**
  117. * Gets the event that will be raised when an error is encountered during drop processing.
  118. * @memberof viewerDragDropMixin.prototype
  119. * @type {Event}
  120. */
  121. dropError: {
  122. get: function () {
  123. return dropError;
  124. },
  125. },
  126. /**
  127. * Gets or sets a value indicating if existing data sources should be cleared before adding the newly dropped sources.
  128. * @memberof viewerDragDropMixin.prototype
  129. * @type {Boolean}
  130. */
  131. clearOnDrop: {
  132. get: function () {
  133. return clearOnDrop;
  134. },
  135. set: function (value) {
  136. clearOnDrop = value;
  137. },
  138. },
  139. /**
  140. * Gets or sets a value indicating if the camera should fly to the data source after it is loaded.
  141. * @memberof viewerDragDropMixin.prototype
  142. * @type {Boolean}
  143. */
  144. flyToOnDrop: {
  145. get: function () {
  146. return flyToOnDrop;
  147. },
  148. set: function (value) {
  149. flyToOnDrop = value;
  150. },
  151. },
  152. /**
  153. * Gets or sets the proxy to be used for KML.
  154. * @memberof viewerDragDropMixin.prototype
  155. * @type {Proxy}
  156. */
  157. proxy: {
  158. get: function () {
  159. return proxy;
  160. },
  161. set: function (value) {
  162. proxy = value;
  163. },
  164. },
  165. /**
  166. * Gets or sets a value indicating if the datasources should be clamped to the ground
  167. * @memberof viewerDragDropMixin.prototype
  168. * @type {Boolean}
  169. */
  170. clampToGround: {
  171. get: function () {
  172. return clampToGround;
  173. },
  174. set: function (value) {
  175. clampToGround = value;
  176. },
  177. },
  178. });
  179. function handleDrop(event) {
  180. stop(event);
  181. if (clearOnDrop) {
  182. viewer.entities.removeAll();
  183. viewer.dataSources.removeAll();
  184. }
  185. var files = event.dataTransfer.files;
  186. var length = files.length;
  187. for (var i = 0; i < length; i++) {
  188. var file = files[i];
  189. var reader = new FileReader();
  190. reader.onload = createOnLoadCallback(viewer, file, proxy, clampToGround);
  191. reader.onerror = createDropErrorCallback(viewer, file);
  192. reader.readAsText(file);
  193. }
  194. }
  195. //Enable drop by default;
  196. subscribe(dropTarget, handleDrop);
  197. //Wrap the destroy function to make sure all events are unsubscribed from
  198. viewer.destroy = wrapFunction(viewer, viewer.destroy, function () {
  199. viewer.dropEnabled = false;
  200. });
  201. //Specs need access to handleDrop
  202. viewer._handleDrop = handleDrop;
  203. }
  204. function stop(event) {
  205. event.stopPropagation();
  206. event.preventDefault();
  207. }
  208. function unsubscribe(dropTarget, handleDrop) {
  209. var currentTarget = dropTarget;
  210. if (defined(currentTarget)) {
  211. currentTarget.removeEventListener("drop", handleDrop, false);
  212. currentTarget.removeEventListener("dragenter", stop, false);
  213. currentTarget.removeEventListener("dragover", stop, false);
  214. currentTarget.removeEventListener("dragexit", stop, false);
  215. }
  216. }
  217. function subscribe(dropTarget, handleDrop) {
  218. dropTarget.addEventListener("drop", handleDrop, false);
  219. dropTarget.addEventListener("dragenter", stop, false);
  220. dropTarget.addEventListener("dragover", stop, false);
  221. dropTarget.addEventListener("dragexit", stop, false);
  222. }
  223. function createOnLoadCallback(viewer, file, proxy, clampToGround) {
  224. var scene = viewer.scene;
  225. return function (evt) {
  226. var fileName = file.name;
  227. try {
  228. var loadPromise;
  229. if (/\.czml$/i.test(fileName)) {
  230. loadPromise = CzmlDataSource.load(JSON.parse(evt.target.result), {
  231. sourceUri: fileName,
  232. });
  233. } else if (
  234. /\.geojson$/i.test(fileName) ||
  235. /\.json$/i.test(fileName) ||
  236. /\.topojson$/i.test(fileName)
  237. ) {
  238. loadPromise = GeoJsonDataSource.load(JSON.parse(evt.target.result), {
  239. sourceUri: fileName,
  240. clampToGround: clampToGround,
  241. });
  242. } else if (/\.(kml|kmz)$/i.test(fileName)) {
  243. loadPromise = KmlDataSource.load(file, {
  244. sourceUri: fileName,
  245. proxy: proxy,
  246. camera: scene.camera,
  247. canvas: scene.canvas,
  248. clampToGround: clampToGround,
  249. });
  250. } else {
  251. viewer.dropError.raiseEvent(
  252. viewer,
  253. fileName,
  254. "Unrecognized file: " + fileName
  255. );
  256. return;
  257. }
  258. if (defined(loadPromise)) {
  259. viewer.dataSources
  260. .add(loadPromise)
  261. .then(function (dataSource) {
  262. if (viewer.flyToOnDrop) {
  263. viewer.flyTo(dataSource);
  264. }
  265. })
  266. .otherwise(function (error) {
  267. viewer.dropError.raiseEvent(viewer, fileName, error);
  268. });
  269. }
  270. } catch (error) {
  271. viewer.dropError.raiseEvent(viewer, fileName, error);
  272. }
  273. };
  274. }
  275. function createDropErrorCallback(viewer, file) {
  276. return function (evt) {
  277. viewer.dropError.raiseEvent(viewer, file.name, evt.target.error);
  278. };
  279. }
  280. export default viewerDragDropMixin;