react-spatial 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/.github/workflows/conventional-pr-title.yml +21 -0
  2. package/.github/workflows/main.yml +28 -0
  3. package/.husky/commit-msg +4 -0
  4. package/.husky/post-checkout +4 -0
  5. package/.husky/post-merge +4 -0
  6. package/.husky/post-rebase +4 -0
  7. package/.husky/pre-commit +4 -0
  8. package/.nvmrc +1 -0
  9. package/.whitesource +8 -0
  10. package/CHANGELOG.md +65 -0
  11. package/DEVELOP.md +113 -0
  12. package/__mocks__/mapbox-gl.js +11 -0
  13. package/__mocks__/resize-observer-polyfill.js +9 -0
  14. package/babel.config.js +3 -0
  15. package/commitlint.config.js +1 -0
  16. package/data/topic1.js +119 -0
  17. package/data/topic2.js +28 -0
  18. package/doc/README.md +21 -0
  19. package/doc/doc-config.json +4 -0
  20. package/package.json +4 -3
  21. package/pull_request_template.md +16 -0
  22. package/renovate.json +4 -0
  23. package/scripts/read-pkg-json.js +17 -0
  24. package/src/components/BaseLayerSwitcher/BaseLayerSwitcher.js +322 -0
  25. package/src/components/BaseLayerSwitcher/BaseLayerSwitcher.test.js +69 -0
  26. package/src/components/BaseLayerSwitcher/README.md +61 -0
  27. package/src/components/BaseLayerSwitcher/__snapshots__/BaseLayerSwitcher.test.js.snap +88 -0
  28. package/src/components/BaseLayerSwitcher/index.js +1 -0
  29. package/src/components/BasicMap/BasicMap.js +413 -0
  30. package/src/components/BasicMap/BasicMap.test.js +281 -0
  31. package/src/components/BasicMap/README.md +18 -0
  32. package/src/components/BasicMap/index.js +1 -0
  33. package/{components → src/components}/CanvasSaveButton/CanvasSaveButton.js +320 -93
  34. package/src/components/CanvasSaveButton/CanvasSaveButton.test.js +148 -0
  35. package/src/components/CanvasSaveButton/README.md +76 -0
  36. package/src/components/CanvasSaveButton/__snapshots__/CanvasSaveButton.test.js.snap +76 -0
  37. package/src/components/CanvasSaveButton/index.js +1 -0
  38. package/src/components/Copyright/Copyright.js +89 -0
  39. package/src/components/Copyright/Copyright.test.js +134 -0
  40. package/src/components/Copyright/README.md +36 -0
  41. package/src/components/Copyright/index.js +1 -0
  42. package/src/components/FeatureExportButton/FeatureExportButton.js +118 -0
  43. package/src/components/FeatureExportButton/FeatureExportButton.test.js +417 -0
  44. package/src/components/FeatureExportButton/README.md +76 -0
  45. package/src/components/FeatureExportButton/__snapshots__/FeatureExportButton.test.js.snap +67 -0
  46. package/src/components/FeatureExportButton/index.js +1 -0
  47. package/src/components/FitExtent/FitExtent.js +62 -0
  48. package/src/components/FitExtent/FitExtent.test.js +48 -0
  49. package/src/components/FitExtent/README.md +34 -0
  50. package/src/components/FitExtent/__snapshots__/FitExtent.test.js.snap +13 -0
  51. package/src/components/FitExtent/index.js +1 -0
  52. package/{components → src/components}/Geolocation/Geolocation.js +144 -61
  53. package/src/components/Geolocation/Geolocation.test.js +267 -0
  54. package/src/components/Geolocation/README.md +25 -0
  55. package/src/components/Geolocation/__snapshots__/Geolocation.test.js.snap +92 -0
  56. package/src/components/Geolocation/index.js +1 -0
  57. package/src/components/LayerTree/LayerTree.js +487 -0
  58. package/src/components/LayerTree/LayerTree.test.js +337 -0
  59. package/src/components/LayerTree/README.md +92 -0
  60. package/src/components/LayerTree/__snapshots__/LayerTree.test.js.snap +1746 -0
  61. package/src/components/LayerTree/index.js +1 -0
  62. package/src/components/MousePosition/MousePosition.js +175 -0
  63. package/src/components/MousePosition/MousePosition.test.js +132 -0
  64. package/src/components/MousePosition/README.md +50 -0
  65. package/src/components/MousePosition/__snapshots__/MousePosition.test.js.snap +76 -0
  66. package/src/components/MousePosition/index.js +1 -0
  67. package/src/components/NorthArrow/NorthArrow.js +75 -0
  68. package/src/components/NorthArrow/NorthArrow.test.js +104 -0
  69. package/src/components/NorthArrow/README.md +59 -0
  70. package/src/components/NorthArrow/__snapshots__/NorthArrow.test.js.snap +117 -0
  71. package/src/components/NorthArrow/index.js +1 -0
  72. package/src/components/Overlay/Overlay.js +176 -0
  73. package/src/components/Overlay/Overlay.test.js +149 -0
  74. package/src/components/Overlay/README.md +59 -0
  75. package/src/components/Overlay/__snapshots__/Overlay.test.js.snap +9 -0
  76. package/src/components/Overlay/index.js +1 -0
  77. package/src/components/Permalink/Permalink.js +326 -0
  78. package/src/components/Permalink/Permalink.test.js +285 -0
  79. package/src/components/Permalink/README.md +105 -0
  80. package/src/components/Permalink/index.js +1 -0
  81. package/{components → src/components}/Popup/Popup.js +165 -55
  82. package/src/components/Popup/Popup.test.js +307 -0
  83. package/src/components/Popup/README.md +93 -0
  84. package/src/components/Popup/__snapshots__/Popup.test.js.snap +180 -0
  85. package/src/components/Popup/index.js +1 -0
  86. package/src/components/README.md +41 -0
  87. package/{components → src/components}/ResizeHandler/ResizeHandler.js +50 -15
  88. package/src/components/ResizeHandler/ResizeHandler.test.js +344 -0
  89. package/src/components/ResizeHandler/index.js +1 -0
  90. package/src/components/RouteSchedule/README.md +118 -0
  91. package/src/components/RouteSchedule/RouteSchedule.js +370 -0
  92. package/src/components/RouteSchedule/RouteSchedule.test.js +113 -0
  93. package/src/components/RouteSchedule/__snapshots__/RouteSchedule.test.js.snap +248 -0
  94. package/src/components/RouteSchedule/index.js +1 -0
  95. package/src/components/ScaleLine/README.md +29 -0
  96. package/src/components/ScaleLine/ScaleLine.js +50 -0
  97. package/src/components/ScaleLine/ScaleLine.test.js +30 -0
  98. package/src/components/ScaleLine/__snapshots__/ScaleLine.test.js.snap +7 -0
  99. package/src/components/ScaleLine/index.js +1 -0
  100. package/src/components/StopsFinder/README.md +50 -0
  101. package/src/components/StopsFinder/StopsFinder.js +284 -0
  102. package/src/components/StopsFinder/StopsFinder.test.js +17 -0
  103. package/src/components/StopsFinder/StopsFinderOption.js +61 -0
  104. package/src/components/StopsFinder/__snapshots__/StopsFinder.test.js.snap +133 -0
  105. package/src/components/StopsFinder/index.js +1 -0
  106. package/src/components/Zoom/README.md +25 -0
  107. package/src/components/Zoom/Zoom.js +180 -0
  108. package/src/components/Zoom/Zoom.test.js +141 -0
  109. package/src/components/Zoom/__snapshots__/Zoom.test.js.snap +201 -0
  110. package/src/components/Zoom/index.js +1 -0
  111. package/{propTypes.js → src/propTypes.js} +16 -12
  112. package/{setupTests.js → src/setupTests.js} +1 -1
  113. package/src/styleguidist/ComponentsList.js +52 -0
  114. package/src/styleguidist/StyleGuide.js +277 -0
  115. package/src/styleguidist/styleguidist.css +38 -0
  116. package/src/utils/GlobalsForOle.js +99 -0
  117. package/src/utils/KML.js +594 -0
  118. package/src/utils/KML.test.js +337 -0
  119. package/src/utils/KMLFormat.js +100 -0
  120. package/src/utils/KMLFormat.test.js +50 -0
  121. package/{utils → src/utils}/Styles.js +20 -14
  122. package/src/utils/__snapshots__/KML.test.js.snap.KML-readFeatures()-and-writeFeatures()-should-read-and-write-lineDash-and-fillPattern-style-for-polygon.canvas-image.png +0 -0
  123. package/src/utils/__snapshots__/getPolygonPattern.test.js.snap.getPolygonPattern()-render-pattern-2-(cross)-color-and-(light-blue)-opacity.canvas-image.png +0 -0
  124. package/src/utils/__snapshots__/getPolygonPattern.test.js.snap.getPolygonPattern()-render-pattern-3-(diagonal-line-from-bottom-left-tot-top-right)-with-color-(light-blue)-and-opacity.canvas-image.png +0 -0
  125. package/src/utils/__snapshots__/getPolygonPattern.test.js.snap.getPolygonPattern()-render-pattern-4-(diagonal-line-from-top-left-to-bottom-right)-with-color-(light-blue)-and-opacity.canvas-image.png +0 -0
  126. package/{utils → src/utils}/getPolygonPattern.js +34 -6
  127. package/src/utils/getPolygonPattern.test.js +61 -0
  128. package/src/utils/timeUtils.js +52 -0
  129. package/src/utils/timeUtils.test.js +30 -0
  130. package/styleguide.config.js +251 -0
  131. package/components/BaseLayerSwitcher/BaseLayerSwitcher.js +0 -231
  132. package/components/BaseLayerSwitcher/BaseLayerSwitcher.js.map +0 -7
  133. package/components/BaseLayerSwitcher/index.js +0 -1
  134. package/components/BaseLayerSwitcher/index.js.map +0 -7
  135. package/components/BasicMap/BasicMap.js +0 -278
  136. package/components/BasicMap/BasicMap.js.map +0 -7
  137. package/components/BasicMap/index.js +0 -1
  138. package/components/BasicMap/index.js.map +0 -7
  139. package/components/CanvasSaveButton/CanvasSaveButton.js.map +0 -7
  140. package/components/CanvasSaveButton/index.js +0 -1
  141. package/components/CanvasSaveButton/index.js.map +0 -7
  142. package/components/Copyright/Copyright.js +0 -55
  143. package/components/Copyright/Copyright.js.map +0 -7
  144. package/components/Copyright/index.js +0 -1
  145. package/components/Copyright/index.js.map +0 -7
  146. package/components/FeatureExportButton/FeatureExportButton.js +0 -62
  147. package/components/FeatureExportButton/FeatureExportButton.js.map +0 -7
  148. package/components/FeatureExportButton/index.js +0 -1
  149. package/components/FeatureExportButton/index.js.map +0 -7
  150. package/components/FitExtent/FitExtent.js +0 -32
  151. package/components/FitExtent/FitExtent.js.map +0 -7
  152. package/components/FitExtent/index.js +0 -1
  153. package/components/FitExtent/index.js.map +0 -7
  154. package/components/Geolocation/Geolocation.js.map +0 -7
  155. package/components/Geolocation/index.js +0 -1
  156. package/components/Geolocation/index.js.map +0 -7
  157. package/components/LayerTree/LayerTree.js +0 -278
  158. package/components/LayerTree/LayerTree.js.map +0 -7
  159. package/components/LayerTree/index.js +0 -1
  160. package/components/LayerTree/index.js.map +0 -7
  161. package/components/MousePosition/MousePosition.js +0 -110
  162. package/components/MousePosition/MousePosition.js.map +0 -7
  163. package/components/MousePosition/index.js +0 -1
  164. package/components/MousePosition/index.js.map +0 -7
  165. package/components/NorthArrow/NorthArrow.js +0 -43
  166. package/components/NorthArrow/NorthArrow.js.map +0 -7
  167. package/components/NorthArrow/index.js +0 -1
  168. package/components/NorthArrow/index.js.map +0 -7
  169. package/components/Overlay/Overlay.js +0 -122
  170. package/components/Overlay/Overlay.js.map +0 -7
  171. package/components/Overlay/index.js +0 -1
  172. package/components/Overlay/index.js.map +0 -7
  173. package/components/Permalink/Permalink.js +0 -206
  174. package/components/Permalink/Permalink.js.map +0 -7
  175. package/components/Permalink/index.js +0 -1
  176. package/components/Permalink/index.js.map +0 -7
  177. package/components/Popup/Popup.js.map +0 -7
  178. package/components/Popup/index.js +0 -1
  179. package/components/Popup/index.js.map +0 -7
  180. package/components/ResizeHandler/ResizeHandler.js.map +0 -7
  181. package/components/ResizeHandler/index.js +0 -1
  182. package/components/ResizeHandler/index.js.map +0 -7
  183. package/components/RouteSchedule/RouteSchedule.js +0 -220
  184. package/components/RouteSchedule/RouteSchedule.js.map +0 -7
  185. package/components/RouteSchedule/index.js +0 -1
  186. package/components/RouteSchedule/index.js.map +0 -7
  187. package/components/ScaleLine/ScaleLine.js +0 -32
  188. package/components/ScaleLine/ScaleLine.js.map +0 -7
  189. package/components/ScaleLine/index.js +0 -1
  190. package/components/ScaleLine/index.js.map +0 -7
  191. package/components/StopsFinder/StopsFinder.js +0 -210
  192. package/components/StopsFinder/StopsFinder.js.map +0 -7
  193. package/components/StopsFinder/StopsFinderOption.js +0 -51
  194. package/components/StopsFinder/StopsFinderOption.js.map +0 -7
  195. package/components/StopsFinder/index.js +0 -1
  196. package/components/StopsFinder/index.js.map +0 -7
  197. package/components/Zoom/Zoom.js +0 -130
  198. package/components/Zoom/Zoom.js.map +0 -7
  199. package/components/Zoom/index.js +0 -1
  200. package/components/Zoom/index.js.map +0 -7
  201. package/propTypes.js.map +0 -7
  202. package/setupTests.js.map +0 -7
  203. package/utils/GlobalsForOle.js +0 -94
  204. package/utils/GlobalsForOle.js.map +0 -7
  205. package/utils/KML.js +0 -412
  206. package/utils/KML.js.map +0 -7
  207. package/utils/KMLFormat.js +0 -69
  208. package/utils/KMLFormat.js.map +0 -7
  209. package/utils/Styles.js.map +0 -7
  210. package/utils/getPolygonPattern.js.map +0 -7
  211. package/utils/timeUtils.js +0 -31
  212. package/utils/timeUtils.js.map +0 -7
  213. /package/{components → src/components}/BaseLayerSwitcher/BaseLayerSwitcher.md.scss +0 -0
  214. /package/{components → src/components}/BaseLayerSwitcher/BaseLayerSwitcher.scss +0 -0
  215. /package/{components → src/components}/BasicMap/BasicMap.md.scss +0 -0
  216. /package/{components → src/components}/CanvasSaveButton/CanvasSaveButton.md.scss +0 -0
  217. /package/{components → src/components}/Copyright/Copyright.md.scss +0 -0
  218. /package/{components → src/components}/FeatureExportButton/FeatureExportButton.md.scss +0 -0
  219. /package/{components → src/components}/FitExtent/FitExtent.md.scss +0 -0
  220. /package/{components → src/components}/Geolocation/Geolocation.md.scss +0 -0
  221. /package/{components → src/components}/Geolocation/Geolocation.scss +0 -0
  222. /package/{components → src/components}/LayerTree/LayerTree.md.scss +0 -0
  223. /package/{components → src/components}/LayerTree/LayerTree.scss +0 -0
  224. /package/{components → src/components}/MousePosition/MousePosition.md.scss +0 -0
  225. /package/{components → src/components}/NorthArrow/NorthArrow.scss +0 -0
  226. /package/{components → src/components}/Overlay/Overlay.md.scss +0 -0
  227. /package/{components → src/components}/Overlay/Overlay.scss +0 -0
  228. /package/{components → src/components}/Permalink/Permalink.md.scss +0 -0
  229. /package/{components → src/components}/Popup/Popup.md.scss +0 -0
  230. /package/{components → src/components}/Popup/Popup.scss +0 -0
  231. /package/{components → src/components}/RouteSchedule/RouteSchedule.md.scss +0 -0
  232. /package/{components → src/components}/RouteSchedule/RouteSchedule.scss +0 -0
  233. /package/{components → src/components}/ScaleLine/ScaleLine.scss +0 -0
  234. /package/{components → src/components}/Zoom/Zoom.md.scss +0 -0
  235. /package/{components → src/components}/Zoom/Zoom.scss +0 -0
  236. /package/{images → src/images}/RouteSchedule/firstStation.png +0 -0
  237. /package/{images → src/images}/RouteSchedule/lastStation.png +0 -0
  238. /package/{images → src/images}/RouteSchedule/line.png +0 -0
  239. /package/{images → src/images}/RouteSchedule/station.png +0 -0
  240. /package/{images → src/images}/baselayer/baselayer.basebright.png +0 -0
  241. /package/{images → src/images}/baselayer/baselayer.osm.png +0 -0
  242. /package/{images → src/images}/baselayer/baselayer.travic.png +0 -0
  243. /package/{images → src/images}/baselayer/open.topo.map.png +0 -0
  244. /package/{images → src/images}/baselayer/osm.baselayer.hot.png +0 -0
  245. /package/{images → src/images}/baselayer/osm.baselayer.png +0 -0
  246. /package/{images → src/images}/favicon.png +0 -0
  247. /package/{images → src/images}/geops_logo.png +0 -0
  248. /package/{images → src/images}/geops_logo.svg +0 -0
  249. /package/{images → src/images}/geops_qr.png +0 -0
  250. /package/{images → src/images}/mots/bus_poi-blue-01.svg +0 -0
  251. /package/{images → src/images}/mots/bus_poi-grey-01.svg +0 -0
  252. /package/{images → src/images}/mots/bus_round-blue-01.svg +0 -0
  253. /package/{images → src/images}/mots/bus_round-grey-01.svg +0 -0
  254. /package/{images → src/images}/mots/bus_square-blue-01.svg +0 -0
  255. /package/{images → src/images}/mots/bus_square-grey-01.svg +0 -0
  256. /package/{images → src/images}/mots/cable_car_poi-blue-01.svg +0 -0
  257. /package/{images → src/images}/mots/cable_car_poi-grey-01.svg +0 -0
  258. /package/{images → src/images}/mots/cable_car_round-blue-01.svg +0 -0
  259. /package/{images → src/images}/mots/cable_car_round-grey-01.svg +0 -0
  260. /package/{images → src/images}/mots/cable_car_square-blue-01.svg +0 -0
  261. /package/{images → src/images}/mots/cable_car_square-grey-01.svg +0 -0
  262. /package/{images → src/images}/mots/ferry_poi-blue-01.svg +0 -0
  263. /package/{images → src/images}/mots/ferry_poi-grey-01.svg +0 -0
  264. /package/{images → src/images}/mots/ferry_round-blue-01.svg +0 -0
  265. /package/{images → src/images}/mots/ferry_round-grey-01.svg +0 -0
  266. /package/{images → src/images}/mots/ferry_square-blue-01.svg +0 -0
  267. /package/{images → src/images}/mots/ferry_square-grey-01.svg +0 -0
  268. /package/{images → src/images}/mots/funicular_round-blue-01.svg +0 -0
  269. /package/{images → src/images}/mots/funicular_round-grey-01.svg +0 -0
  270. /package/{images → src/images}/mots/funicular_square-blue-01.svg +0 -0
  271. /package/{images → src/images}/mots/gondola_round-blue-01.svg +0 -0
  272. /package/{images → src/images}/mots/rail_poi-blue-01.svg +0 -0
  273. /package/{images → src/images}/mots/rail_poi-grey-01.svg +0 -0
  274. /package/{images → src/images}/mots/rail_round-blue-01.svg +0 -0
  275. /package/{images → src/images}/mots/rail_round-grey-01.svg +0 -0
  276. /package/{images → src/images}/mots/rail_square-blue-01.svg +0 -0
  277. /package/{images → src/images}/mots/rail_square-grey-01.svg +0 -0
  278. /package/{images → src/images}/mots/subway_round blue-01.svg +0 -0
  279. /package/{images → src/images}/mots/subway_round-blue-01.svg +0 -0
  280. /package/{images → src/images}/mots/tram_poi-blue-01.svg +0 -0
  281. /package/{images → src/images}/mots/tram_poi-grey-01.svg +0 -0
  282. /package/{images → src/images}/mots/tram_round-blue-01.svg +0 -0
  283. /package/{images → src/images}/mots/tram_round-grey-01.svg +0 -0
  284. /package/{images → src/images}/mots/tram_square-blue-01.svg +0 -0
  285. /package/{images → src/images}/mots/tram_square-grey-01.svg +0 -0
  286. /package/{images → src/images}/northArrow.svg +0 -0
  287. /package/{images → src/images}/northArrow.url.svg +0 -0
  288. /package/{images → src/images}/northArrowCircle.svg +0 -0
  289. /package/{images → src/images}/northArrowCircle.url.svg +0 -0
  290. /package/{themes → src/themes}/README.md +0 -0
  291. /package/{themes → src/themes}/default/components.scss +0 -0
  292. /package/{themes → src/themes}/default/examples.scss +0 -0
  293. /package/{themes → src/themes}/default/index.scss +0 -0
  294. /package/{themes → src/themes}/default/mixins.scss +0 -0
  295. /package/{themes → src/themes}/default/variables.scss +0 -0
