From b9e5a319838c2c1992f8ece53052c9d181a44049 Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Mon, 8 Jul 2024 13:23:27 +0200 Subject: [PATCH 01/16] export map changes --- package-lock.json | 180 ++++++++++++++++++++++++++ package.json | 2 + src/actions/controlActions.tsx | 1 + src/actions/mapActions.tsx | 8 ++ src/components/MapInteractionsBar.tsx | 13 ++ src/components/ol/ExportMap.tsx | 77 +++++++++++ src/components/ol/layer/Tile.tsx | 3 + src/states/controlState.ts | 3 +- 8 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 src/components/ol/ExportMap.tsx diff --git a/package-lock.json b/package-lock.json index b739e7eb..7b591cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,8 @@ "fflate": "^0.7.4", "file-saver": "^2.0.5", "history": "^4.10.1", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", "jszip": "^3.8.0", "oidc-client-ts": "^2.0.3", "ol": "^6.15.1", @@ -2441,6 +2443,12 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, "node_modules/@types/react": { "version": "17.0.80", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", @@ -3157,6 +3165,17 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3201,6 +3220,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3222,6 +3249,17 @@ "node": ">=8" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3258,6 +3296,31 @@ "node": ">=6" } }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true + }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -3426,6 +3489,17 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "hasInstallScript": true, + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3470,6 +3544,14 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -3852,6 +3934,12 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.5.tgz", + "integrity": "sha512-FgbqnEPiv5Vdtwt6Mxl7XSylttCC03cqP5ldNT2z+Kj0nLxPHJH4+1Cyf5Jasxhw93Rl4Oo11qRoUV72fmya2Q==", + "optional": true + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -4735,6 +4823,18 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5597,6 +5697,28 @@ "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jspdf/node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/jss": { "version": "10.10.0", "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", @@ -6833,6 +6955,12 @@ "pbf": "bin/pbf" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "optional": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7034,6 +7162,15 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/rbush": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", @@ -7435,6 +7572,15 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rifm": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz", @@ -7782,6 +7928,15 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/std-env": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", @@ -7915,6 +8070,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -7957,6 +8121,14 @@ "node": "*" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8269,6 +8441,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", diff --git a/package.json b/package.json index 9ad17d90..3b1b5e62 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "fflate": "^0.7.4", "file-saver": "^2.0.5", "history": "^4.10.1", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", "jszip": "^3.8.0", "oidc-client-ts": "^2.0.3", "ol": "^6.15.1", diff --git a/src/actions/controlActions.tsx b/src/actions/controlActions.tsx index e904a0e2..83e7821c 100644 --- a/src/actions/controlActions.tsx +++ b/src/actions/controlActions.tsx @@ -65,6 +65,7 @@ import { } from "./dataActions"; import { MessageLogAction, postMessage } from "./messageLogActions"; import { locateInMap } from "./mapActions"; +import { exportMap } from "./mapActions"; //////////////////////////////////////////////////////////////////////////////// diff --git a/src/actions/mapActions.tsx b/src/actions/mapActions.tsx index 63f0c934..cb350066 100644 --- a/src/actions/mapActions.tsx +++ b/src/actions/mapActions.tsx @@ -31,6 +31,7 @@ import { default as OlSimpleGeometry } from "ol/geom/SimpleGeometry"; import { GEOGRAPHIC_CRS } from "@/model/proj"; import { MAP_OBJECTS } from "@/states/controlState"; +import { exportMapAsImage } from "@/components/ol/ExportMap"; // noinspection JSUnusedLocalSymbols export function renameUserPlaceInLayer( @@ -71,3 +72,10 @@ export function locateInMap( } } } + +export function exportMap(mapId: string) { + if (MAP_OBJECTS[mapId]) { + const map = MAP_OBJECTS[mapId] as OlMap; + exportMapAsImage(map); + } +} diff --git a/src/components/MapInteractionsBar.tsx b/src/components/MapInteractionsBar.tsx index 5a286217..22a31c6b 100644 --- a/src/components/MapInteractionsBar.tsx +++ b/src/components/MapInteractionsBar.tsx @@ -34,11 +34,13 @@ import CategoryIcon from "@mui/icons-material/Category"; import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; import FileUploadIcon from "@mui/icons-material/CloudUpload"; import { styled } from "@mui/system"; +import CameraAltIcon from "@mui/icons-material/CameraAlt"; import i18n from "@/i18n"; import { MapInteraction } from "@/states/controlState"; import { WithLocale } from "@/util/lang"; import { commonStyles } from "@/components/common-styles"; +import { exportMap } from "@/actions/mapActions"; const StyledFromControl = styled(FormControl)(({ theme }) => ({ marginTop: theme.spacing(2), @@ -124,6 +126,17 @@ export default function MapInteractionsBar({ + exportMap("map")} + > + + + + ); diff --git a/src/components/ol/ExportMap.tsx b/src/components/ol/ExportMap.tsx new file mode 100644 index 00000000..40aa2787 --- /dev/null +++ b/src/components/ol/ExportMap.tsx @@ -0,0 +1,77 @@ +import { Map as OlMap } from "ol"; + +const dims: { [key: string]: [number, number] } = { + a0: [1189, 841], + a1: [841, 594], + a2: [594, 420], + a3: [420, 297], + a4: [297, 210], + a5: [210, 148], +}; + +export const exportMapAsImage = ( + map: OlMap, + // format: string = "a4", + // resolution: number = 150, +) => { + const mapElement = map.getTargetElement(); + if (!mapElement) { + console.error("Map element not found."); + document.body.style.cursor = "auto"; + return; + } + const mapCanvas = document.createElement("canvas"); + const size = map.getSize(); + if (size) { + mapCanvas.width = size[0]; + mapCanvas.height = size[1]; + } + const mapContext = mapCanvas.getContext("2d"); + + if (mapContext) { + Array.prototype.forEach.call( + document.querySelectorAll(".ol-layer canvas"), + (canvas: HTMLCanvasElement) => { + if (canvas.width > 0) { + const opacity = canvas.style.opacity || 1; + mapContext.globalAlpha = Number(opacity); + const transform = canvas.style.transform; + const matrix = transform + .match(/^matrix\(([^\(]*)\)$/)?.[1] + .split(",") + .map(Number) || [1, 0, 0, 1, 0, 0]; + mapContext.setTransform( + matrix[0], + matrix[1], + matrix[2], + matrix[3], + matrix[4], + matrix[5], + ); + mapContext.drawImage(canvas, 0, 0); + } + }, + ); + + mapContext.globalAlpha = 1; + mapContext.setTransform(1, 0, 0, 1, 0, 0); + + mapCanvas.toBlob((blob) => { + if (blob) { + const item = new ClipboardItem({ "image/png": blob }); + navigator.clipboard.write([item]).then( + () => { + alert("Map image copied to clipboard."); + }, + (error) => { + console.error("Error copying image to clipboard: ", error); + alert("Failed to copy map image to clipboard."); + }, + ); + } + + map.setSize(size); + document.body.style.cursor = "auto"; + }); + } +}; diff --git a/src/components/ol/layer/Tile.tsx b/src/components/ol/layer/Tile.tsx index 8ed883e2..a24a4b16 100644 --- a/src/components/ol/layer/Tile.tsx +++ b/src/components/ol/layer/Tile.tsx @@ -170,6 +170,7 @@ const NATURAL_EARTH_2_SOURCE = new OlXYZSource({ "© MapBox", "© MapBox and contributors", ], + crossOrigin: "anonymous", }); const BATHYMETRY_SOURCE = new OlXYZSource({ @@ -178,6 +179,7 @@ const BATHYMETRY_SOURCE = new OlXYZSource({ "© GEBCO", "© NOAHH and contributors", ], + crossOrigin: "anonymous", }); const OSM_SOURCE = new OlOSMSource(); @@ -187,6 +189,7 @@ const OSM_BW_SOURCE = new OlXYZSource({ attributions: [ "© OpenStreetMap contributors", ], + crossOrigin: "anonymous", }); function equalTileGrids( diff --git a/src/states/controlState.ts b/src/states/controlState.ts index 30361d20..ac642eb7 100644 --- a/src/states/controlState.ts +++ b/src/states/controlState.ts @@ -49,7 +49,8 @@ export type MapInteraction = | "Point" | "Polygon" | "Circle" - | "Geometry"; + | "Geometry" + | "Export"; export type ViewMode = "text" | "list" | "code" | "python"; From 2c4cc8de6a182fdcc71742997f3f4efb220af4c1 Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Mon, 12 Aug 2024 11:42:03 +0200 Subject: [PATCH 02/16] now the images is getting downloaded but size is not proper --- src/components/ol/ExportMap.tsx | 60 +++++++++++++++++--------------- src/components/ol/layer/Tile.tsx | 11 +++--- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/components/ol/ExportMap.tsx b/src/components/ol/ExportMap.tsx index 40aa2787..264104f3 100644 --- a/src/components/ol/ExportMap.tsx +++ b/src/components/ol/ExportMap.tsx @@ -1,18 +1,9 @@ import { Map as OlMap } from "ol"; -const dims: { [key: string]: [number, number] } = { - a0: [1189, 841], - a1: [841, 594], - a2: [594, 420], - a3: [420, 297], - a4: [297, 210], - a5: [210, 148], -}; - export const exportMapAsImage = ( map: OlMap, - // format: string = "a4", - // resolution: number = 150, + //format: string = "a4", + resolution: number = 2, ) => { const mapElement = map.getTargetElement(); if (!mapElement) { @@ -20,11 +11,12 @@ export const exportMapAsImage = ( document.body.style.cursor = "auto"; return; } + const mapCanvas = document.createElement("canvas"); const size = map.getSize(); if (size) { - mapCanvas.width = size[0]; - mapCanvas.height = size[1]; + mapCanvas.width = 800 * resolution; + mapCanvas.height = 800 * resolution; } const mapContext = mapCanvas.getContext("2d"); @@ -56,22 +48,34 @@ export const exportMapAsImage = ( mapContext.globalAlpha = 1; mapContext.setTransform(1, 0, 0, 1, 0, 0); - mapCanvas.toBlob((blob) => { - if (blob) { - const item = new ClipboardItem({ "image/png": blob }); - navigator.clipboard.write([item]).then( - () => { - alert("Map image copied to clipboard."); - }, - (error) => { - console.error("Error copying image to clipboard: ", error); - alert("Failed to copy map image to clipboard."); - }, - ); - } + const dataURL = mapCanvas.toDataURL("image/png"); + + let image = new Image(); + image.crossOrigin = "anonymous"; + image.src = dataURL; + + image.onload = () => { + mapContext.clearRect(0, 0, mapCanvas.width, mapCanvas.height); + mapContext.drawImage(image, mapCanvas.width / 2 - 40, 25, 80, 120); + + mapCanvas.toBlob((blob) => { + if (blob) { + const item = new ClipboardItem({ "image/png": blob }); + navigator.clipboard.write([item]).then( + () => { + //TODO- Add this to dispatch and remove below code + alert("Map image copied to clipboard."); + }, + (error) => { + console.error("Error copying image to clipboard: ", error); + alert("Failed to copy map image to clipboard."); + } + ); + } + }, "image/png"); map.setSize(size); document.body.style.cursor = "auto"; - }); + }; } -}; +}; \ No newline at end of file diff --git a/src/components/ol/layer/Tile.tsx b/src/components/ol/layer/Tile.tsx index a24a4b16..d58fdfe5 100644 --- a/src/components/ol/layer/Tile.tsx +++ b/src/components/ol/layer/Tile.tsx @@ -41,7 +41,7 @@ let trace: (message?: string, ...optionalParams: unknown[]) => void; if (import.meta.env.DEV && DEBUG) { trace = console.debug; } else { - trace = () => {}; + trace = () => { }; } // noinspection JSUnusedGlobalSymbols @@ -66,12 +66,16 @@ export function OSMBlackAndWhite(): JSX.Element { interface TileProps extends MapComponentProps, - OlTileLayerOptions {} + OlTileLayerOptions { } export class Tile extends MapComponent, TileProps> { addMapObject(map: OlMap): OlTileLayer { const layer = new OlTileLayer(this.props); layer.set("id", this.props.id); + const source = layer.getSource(); + if (source && 'crossOrigin' in source) { + (source as any).crossOrigin = "Anonymous"; + } map.getLayers().push(layer); return layer; } @@ -170,7 +174,6 @@ const NATURAL_EARTH_2_SOURCE = new OlXYZSource({ "© MapBox", "© MapBox and contributors", ], - crossOrigin: "anonymous", }); const BATHYMETRY_SOURCE = new OlXYZSource({ @@ -179,7 +182,6 @@ const BATHYMETRY_SOURCE = new OlXYZSource({ "© GEBCO", "© NOAHH and contributors", ], - crossOrigin: "anonymous", }); const OSM_SOURCE = new OlOSMSource(); @@ -189,7 +191,6 @@ const OSM_BW_SOURCE = new OlXYZSource({ attributions: [ "© OpenStreetMap contributors", ], - crossOrigin: "anonymous", }); function equalTileGrids( From 1d584dc606561a47a802ad950280c93c0918aaad Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Wed, 11 Sep 2024 16:36:33 +0200 Subject: [PATCH 03/16] changes --- src/components/ol/ExportMap.tsx | 67 ++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/components/ol/ExportMap.tsx b/src/components/ol/ExportMap.tsx index 264104f3..08235d72 100644 --- a/src/components/ol/ExportMap.tsx +++ b/src/components/ol/ExportMap.tsx @@ -2,8 +2,7 @@ import { Map as OlMap } from "ol"; export const exportMapAsImage = ( map: OlMap, - //format: string = "a4", - resolution: number = 2, + resolution: number = 4 ) => { const mapElement = map.getTargetElement(); if (!mapElement) { @@ -14,32 +13,52 @@ export const exportMapAsImage = ( const mapCanvas = document.createElement("canvas"); const size = map.getSize(); + if (size) { - mapCanvas.width = 800 * resolution; - mapCanvas.height = 800 * resolution; + const width = size[0]; + const height = size[1]; + + + mapCanvas.width = width * resolution; + mapCanvas.height = height * resolution; } + const mapContext = mapCanvas.getContext("2d"); if (mapContext) { + + mapContext.scale(resolution, resolution); + + Array.prototype.forEach.call( - document.querySelectorAll(".ol-layer canvas"), + document.querySelectorAll(".ol-viewport canvas, .ol-unselectable .ol-control"), (canvas: HTMLCanvasElement) => { if (canvas.width > 0) { const opacity = canvas.style.opacity || 1; mapContext.globalAlpha = Number(opacity); + let matrix: number[]; const transform = canvas.style.transform; - const matrix = transform - .match(/^matrix\(([^\(]*)\)$/)?.[1] - .split(",") - .map(Number) || [1, 0, 0, 1, 0, 0]; - mapContext.setTransform( - matrix[0], - matrix[1], - matrix[2], - matrix[3], - matrix[4], - matrix[5], - ); + if (transform) { + matrix = transform + .match(/^matrix\(([^\(]*)\)$/)?.[1] + .split(',') + .map(Number) || [1, 0, 0, 1, 0, 0]; + } else { + matrix = [ + parseFloat(canvas.style.width) / canvas.width || 1, + 0, + 0, + parseFloat(canvas.style.height) / canvas.height || 1, + 0, + 0, + ]; + } + + if (matrix.length === 6) { + mapContext.setTransform( + matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5] + ); + } mapContext.drawImage(canvas, 0, 0); } }, @@ -56,15 +75,20 @@ export const exportMapAsImage = ( image.onload = () => { mapContext.clearRect(0, 0, mapCanvas.width, mapCanvas.height); - mapContext.drawImage(image, mapCanvas.width / 2 - 40, 25, 80, 120); + + mapContext.drawImage( + image, + 0, 0, + mapCanvas.width, + mapCanvas.height + ); mapCanvas.toBlob((blob) => { if (blob) { const item = new ClipboardItem({ "image/png": blob }); navigator.clipboard.write([item]).then( () => { - //TODO- Add this to dispatch and remove below code - alert("Map image copied to clipboard."); + alert("map image copied to clipboard."); }, (error) => { console.error("Error copying image to clipboard: ", error); @@ -74,8 +98,7 @@ export const exportMapAsImage = ( } }, "image/png"); - map.setSize(size); document.body.style.cursor = "auto"; }; } -}; \ No newline at end of file +}; From ef1216c3ccdfa2623104ee9426873dad8f0e80ad Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Thu, 12 Sep 2024 10:14:45 +0200 Subject: [PATCH 04/16] change --- src/actions/controlActions.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/actions/controlActions.tsx b/src/actions/controlActions.tsx index 83e7821c..e904a0e2 100644 --- a/src/actions/controlActions.tsx +++ b/src/actions/controlActions.tsx @@ -65,7 +65,6 @@ import { } from "./dataActions"; import { MessageLogAction, postMessage } from "./messageLogActions"; import { locateInMap } from "./mapActions"; -import { exportMap } from "./mapActions"; //////////////////////////////////////////////////////////////////////////////// From 4e4f5a1a4a67fb31153e6b59a7b7d9dc2f94a539 Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Thu, 12 Sep 2024 18:34:51 +0200 Subject: [PATCH 05/16] used snapshot button cmp and all origin set to anonymous --- src/actions/mapActions.tsx | 8 -- .../ColorBarLegend/ColorBarLegend.tsx | 2 +- src/components/MapInteractionsBar.tsx | 14 +-- src/components/SnapshotButton.tsx | 32 +++++- src/components/ol/ExportMap.tsx | 104 ------------------ src/components/ol/layer/Tile.tsx | 4 - src/connected/MapInteractionsBar.tsx | 2 + src/selectors/controlSelectors.tsx | 1 + src/util/export.ts | 11 ++ 9 files changed, 49 insertions(+), 129 deletions(-) delete mode 100644 src/components/ol/ExportMap.tsx diff --git a/src/actions/mapActions.tsx b/src/actions/mapActions.tsx index 73c2269b..02cc8181 100644 --- a/src/actions/mapActions.tsx +++ b/src/actions/mapActions.tsx @@ -35,7 +35,6 @@ import { GEOGRAPHIC_CRS } from "@/model/proj"; import { MAP_OBJECTS } from "@/states/controlState"; import { PlaceStyle } from "@/model/place"; import { setFeatureStyle } from "@/components/ol/style"; -import { exportMapAsImage } from "@/components/ol/ExportMap"; // noinspection JSUnusedLocalSymbols export function renameUserPlaceInLayer( @@ -94,10 +93,3 @@ export function locateInMap( } } } - -export function exportMap(mapId: string) { - if (MAP_OBJECTS[mapId]) { - const map = MAP_OBJECTS[mapId] as OlMap; - exportMapAsImage(map); - } -} diff --git a/src/components/ColorBarLegend/ColorBarLegend.tsx b/src/components/ColorBarLegend/ColorBarLegend.tsx index 242ff42d..c37caf34 100644 --- a/src/components/ColorBarLegend/ColorBarLegend.tsx +++ b/src/components/ColorBarLegend/ColorBarLegend.tsx @@ -124,7 +124,7 @@ export default function ColorBarLegend( : `${variableTitle || variableName} (${variableUnits || "-"})`; return ( - + {variableTitleWithUnits} {variableColorBar.type === "categorical" ? ( ({ marginTop: theme.spacing(2), @@ -51,11 +51,13 @@ const StyledFromControl = styled(FormControl)(({ theme }) => ({ interface MapInteractionsBarProps extends WithLocale { mapInteraction: MapInteraction; setMapInteraction: (interaction: MapInteraction) => void; + postMessage: (messageType: MessageType, messageText: string | Error) => void; } export default function MapInteractionsBar({ mapInteraction, setMapInteraction, + postMessage }: MapInteractionsBarProps) { function handleChange( _event: React.MouseEvent, @@ -130,12 +132,8 @@ export default function MapInteractionsBar({ key={5} value="Export" size="small" - sx={commonStyles.toggleButton} - onClick={() => exportMap("map")} - > - - - + sx={commonStyles.toggleButton}> + diff --git a/src/components/SnapshotButton.tsx b/src/components/SnapshotButton.tsx index 3e9e8db7..b4d0b2af 100644 --- a/src/components/SnapshotButton.tsx +++ b/src/components/SnapshotButton.tsx @@ -24,22 +24,30 @@ import { RefObject } from "react"; import CameraAltIcon from "@mui/icons-material/CameraAlt"; +import { default as OlMap } from "ol/Map"; import i18n from "@/i18n"; import { WithLocale } from "@/util/lang"; import { MessageType } from "@/states/messageLogState"; import { exportElement } from "@/util/export"; import ToolButton from "@/components/ToolButton"; +import { MAP_OBJECTS } from "@/states/controlState"; interface SnapshotButtonProps extends WithLocale { - elementRef: RefObject; + //btnKey?: string; + elementRef?: RefObject; + mapRef?: string; postMessage: (messageType: MessageType, messageText: string | Error) => void; } export default function SnapshotButton({ + //btnKey, elementRef, + mapRef, postMessage, }: SnapshotButtonProps) { + const pixelRatio = 2; + const handleExportSuccess = () => { postMessage("success", i18n.get("Snapshot copied to clipboard")); }; @@ -51,12 +59,27 @@ export default function SnapshotButton({ }; const handleButtonClick = () => { - if (elementRef.current) { - exportElement(elementRef.current!, { + let targetElement: HTMLElement | null = null; + + if (mapRef) { + if (MAP_OBJECTS[mapRef]) { + const map = MAP_OBJECTS[mapRef] as OlMap; + targetElement = map.getTargetElement(); + } + } else if (elementRef) { + // Handle elementRef scenario + if (elementRef.current) { + targetElement = elementRef.current; + } + } + + if (targetElement) { + exportElement(targetElement, { format: "png", width: 2000, handleSuccess: handleExportSuccess, handleError: handleExportError, + pixelratio: pixelRatio, }); } else { handleExportError(new Error("missing element reference")); @@ -65,7 +88,8 @@ export default function SnapshotButton({ return ( } /> diff --git a/src/components/ol/ExportMap.tsx b/src/components/ol/ExportMap.tsx deleted file mode 100644 index 08235d72..00000000 --- a/src/components/ol/ExportMap.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Map as OlMap } from "ol"; - -export const exportMapAsImage = ( - map: OlMap, - resolution: number = 4 -) => { - const mapElement = map.getTargetElement(); - if (!mapElement) { - console.error("Map element not found."); - document.body.style.cursor = "auto"; - return; - } - - const mapCanvas = document.createElement("canvas"); - const size = map.getSize(); - - if (size) { - const width = size[0]; - const height = size[1]; - - - mapCanvas.width = width * resolution; - mapCanvas.height = height * resolution; - } - - const mapContext = mapCanvas.getContext("2d"); - - if (mapContext) { - - mapContext.scale(resolution, resolution); - - - Array.prototype.forEach.call( - document.querySelectorAll(".ol-viewport canvas, .ol-unselectable .ol-control"), - (canvas: HTMLCanvasElement) => { - if (canvas.width > 0) { - const opacity = canvas.style.opacity || 1; - mapContext.globalAlpha = Number(opacity); - let matrix: number[]; - const transform = canvas.style.transform; - if (transform) { - matrix = transform - .match(/^matrix\(([^\(]*)\)$/)?.[1] - .split(',') - .map(Number) || [1, 0, 0, 1, 0, 0]; - } else { - matrix = [ - parseFloat(canvas.style.width) / canvas.width || 1, - 0, - 0, - parseFloat(canvas.style.height) / canvas.height || 1, - 0, - 0, - ]; - } - - if (matrix.length === 6) { - mapContext.setTransform( - matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5] - ); - } - mapContext.drawImage(canvas, 0, 0); - } - }, - ); - - mapContext.globalAlpha = 1; - mapContext.setTransform(1, 0, 0, 1, 0, 0); - - const dataURL = mapCanvas.toDataURL("image/png"); - - let image = new Image(); - image.crossOrigin = "anonymous"; - image.src = dataURL; - - image.onload = () => { - mapContext.clearRect(0, 0, mapCanvas.width, mapCanvas.height); - - mapContext.drawImage( - image, - 0, 0, - mapCanvas.width, - mapCanvas.height - ); - - mapCanvas.toBlob((blob) => { - if (blob) { - const item = new ClipboardItem({ "image/png": blob }); - navigator.clipboard.write([item]).then( - () => { - alert("map image copied to clipboard."); - }, - (error) => { - console.error("Error copying image to clipboard: ", error); - alert("Failed to copy map image to clipboard."); - } - ); - } - }, "image/png"); - - document.body.style.cursor = "auto"; - }; - } -}; diff --git a/src/components/ol/layer/Tile.tsx b/src/components/ol/layer/Tile.tsx index d58fdfe5..0070951f 100644 --- a/src/components/ol/layer/Tile.tsx +++ b/src/components/ol/layer/Tile.tsx @@ -72,10 +72,6 @@ export class Tile extends MapComponent, TileProps> { addMapObject(map: OlMap): OlTileLayer { const layer = new OlTileLayer(this.props); layer.set("id", this.props.id); - const source = layer.getSource(); - if (source && 'crossOrigin' in source) { - (source as any).crossOrigin = "Anonymous"; - } map.getLayers().push(layer); return layer; } diff --git a/src/connected/MapInteractionsBar.tsx b/src/connected/MapInteractionsBar.tsx index b678d4be..a9abd0e9 100644 --- a/src/connected/MapInteractionsBar.tsx +++ b/src/connected/MapInteractionsBar.tsx @@ -27,6 +27,7 @@ import { connect } from "react-redux"; import _MapInteractionsBar from "@/components/MapInteractionsBar"; import { AppState } from "@/states/appState"; import { setMapInteraction } from "@/actions/controlActions"; +import { postMessage } from "@/actions/messageLogActions"; const mapStateToProps = (state: AppState) => { return { @@ -36,6 +37,7 @@ const mapStateToProps = (state: AppState) => { const mapDispatchToProps = { setMapInteraction, + postMessage, }; const MapInteractionsBar = connect( diff --git a/src/selectors/controlSelectors.tsx b/src/selectors/controlSelectors.tsx index f332bbe5..410f2cc6 100644 --- a/src/selectors/controlSelectors.tsx +++ b/src/selectors/controlSelectors.tsx @@ -840,6 +840,7 @@ function getOlXYZSource( // level at minZoom when zooming out! // minZoom: tileLevelMin, maxZoom: tileLevelMax, + crossOrigin: "Anonymous", }); } diff --git a/src/util/export.ts b/src/util/export.ts index 1feefe08..c3065807 100644 --- a/src/util/export.ts +++ b/src/util/export.ts @@ -37,6 +37,7 @@ export interface ExportOptions { height?: number; handleSuccess?: () => void; handleError?: (error: unknown) => void; + pixelratio?: number; } /** @@ -88,6 +89,16 @@ async function _exportElement( ((options.width || chartElement.clientWidth) * chartElement.clientHeight) / chartElement.clientWidth, + pixelRatio: options.pixelratio, + }); + let image = new Image(); + image.crossOrigin = "anonymous"; + image.src = dataUrl; + + // Wait for the image to load before proceeding + await new Promise((resolve, reject) => { + image.onload = () => resolve(); + image.onerror = (error) => reject(error); }); const response = await fetch(dataUrl); const blob = await response.blob(); From 7aff3ebd05840b2b0f79ba3a32b083ea18cff9de Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Mon, 16 Sep 2024 13:23:51 +0200 Subject: [PATCH 06/16] export map image --- src/components/MapInteractionsBar.tsx | 8 +--- src/components/SnapshotButton.tsx | 38 +++++++++++----- src/components/ol/layer/Tile.tsx | 4 ++ src/util/export.ts | 64 ++++++++++++++++++++------- 4 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/components/MapInteractionsBar.tsx b/src/components/MapInteractionsBar.tsx index 28bceb90..35dfd126 100644 --- a/src/components/MapInteractionsBar.tsx +++ b/src/components/MapInteractionsBar.tsx @@ -128,13 +128,7 @@ export default function MapInteractionsBar({ - - - + ); diff --git a/src/components/SnapshotButton.tsx b/src/components/SnapshotButton.tsx index b4d0b2af..c83374cd 100644 --- a/src/components/SnapshotButton.tsx +++ b/src/components/SnapshotButton.tsx @@ -34,10 +34,11 @@ import ToolButton from "@/components/ToolButton"; import { MAP_OBJECTS } from "@/states/controlState"; interface SnapshotButtonProps extends WithLocale { - //btnKey?: string; elementRef?: RefObject; mapRef?: string; postMessage: (messageType: MessageType, messageText: string | Error) => void; + fontSize?: 'small' | 'inherit' | 'medium' | 'large'; + isToggle?: boolean } export default function SnapshotButton({ @@ -45,8 +46,10 @@ export default function SnapshotButton({ elementRef, mapRef, postMessage, + fontSize = "inherit", + isToggle = false, }: SnapshotButtonProps) { - const pixelRatio = 2; + const pixelRatio = 1; const handleExportSuccess = () => { postMessage("success", i18n.get("Snapshot copied to clipboard")); @@ -58,29 +61,39 @@ export default function SnapshotButton({ postMessage("error", i18n.get(message)); }; - const handleButtonClick = () => { + const handleButtonClick = async () => { let targetElement: HTMLElement | null = null; + let controlDiv: HTMLElement | null = null; + let zoomDiv: HTMLElement | null = null; if (mapRef) { if (MAP_OBJECTS[mapRef]) { const map = MAP_OBJECTS[mapRef] as OlMap; targetElement = map.getTargetElement(); + controlDiv = targetElement.querySelector('.ol-unselectable.ol-control.MuiBox-root.css-0') as HTMLElement; + zoomDiv = targetElement.querySelector(".ol-zoom.ol-unselectable.ol-control") as HTMLElement; } } else if (elementRef) { - // Handle elementRef scenario if (elementRef.current) { targetElement = elementRef.current; } } if (targetElement) { - exportElement(targetElement, { - format: "png", - width: 2000, - handleSuccess: handleExportSuccess, - handleError: handleExportError, - pixelratio: pixelRatio, - }); + try { + // Pass controlDiv as part of ExportOptions + exportElement(targetElement, { + format: "png", + width: 2000, + handleSuccess: handleExportSuccess, + handleError: handleExportError, + pixelratio: pixelRatio, + controlDiv: controlDiv, + zoomDiv: zoomDiv, + }); + } catch (error) { + handleExportError(error); + } } else { handleExportError(new Error("missing element reference")); } @@ -91,7 +104,8 @@ export default function SnapshotButton({ //key={btnKey} tooltipText={i18n.get("Copy snapshot to clipboard")} onClick={handleButtonClick} - icon={} + toggle={isToggle} + icon={} /> ); } diff --git a/src/components/ol/layer/Tile.tsx b/src/components/ol/layer/Tile.tsx index 0070951f..d58fdfe5 100644 --- a/src/components/ol/layer/Tile.tsx +++ b/src/components/ol/layer/Tile.tsx @@ -72,6 +72,10 @@ export class Tile extends MapComponent, TileProps> { addMapObject(map: OlMap): OlTileLayer { const layer = new OlTileLayer(this.props); layer.set("id", this.props.id); + const source = layer.getSource(); + if (source && 'crossOrigin' in source) { + (source as any).crossOrigin = "Anonymous"; + } map.getLayers().push(layer); return layer; } diff --git a/src/util/export.ts b/src/util/export.ts index c3065807..c5c59076 100644 --- a/src/util/export.ts +++ b/src/util/export.ts @@ -38,6 +38,8 @@ export interface ExportOptions { handleSuccess?: () => void; handleError?: (error: unknown) => void; pixelratio?: number; + controlDiv?: HTMLElement | null; + zoomDiv?: HTMLElement | null; } /** @@ -72,40 +74,68 @@ async function _exportElement( element: HTMLElement, options: ExportOptions = {}, ): Promise { - const chartElement = element; const format = options.format || "png"; if (!(format in converters)) { throw new Error(`Image format '${format}' is unknown or not supported.`); } - const dataUrl = await converters[format](chartElement, { + + const canvasWidth = + options.width || + ((options.height || element.clientHeight) * element.clientWidth) / + element.clientHeight; + const canvasHeight = + options.height || + ((options.width || element.clientWidth) * element.clientHeight) / + element.clientWidth; + + const controlDiv = options.controlDiv; + if (controlDiv) controlDiv.style.display = "none"; + const zoomDiv = options.zoomDiv; + if (zoomDiv) zoomDiv.hidden = true; + + const offScreenCanvas = document.createElement("canvas"); + offScreenCanvas.width = canvasWidth; + offScreenCanvas.height = canvasHeight; + const context = offScreenCanvas.getContext("2d"); + + if (!context) { + throw new Error("Failed to get canvas context."); + } + + const dataUrl = await converters[format](element, { backgroundColor: "#00000000", - canvasWidth: - options.width || - ((options.height || chartElement.clientHeight) * - chartElement.clientWidth) / - chartElement.clientHeight, - canvasHeight: - options.height || - ((options.width || chartElement.clientWidth) * - chartElement.clientHeight) / - chartElement.clientWidth, + canvasWidth, + canvasHeight, pixelRatio: options.pixelratio, }); - let image = new Image(); + + const image = new Image(); image.crossOrigin = "anonymous"; image.src = dataUrl; - // Wait for the image to load before proceeding await new Promise((resolve, reject) => { image.onload = () => resolve(); image.onerror = (error) => reject(error); }); - const response = await fetch(dataUrl); - const blob = await response.blob(); + + context.drawImage(image, 0, 0); + + const blob = await new Promise((resolve, reject) => { + offScreenCanvas.toBlob((blob) => { + if (blob === null) { + reject(new Error("Failed to create a blob from the canvas.")); + } else { + resolve(blob); + } + }, `image/${format}`); + }); await navigator.clipboard.write([ new ClipboardItem({ - "image/png": blob, + [`image/${format}`]: blob, }), ]); + + if (controlDiv) controlDiv.style.display = "block"; + if (zoomDiv) zoomDiv.hidden = false; } From 4b68e3bb986c47a44c5f020f389b2ce61f99deed Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Mon, 16 Sep 2024 13:51:11 +0200 Subject: [PATCH 07/16] removed unnecessary packages --- package-lock.json | 163 ++-------------------------------------------- package.json | 2 - 2 files changed, 5 insertions(+), 160 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5685a8b5..81b8ac45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,8 +30,6 @@ "file-saver": "^2.0.5", "history": "^4.10.1", "html-to-image": "^1.11.11", - "html2canvas": "^1.4.1", - "jspdf": "^2.5.1", "jszip": "^3.8.0", "oidc-client-ts": "^2.0.3", "ol": "^6.15.1", @@ -2774,12 +2772,6 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, - "node_modules/@types/raf": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", - "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", - "optional": true - }, "node_modules/@types/react": { "version": "17.0.62", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.62.tgz", @@ -3606,17 +3598,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3661,14 +3642,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3690,17 +3663,6 @@ "node": ">=8" } }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3738,31 +3700,6 @@ "node": ">=6" } }, - "node_modules/canvg": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", - "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/raf": "^3.4.0", - "core-js": "^3.8.3", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.7", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^6.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/canvg/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "optional": true - }, "node_modules/chai": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", @@ -3943,17 +3880,6 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", - "hasInstallScript": true, - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3998,14 +3924,6 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -4382,18 +4300,6 @@ "csstype": "^3.0.2" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5249,6 +5155,11 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-to-image": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz", + "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5915,12 +5826,6 @@ "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz", "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==" }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, "node_modules/jss": { "version": "10.10.0", "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", @@ -7117,12 +7022,6 @@ "pbf": "bin/pbf" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "optional": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7292,15 +7191,6 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "optional": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, "node_modules/rbush": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", @@ -7706,15 +7596,6 @@ "node": ">=0.10.0" } }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "optional": true, - "engines": { - "node": ">= 0.8.15" - } - }, "node_modules/rifm": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz", @@ -7980,15 +7861,6 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true }, - "node_modules/stackblur-canvas": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", - "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", - "optional": true, - "engines": { - "node": ">=0.1.14" - } - }, "node_modules/std-env": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", @@ -8115,15 +7987,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-pathdata": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", - "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -8166,14 +8029,6 @@ "node": "*" } }, - "node_modules/text-segmentation": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8490,14 +8345,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "dependencies": { - "base64-arraybuffer": "^1.0.2" - } - }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", diff --git a/package.json b/package.json index 000ce870..e4b58a8c 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,6 @@ "fflate": "^0.7.4", "file-saver": "^2.0.5", "history": "^4.10.1", - "html2canvas": "^1.4.1", - "jspdf": "^2.5.1", "html-to-image": "^1.11.11", "jszip": "^3.8.0", "oidc-client-ts": "^2.0.3", From 102d75b1a31f3221d32e9e36272ffa670cc55d25 Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Mon, 16 Sep 2024 15:33:13 +0200 Subject: [PATCH 08/16] changes.md updates and removed any --- CHANGES.md | 2 ++ src/components/ol/layer/Tile.tsx | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 70fce827..866cae94 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,6 +31,8 @@ * Now recognising new custom color maps from xcube server, for details refer to https://github.com/xcube-dev/xcube/issues/1046. (#392) +* Users can now copy snapshots of a map into the clipboard by clicking a new camera icon on a map's action bar.(#290) + ### Enhancements * Avoiding confusion regarding variable comparison. diff --git a/src/components/ol/layer/Tile.tsx b/src/components/ol/layer/Tile.tsx index d58fdfe5..54f6fbf4 100644 --- a/src/components/ol/layer/Tile.tsx +++ b/src/components/ol/layer/Tile.tsx @@ -72,9 +72,9 @@ export class Tile extends MapComponent, TileProps> { addMapObject(map: OlMap): OlTileLayer { const layer = new OlTileLayer(this.props); layer.set("id", this.props.id); - const source = layer.getSource(); + const source = layer.getSource() as OlTileSource & { crossOrigin?: string }; if (source && 'crossOrigin' in source) { - (source as any).crossOrigin = "Anonymous"; + source.crossOrigin = "Anonymous"; } map.getLayers().push(layer); return layer; From 0e7f1cea207e3da1f7595374faf0ab54e098c6a3 Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Mon, 16 Sep 2024 15:52:49 +0200 Subject: [PATCH 09/16] translation updated --- src/resources/lang.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/resources/lang.json b/src/resources/lang.json index 146bd6ff..5ace5a51 100644 --- a/src/resources/lang.json +++ b/src/resources/lang.json @@ -461,9 +461,9 @@ "se": "Hjälp" }, { - "en": "Copy snapshot of chart to clipboard", - "de": "Schnappschuss des Diagramms in die Zwischenablage kopieren", - "se": "Kopiera ögonblicksbild av diagrammet till urklipp" + "en": "Copy snapshot to clipboard", + "de": "Schnappschuss in die Zwischenablage kopieren", + "se": "Kopiera ögonblicksbild till urklipp" }, { "en": "Snapshot copied to clipboard", From 15f8c23198139c33d45fcfc054b379b8c66055f7 Mon Sep 17 00:00:00 2001 From: ruchimotwaniBC Date: Wed, 18 Sep 2024 10:43:32 +0200 Subject: [PATCH 10/16] changes file updated --- CHANGES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 51eeb0fa..852ccefd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ ## Changes in version 1.3.1 (in development) +### New Features + +* Users can now copy snapshots of a map into the clipboard by clicking a new + camera icon on a map's action bar.(#290) + ### Fixes * The `