@@ -1,26 +1,114 @@
1
- import React, { PureComponent } from "react";
2
- import PropTypes from "prop-types";
3
- import OLMap from "ol/Map";
4
- import { getTopLeft, getBottomRight } from "ol/extent";
5
- import NorthArrowSimple from "../../images/northArrow.url.svg";
6
- import NorthArrowCircle from "../../images/northArrowCircle.url.svg";
1
+ /* eslint-disable no-param-reassign */
2
+ import React, { PureComponent } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import OLMap from 'ol/Map';
5
+ import { getTopLeft, getBottomRight } from 'ol/extent';
6
+ import NorthArrowSimple from '../../images/northArrow.url.svg';
7
+ import NorthArrowCircle from '../../images/northArrowCircle.url.svg';
8
+
7
9
  const extraDataImgPropType = PropTypes.shape({
8
10
  src: PropTypes.string,
9
11
  width: PropTypes.number,
10
12
  height: PropTypes.number,
11
13
  rotation: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
12
- circled: PropTypes.bool
14
+ circled: PropTypes.bool,
13
15
  });
16
+
14
17
  const propTypes = {
18
+ /**
19
+ * Automatically download the image saved.
20
+ */
15
21
  autoDownload: PropTypes.bool,
22
+
23
+ /**
24
+ * Children content of the button.
25
+ */
16
26
  children: PropTypes.node,
17
- format: PropTypes.oneOf(["image/jpeg", "image/png"]),
27
+
28
+ /**
29
+ * Output format of the image.
30
+ */
31
+ format: PropTypes.oneOf(['image/jpeg', 'image/png']),
32
+
33
+ /** An [ol/map](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html). */
18
34
  map: PropTypes.instanceOf(OLMap),
35
+
36
+ /**
37
+ * Extent for the export. If no extent is given, the whole map is exported.
38
+ */
19
39
  extent: PropTypes.arrayOf(PropTypes.number),
40
+
41
+ /**
42
+ * Array of 4 [ol/Coordinate](https://openlayers.org/en/latest/apidoc/module-ol_coordinate.html#~Coordinate).
43
+ * If no coordinates and no extent are given, the whole map is exported.
44
+ * This property must be used to export rotated map.
45
+ * If you don't need to export rotated map the extent property can be used as well.
46
+ * If extent is specified, coordinates property is ignored.
47
+ */
20
48
  coordinates: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
49
+
50
+ /**
51
+ * Scale the map for better quality. Possible values: 1, 2 or 3.
52
+ * WARNING: The tiled layer with a WMTS or XYZ source must provides an url
53
+ * for each scale in the config file.
54
+ */
21
55
  scale: PropTypes.number,
56
+
57
+ /**
58
+ * Function called before the dowload process begins.
59
+ */
22
60
  onSaveStart: PropTypes.func,
61
+
62
+ /**
63
+ * Function called after the dowload process ends.
64
+ *
65
+ * @param {object} error Error message the process fails.
66
+ */
23
67
  onSaveEnd: PropTypes.func,
68
+
69
+ /**
70
+ * Extra data, such as copyright, north arrow configuration.
71
+ * All extra data is optional.
72
+ *
73
+ * Example 1:
74
+ *
75
+ {
76
+ copyright: {
77
+ text: 'Example copyright', // Copyright text or function
78
+ font: '10px Arial', // Font, default is '12px Arial'
79
+ fillStyle: 'blue', // Fill style, default is 'black'
80
+ },
81
+ northArrow, // True if the north arrow
82
+ // should be placed with default configuration
83
+ // (default image, rotation=0, circled=false)
84
+ }
85
+ * Example 2:
86
+ *
87
+ {
88
+ northArrow: {
89
+ src: NorthArrowCustom,
90
+ width: 60, // Width in px, default is 80
91
+ height: 100, // Height in px, default is 80
92
+ rotation: 25, // Absolute rotation in degrees as number or function
93
+
94
+ }
95
+ }
96
+ * Example 3:
97
+ *
98
+ {
99
+ copyright: {
100
+ text: () => { // Copyright as function
101
+ return this.copyright;
102
+ },
103
+ },
104
+ northArrow: {
105
+ rotation: () => { // Rotation as function
106
+ return NorthArrow.radToDeg(this.map.getView().getRotation());
107
+ },
108
+ circled, // Display circle around the north arrow (Does not work for custom src)
109
+ },
110
+ }
111
+ */
24
112
  extraData: PropTypes.shape({
25
113
  logo: extraDataImgPropType,
26
114
  northArrow: extraDataImgPropType,
@@ -30,17 +118,18 @@ const propTypes = {
30
118
  font: PropTypes.string,
31
119
  fillStyle: PropTypes.oneOfType([
32
120
  PropTypes.string,
33
- PropTypes.instanceOf(CanvasPattern)
121
+ PropTypes.instanceOf(CanvasPattern),
34
122
  ]),
35
- background: PropTypes.bool
36
- })
37
- })
123
+ background: PropTypes.bool,
124
+ }),
125
+ }),
38
126
  };
127
+
39
128
  const defaultProps = {
40
129
  autoDownload: true,
41
130
  children: null,
42
131
  map: null,
43
- format: "image/png",
132
+ format: 'image/png',
44
133
  extent: null,
45
134
  extraData: null,
46
135
  coordinates: null,
@@ -48,113 +137,169 @@ const defaultProps = {
48
137
  onSaveStart: (map) => {
49
138
  return Promise.resolve(map);
50
139
  },
51
- onSaveEnd: () => {
52
- }
140
+ onSaveEnd: () => {},
53
141
  };
142
+
143
+ /**
144
+ * The CanvasSaveButton component creates a button to save
145
+ * an [ol/map](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html)
146
+ * canvas as an image.
147
+ */
54
148
  class CanvasSaveButton extends PureComponent {
55
149
  constructor(props) {
56
150
  super(props);
57
151
  this.padding = 5;
58
152
  }
153
+
59
154
  static getMargin(destCanvas) {
60
- const newMargin = destCanvas.width / 100;
155
+ const newMargin = destCanvas.width / 100; // 1% of the canvas width
61
156
  return newMargin;
62
157
  }
158
+
63
159
  onClick(evt) {
64
160
  const { map, onSaveStart, onSaveEnd, autoDownload } = this.props;
65
161
  if (window.navigator.msSaveBlob) {
162
+ // ie only
66
163
  evt.preventDefault();
67
164
  evt.stopPropagation();
68
165
  }
69
166
  onSaveStart(map).then((mapToExport) => {
70
- return this.createCanvasImage(mapToExport || map).then((canvas) => {
71
- if (autoDownload) {
72
- this.downloadCanvasImage(canvas).then((blob) => {
73
- onSaveEnd(mapToExport, canvas, blob);
74
- });
75
- } else {
76
- onSaveEnd(mapToExport, canvas);
77
- }
78
- }).catch((err) => {
79
- if (err) {
80
- console.error(err);
81
- }
82
- onSaveEnd(mapToExport, err);
83
- });
167
+ return this.createCanvasImage(mapToExport || map)
168
+ .then((canvas) => {
169
+ if (autoDownload) {
170
+ this.downloadCanvasImage(canvas).then((blob) => {
171
+ onSaveEnd(mapToExport, canvas, blob);
172
+ });
173
+ } else {
174
+ onSaveEnd(mapToExport, canvas);
175
+ }
176
+ })
177
+ .catch((err) => {
178
+ if (err) {
179
+ // eslint-disable-next-line no-console
180
+ console.error(err);
181
+ }
182
+ onSaveEnd(mapToExport, err);
183
+ });
84
184
  });
85
185
  }
186
+
86
187
  getDownloadImageName() {
87
188
  const { format } = this.props;
88
- const fileExt = format === "image/jpeg" ? "jpg" : "png";
89
- return `${window.document.title.replace(/ /g, "_").toLowerCase()}.${fileExt}`;
189
+ const fileExt = format === 'image/jpeg' ? 'jpg' : 'png';
190
+ return (
191
+ `${window.document.title.replace(/ /g, '_').toLowerCase()}` +
192
+ `.${fileExt}`
193
+ );
90
194
  }
195
+
196
+ // Ensure the font size fita with the image width.
91
197
  decreaseFontSize(destContext, maxWidth, copyright, scale) {
92
198
  const minFontSize = 8;
93
199
  let sizeMatch;
94
200
  let fontSize;
95
201
  do {
96
202
  sizeMatch = destContext.font.match(/[0-9]+(?:\.[0-9]+)?(px)/i);
97
- fontSize = parseInt(sizeMatch[0].replace(sizeMatch[1], ""), 10);
203
+ fontSize = parseInt(sizeMatch[0].replace(sizeMatch[1], ''), 10);
204
+
205
+ // eslint-disable-next-line no-param-reassign
98
206
  destContext.font = destContext.font.replace(fontSize, fontSize - 1);
207
+
99
208
  if (fontSize - 1 === minFontSize) {
100
209
  this.multilineCopyright = true;
101
210
  }
102
- } while (fontSize - 1 > minFontSize && destContext.measureText(copyright).width * scale > maxWidth);
211
+ } while (
212
+ fontSize - 1 > minFontSize &&
213
+ destContext.measureText(copyright).width * scale > maxWidth
214
+ );
215
+
103
216
  return destContext.font;
104
217
  }
105
- drawTextBackground(destContext, textMeasure, textX, textY, padding, styleOptions = {}) {
218
+
219
+ // eslint-disable-next-line class-methods-use-this
220
+ drawTextBackground(
221
+ destContext,
222
+ textMeasure,
223
+ textX,
224
+ textY,
225
+ padding,
226
+ styleOptions = {},
227
+ ) {
228
+ /// get width of text
106
229
  const { width, height, actualBoundingBoxAscent } = textMeasure;
107
230
  destContext.save();
108
- destContext.fillStyle = "rgba(255,255,255,.8)";
109
- if (typeof styleOptions === "object") {
231
+ // Dflt is a white background
232
+ destContext.fillStyle = 'rgba(255,255,255,.8)';
233
+
234
+ // To simplify usability the user could pass a boolean to use only default values.
235
+ if (typeof styleOptions === 'object') {
110
236
  Object.entries(styleOptions).forEach(([key, value]) => {
111
237
  destContext[key] = value;
112
238
  });
113
239
  }
240
+
241
+ /// draw background rect assuming height of font
114
242
  destContext.fillRect(
115
243
  textX - padding,
116
244
  textY - actualBoundingBoxAscent - padding,
117
245
  width + padding * 2,
118
- height + padding * 2
246
+ height + padding * 2,
119
247
  );
120
248
  destContext.restore();
121
249
  }
250
+
122
251
  drawCopyright(destContext, destCanvas, maxWidth) {
123
252
  const { extraData, scale } = this.props;
124
253
  const { text, font, fillStyle, background } = extraData.copyright;
125
- let copyright = typeof text === "function" ? text() : text;
254
+ let copyright = typeof text === 'function' ? text() : text;
255
+
126
256
  if (Array.isArray(copyright)) {
127
257
  copyright = copyright.join();
128
258
  }
259
+
129
260
  destContext.save();
130
261
  destContext.scale(scale, scale);
131
- destContext.font = font || "12px Arial";
262
+ destContext.font = font || '12px Arial';
132
263
  destContext.font = this.decreaseFontSize(
133
264
  destContext,
134
265
  maxWidth - this.padding,
135
266
  copyright,
136
- scale
267
+ scale,
137
268
  );
269
+
138
270
  destContext.scale(scale, scale);
139
- destContext.fillStyle = fillStyle || "black";
271
+ destContext.fillStyle = fillStyle || 'black';
272
+
273
+ // We search if the display on 2 line is necessary
140
274
  let firstLine = copyright;
141
- const wordNumber = copyright.split(" ").length;
275
+ const wordNumber = copyright.split(' ').length;
276
+
277
+ // If the text is bigger than the max width we split it into 2 lines
142
278
  if (this.multilineCopyright) {
143
279
  for (let i = 0; i < wordNumber; i += 1) {
144
- firstLine = firstLine.substring(0, firstLine.lastIndexOf(" "));
145
- if (destContext.measureText(firstLine).width * scale < maxWidth - this.padding) {
280
+ firstLine = firstLine.substring(0, firstLine.lastIndexOf(' '));
281
+ // Stop removing word when fits within one line.
282
+ if (
283
+ destContext.measureText(firstLine).width * scale <
284
+ maxWidth - this.padding
285
+ ) {
146
286
  break;
147
287
  }
148
288
  }
149
289
  }
150
- const secondLine = copyright.replace(firstLine, "");
290
+ const secondLine = copyright.replace(firstLine, '');
291
+
292
+ // Draw first line (line break isn't supported for fillText).
151
293
  const textX = this.margin;
152
294
  let textMeasure = destContext.measureText(firstLine);
153
- textMeasure.height = textMeasure.actualBoundingBoxAscent + textMeasure.actualBoundingBoxDescent;
295
+ textMeasure.height =
296
+ textMeasure.actualBoundingBoxAscent +
297
+ textMeasure.actualBoundingBoxDescent;
154
298
  let firstLineY = destCanvas.height / scale - this.padding;
155
299
  const secondLineY = firstLineY;
156
300
  const paddingBetweenLines = 3;
157
301
  const paddingBackground = paddingBetweenLines / 2;
302
+
158
303
  if (secondLine) {
159
304
  firstLineY -= textMeasure.height + paddingBetweenLines;
160
305
  }
@@ -165,13 +310,17 @@ class CanvasSaveButton extends PureComponent {
165
310
  textX,
166
311
  firstLineY,
167
312
  paddingBackground,
168
- background
313
+ background,
169
314
  );
170
315
  }
171
316
  destContext.fillText(firstLine, textX, firstLineY);
317
+
318
+ // Draw second line.
172
319
  if (secondLine) {
173
320
  textMeasure = destContext.measureText(secondLine);
174
- textMeasure.height = textMeasure.actualBoundingBoxAscent + textMeasure.actualBoundingBoxDescent;
321
+ textMeasure.height =
322
+ textMeasure.actualBoundingBoxAscent +
323
+ textMeasure.actualBoundingBoxDescent;
175
324
  if (background) {
176
325
  this.drawTextBackground(
177
326
  destContext,
@@ -179,109 +328,155 @@ class CanvasSaveButton extends PureComponent {
179
328
  textX,
180
329
  secondLineY,
181
330
  paddingBackground,
182
- background
331
+ background,
183
332
  );
184
333
  }
185
334
  destContext.fillText(secondLine, textX, secondLineY);
186
335
  }
336
+
187
337
  const firstLineMetrics = destContext.measureText(firstLine);
188
338
  const secondLineMetrics = destContext.measureText(secondLine);
189
- const heightFirstLine = firstLineMetrics.actualBoundingBoxAscent + firstLineMetrics.actualBoundingBoxDescent;
190
- const heightSecondLine = secondLineMetrics.actualBoundingBoxAscent + secondLineMetrics.actualBoundingBoxDescent;
191
- this.copyrightY = destCanvas.height - (heightFirstLine + paddingBetweenLines + heightSecondLine) / 2;
339
+ const heightFirstLine =
340
+ firstLineMetrics.actualBoundingBoxAscent +
341
+ firstLineMetrics.actualBoundingBoxDescent;
342
+ const heightSecondLine =
343
+ secondLineMetrics.actualBoundingBoxAscent +
344
+ secondLineMetrics.actualBoundingBoxDescent;
345
+ this.copyrightY =
346
+ destCanvas.height -
347
+ (heightFirstLine + paddingBetweenLines + heightSecondLine) / 2;
192
348
  destContext.restore();
193
349
  }
194
- drawElement(data, destCanvas, previousItemSize = [0, 0], side = "right") {
195
- const destContext = destCanvas.getContext("2d");
350
+
351
+ drawElement(data, destCanvas, previousItemSize = [0, 0], side = 'right') {
352
+ const destContext = destCanvas.getContext('2d');
196
353
  const { scale } = this.props;
197
354
  const { src, width, height, rotation } = data;
355
+
198
356
  return new Promise((resolve) => {
199
357
  const img = new Image();
200
- img.crossOrigin = "Anonymous";
358
+ img.crossOrigin = 'Anonymous';
201
359
  img.src = src;
202
360
  img.onload = () => {
203
361
  destContext.save();
204
362
  const elementWidth = (width || 80) * scale;
205
363
  const elementHeight = (height || 80) * scale;
206
- const left = side === "left" ? this.margin + elementWidth / 2 : destCanvas.width - this.margin - elementWidth / 2;
207
- const top = (side === "left" && this.copyrightY ? this.copyrightY - 2 * this.padding : destCanvas.height) - this.margin - elementHeight / 2 - previousItemSize[1];
364
+ const left =
365
+ side === 'left'
366
+ ? this.margin + elementWidth / 2
367
+ : destCanvas.width - this.margin - elementWidth / 2;
368
+ const top =
369
+ (side === 'left' && this.copyrightY
370
+ ? this.copyrightY - 2 * this.padding
371
+ : destCanvas.height) -
372
+ this.margin -
373
+ elementHeight / 2 -
374
+ previousItemSize[1];
375
+
208
376
  destContext.translate(left, top);
377
+
209
378
  if (rotation) {
210
- const angle = typeof rotation === "function" ? rotation() : rotation;
379
+ const angle = typeof rotation === 'function' ? rotation() : rotation;
211
380
  destContext.rotate(angle * (Math.PI / 180));
212
381
  }
382
+
213
383
  destContext.drawImage(
214
384
  img,
215
385
  -elementWidth / 2,
216
386
  -elementHeight / 2,
217
387
  elementWidth,
218
- elementHeight
388
+ elementHeight,
219
389
  );
220
390
  destContext.restore();
391
+
392
+ // Return the pixels width of the arrow and the margin right,
393
+ // that must not be occupied by the copyright.
221
394
  resolve([
222
395
  elementWidth + 2 * this.padding,
223
- elementHeight + 2 * this.padding
396
+ elementHeight + 2 * this.padding,
224
397
  ]);
225
398
  };
399
+
226
400
  img.onerror = () => {
227
401
  resolve();
228
402
  };
229
403
  });
230
404
  }
405
+
231
406
  calculatePixelsToExport(mapToExport) {
232
407
  const { extent, coordinates } = this.props;
233
408
  let firstCoordinate;
234
409
  let oppositeCoordinate;
410
+
235
411
  if (extent) {
236
412
  firstCoordinate = getTopLeft(extent);
237
413
  oppositeCoordinate = getBottomRight(extent);
238
414
  } else if (coordinates) {
415
+ // In case of coordinates coming from DragBox interaction:
416
+ // firstCoordinate is the first coordinate drawn by the user.
417
+ // oppositeCoordinate is the coordinate of the point dragged by the user.
239
418
  [firstCoordinate, , oppositeCoordinate] = coordinates;
240
419
  }
420
+
241
421
  if (firstCoordinate && oppositeCoordinate) {
242
422
  const firstPixel = mapToExport.getPixelFromCoordinate(firstCoordinate);
243
- const oppositePixel = mapToExport.getPixelFromCoordinate(oppositeCoordinate);
423
+ const oppositePixel =
424
+ mapToExport.getPixelFromCoordinate(oppositeCoordinate);
244
425
  const pixelTopLeft = [
245
426
  firstPixel[0] <= oppositePixel[0] ? firstPixel[0] : oppositePixel[0],
246
- firstPixel[1] <= oppositePixel[1] ? firstPixel[1] : oppositePixel[1]
427
+ firstPixel[1] <= oppositePixel[1] ? firstPixel[1] : oppositePixel[1],
247
428
  ];
248
429
  const pixelBottomRight = [
249
430
  firstPixel[0] > oppositePixel[0] ? firstPixel[0] : oppositePixel[0],
250
- firstPixel[1] > oppositePixel[1] ? firstPixel[1] : oppositePixel[1]
431
+ firstPixel[1] > oppositePixel[1] ? firstPixel[1] : oppositePixel[1],
251
432
  ];
433
+
252
434
  return {
253
435
  x: pixelTopLeft[0],
254
436
  y: pixelTopLeft[1],
255
437
  w: pixelBottomRight[0] - pixelTopLeft[0],
256
- h: pixelBottomRight[1] - pixelTopLeft[1]
438
+ h: pixelBottomRight[1] - pixelTopLeft[1],
257
439
  };
258
440
  }
259
441
  return null;
260
442
  }
443
+
261
444
  createCanvasImage(mapToExport) {
262
445
  const { extraData } = this.props;
446
+
263
447
  return new Promise((resolve) => {
264
- mapToExport.once("rendercomplete", () => {
265
- const canvases = mapToExport.getTargetElement().getElementsByTagName("canvas");
448
+ mapToExport.once('rendercomplete', () => {
449
+ // Find all layer canvases and add it to dest canvas.
450
+ const canvases = mapToExport
451
+ .getTargetElement()
452
+ .getElementsByTagName('canvas');
453
+
454
+ // Create the canvas to export with the good size.
266
455
  let destCanvas;
267
456
  let destContext;
457
+
458
+ // canvases is an HTMLCollection, we don't try to transform to array because some compilers like cra doesn't translate it right.
268
459
  for (let i = 0; i < canvases.length; i += 1) {
269
460
  const canvas = canvases[i];
270
461
  if (!canvas.width || !canvas.height) {
462
+ // eslint-disable-next-line no-continue
271
463
  continue;
272
464
  }
273
465
  const clip = this.calculatePixelsToExport(mapToExport) || {
274
466
  x: 0,
275
467
  y: 0,
276
468
  w: canvas.width,
277
- h: canvas.height
469
+ h: canvas.height,
278
470
  };
471
+
279
472
  if (!destCanvas) {
280
- destCanvas = document.createElement("canvas");
473
+ destCanvas = document.createElement('canvas');
281
474
  destCanvas.width = clip.w;
282
475
  destCanvas.height = clip.h;
283
- destContext = destCanvas.getContext("2d");
476
+ destContext = destCanvas.getContext('2d');
284
477
  }
478
+
479
+ // Draw canvas to the canvas to export.
285
480
  destContext.drawImage(
286
481
  canvas,
287
482
  clip.x,
@@ -291,30 +486,46 @@ class CanvasSaveButton extends PureComponent {
291
486
  0,
292
487
  0,
293
488
  destCanvas.width,
294
- destCanvas.height
489
+ destCanvas.height,
295
490
  );
296
491
  }
492
+
297
493
  this.margin = CanvasSaveButton.getMargin(destCanvas);
494
+
495
+ // Custom info
298
496
  let logoPromise = Promise.resolve();
299
497
  if (destContext && extraData && extraData.logo) {
300
498
  logoPromise = this.drawElement(extraData.logo, destCanvas);
301
499
  }
500
+
302
501
  logoPromise.then((logoSize = [0, 0]) => {
502
+ // North arrow
303
503
  let arrowPromise = Promise.resolve();
304
504
  if (destContext && extraData && extraData.northArrow) {
305
505
  arrowPromise = this.drawElement(
306
506
  {
307
- src: extraData.northArrow.circled ? NorthArrowCircle : NorthArrowSimple,
308
- ...extraData.northArrow
507
+ src: extraData.northArrow.circled
508
+ ? NorthArrowCircle
509
+ : NorthArrowSimple,
510
+ ...extraData.northArrow,
309
511
  },
310
512
  destCanvas,
311
- logoSize
513
+ logoSize,
312
514
  );
313
515
  }
516
+
517
+ // Copyright
314
518
  arrowPromise.then((arrowSize = [0, 0]) => {
315
519
  const widestElement = Math.max(logoSize[0], arrowSize[0]);
316
- if (destContext && extraData && extraData.copyright && extraData.copyright.text) {
317
- const maxWidth = widestElement ? destContext.canvas.width - widestElement - this.margin : destContext.canvas.width;
520
+ if (
521
+ destContext &&
522
+ extraData &&
523
+ extraData.copyright &&
524
+ extraData.copyright.text
525
+ ) {
526
+ const maxWidth = widestElement
527
+ ? destContext.canvas.width - widestElement - this.margin
528
+ : destContext.canvas.width;
318
529
  this.drawCopyright(destContext, destCanvas, maxWidth);
319
530
  }
320
531
  let qrCodePromise = Promise.resolve();
@@ -322,8 +533,8 @@ class CanvasSaveButton extends PureComponent {
322
533
  qrCodePromise = this.drawElement(
323
534
  extraData.qrCode,
324
535
  destCanvas,
325
- void 0,
326
- "left"
536
+ undefined,
537
+ 'left',
327
538
  );
328
539
  }
329
540
  qrCodePromise.then(() => {
@@ -335,32 +546,38 @@ class CanvasSaveButton extends PureComponent {
335
546
  mapToExport.renderSync();
336
547
  });
337
548
  }
549
+
338
550
  downloadCanvasImage(canvas) {
551
+ // Use blob for large images
339
552
  const promise = new Promise((resolve) => {
340
553
  const { format } = this.props;
341
554
  if (/msie (9|10)/gi.test(window.navigator.userAgent.toLowerCase())) {
555
+ // ie 9 and 10
342
556
  const url = canvas.toDataURL(format);
343
- const w = window.open("about:blank", "");
557
+ const w = window.open('about:blank', '');
344
558
  w.document.write(`<img src="${url}" alt="from canvas"/>`);
345
559
  resolve(url);
346
560
  }
347
561
  if (window.navigator.msSaveBlob) {
562
+ // ie 11 and higher
348
563
  let image;
349
564
  try {
350
565
  image = canvas.msToBlob();
351
566
  } catch (e) {
567
+ // eslint-disable-next-line no-console
352
568
  console.log(e);
353
569
  }
354
570
  const blob = new Blob([image], {
355
- type: format
571
+ type: format,
356
572
  });
357
573
  resolve(blob);
358
574
  window.navigator.msSaveBlob(blob, this.getDownloadImageName());
359
575
  } else {
360
576
  canvas.toBlob((blob) => {
361
- const link = document.createElement("a");
577
+ const link = document.createElement('a');
362
578
  link.download = this.getDownloadImageName();
363
579
  link.href = URL.createObjectURL(blob);
580
+ // append child to document for firefox to be able to download.
364
581
  document.body.appendChild(link);
365
582
  link.click();
366
583
  resolve(blob);
@@ -369,8 +586,10 @@ class CanvasSaveButton extends PureComponent {
369
586
  });
370
587
  return promise;
371
588
  }
589
+
372
590
  render() {
373
591
  const { children, ...other } = this.props;
592
+
374
593
  delete other.onSaveStart;
375
594
  delete other.onSaveEnd;
376
595
  delete other.extraData;
@@ -380,20 +599,28 @@ class CanvasSaveButton extends PureComponent {
380
599
  delete other.coordinates;
381
600
  delete other.autoDownload;
382
601
  delete other.scale;
383
- return /* @__PURE__ */ React.createElement("div", {
384
- role: "button",
385
- className: "rs-canvas-save-button",
386
- tabIndex: 0,
387
- ...other,
388
- onClick: (e) => {
389
- return this.onClick(e);
390
- },
391
- onKeyPress: (e) => {
392
- return e.which === 13 && this.onClick(e);
393
- }
394
- }, children);
602
+
603
+ return (
604
+ <div
605
+ role="button"
606
+ className="rs-canvas-save-button"
607
+ tabIndex={0}
608
+ // eslint-disable-next-line react/jsx-props-no-spreading
609
+ {...other}
610
+ onClick={(e) => {
611
+ return this.onClick(e);
612
+ }}
613
+ onKeyPress={(e) => {
614
+ return e.which === 13 && this.onClick(e);
615
+ }}
616
+ >
617
+ {children}
618
+ </div>
619
+ );
395
620
  }
396
621
  }
622
+
397
623
  CanvasSaveButton.propTypes = propTypes;
398
624
  CanvasSaveButton.defaultProps = defaultProps;
625
+
399
626
  export default CanvasSaveButton;