From 615c1dcccf06c6e6262d63bf15755fbf6aa1969f Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Tue, 3 Dec 2024 09:19:16 +0100 Subject: [PATCH 01/33] chore: Add html-to-image --- app/package.json | 1 + yarn.lock | 147 +++++++++++++++++++++++++++++------------------ 2 files changed, 93 insertions(+), 55 deletions(-) diff --git a/app/package.json b/app/package.json index e593ab727..66f7840dc 100644 --- a/app/package.json +++ b/app/package.json @@ -73,6 +73,7 @@ "graphql-tag": "^2.11.0", "graphql-tools": "^7.0.5", "graphql-type-json": "^0.3.2", + "html-to-image": "^1.11.11", "iframe-resizer": "^4.2.11", "immer": "^9.0.6", "io-ts": "^2.2.21", diff --git a/yarn.lock b/yarn.lock index 95a66cd8d..946b63968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -230,7 +230,29 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== -"@babel/core@7.12.9", "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.10.5", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.12.9", "@babel/core@^7.14.6", "@babel/core@^7.18.9", "@babel/core@^7.21.0", "@babel/core@^7.23.0", "@babel/core@^7.23.2", "@babel/core@^7.24.4", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.7.7": +"@babel/core@7.12.9": + version "7.12.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" + integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.5" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.7" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.9" + "@babel/types" "^7.12.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.10.5", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.12.9", "@babel/core@^7.18.9", "@babel/core@^7.21.0", "@babel/core@^7.23.0", "@babel/core@^7.23.2", "@babel/core@^7.24.4", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.7.7": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== @@ -260,6 +282,17 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.12.5", "@babel/generator@^7.25.9", "@babel/generator@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== + dependencies: + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/generator@^7.20.14": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" @@ -290,17 +323,6 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" - integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== - dependencies: - "@babel/parser" "^7.26.2" - "@babel/types" "^7.26.0" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - "@babel/helper-annotate-as-pure@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz" @@ -538,6 +560,15 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-module-transforms@^7.14.5": version "7.15.8" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz" @@ -563,15 +594,6 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - "@babel/helper-optimise-call-expression@^7.14.5", "@babel/helper-optimise-call-expression@^7.15.4": version "7.15.4" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz" @@ -786,7 +808,7 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helpers@^7.26.0": +"@babel/helpers@^7.12.5", "@babel/helpers@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== @@ -840,7 +862,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@7.12.16", "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.14.6", "@babel/parser@^7.14.7", "@babel/parser@^7.15.4", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.21.2", "@babel/parser@^7.21.4", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2", "@babel/parser@^7.7.2": +"@babel/parser@7.12.16": + version "7.12.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4" + integrity sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw== + +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.15.4", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.21.2", "@babel/parser@^7.21.4", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2", "@babel/parser@^7.7.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== @@ -2500,6 +2527,15 @@ resolved "https://registry.npmjs.org/@babel/standalone/-/standalone-7.14.6.tgz" integrity sha512-oAoSp82jhJFnXKybKTOj5QF04XxiDRyiiqrFToiU1udlBXuZoADlPmmnOcuqBrZxSNNUjzJIVK8vt838Qoqjxg== +"@babel/template@^7.12.7", "@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/template@^7.15.4", "@babel/template@^7.3.3": version "7.15.4" resolved "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz" @@ -2527,15 +2563,6 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" -"@babel/template@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" - integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/types" "^7.25.9" - "@babel/traverse@7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz" @@ -2566,6 +2593,19 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.12.9", "@babel/traverse@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" + debug "^4.3.1" + globals "^11.1.0" + "@babel/traverse@^7.18.9", "@babel/traverse@^7.23.2": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" @@ -2598,19 +2638,6 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/generator" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/template" "^7.25.9" - "@babel/types" "^7.25.9" - debug "^4.3.1" - globals "^11.1.0" - "@babel/types@7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz" @@ -2628,6 +2655,14 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" +"@babel/types@^7.12.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/types@^7.16.7": version "7.17.0" resolved "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz" @@ -2690,14 +2725,6 @@ "@babel/helper-validator-identifier" "^7.24.5" to-fast-properties "^2.0.0" -"@babel/types@^7.25.9", "@babel/types@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" - integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -15641,7 +15668,7 @@ fwd-stream@^1.0.4: dependencies: readable-stream "~1.0.26-4" -gensync@^1.0.0-beta.2: +gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== @@ -16459,6 +16486,11 @@ html-tags@^3.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== +html-to-image@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/html-to-image/-/html-to-image-1.11.11.tgz#c0f8a34dc9e4b97b93ff7ea286eb8562642ebbea" + integrity sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA== + html-void-elements@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz" @@ -22328,7 +22360,7 @@ resolve.exports@^1.1.0: resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@1.22.8, resolve@^1.14.2, resolve@^1.22.8: +resolve@1.22.8, resolve@^1.14.2, resolve@^1.22.8, resolve@^1.3.2: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -22676,6 +22708,11 @@ semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +semver@^5.4.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" From 85e46a456f06f2804171804583efd793e47f30a2 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Tue, 3 Dec 2024 09:19:25 +0100 Subject: [PATCH 02/33] feat: Add screenshot-making logic --- app/utils/use-screenshot.ts | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 app/utils/use-screenshot.ts diff --git a/app/utils/use-screenshot.ts b/app/utils/use-screenshot.ts new file mode 100644 index 000000000..880218e0a --- /dev/null +++ b/app/utils/use-screenshot.ts @@ -0,0 +1,100 @@ +import { toPng, toSvg } from "html-to-image"; +import { useCallback, useState } from "react"; + +type ScreenshotFileFormat = "png" | "svg"; + +export const useScreenshot = ({ + type, + screenshotName, + screenshotNode, + modifyNode: _modifyNode, +}: { + type: ScreenshotFileFormat; + screenshotName: string; + screenshotNode?: HTMLElement | null; + modifyNode?: (d: HTMLElement) => void; +}) => { + const [loading, setLoading] = useState(false); + const modifyNode = useCallback( + (node: HTMLElement) => { + removeDisabledElements(node); + + if (_modifyNode) { + _modifyNode(node); + } + }, + [_modifyNode] + ); + const screenshot = useCallback(async () => { + if (screenshotNode) { + setLoading(true); + try { + await makeScreenshot({ + type, + name: screenshotName, + node: screenshotNode, + modifyNode, + }); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + } + }, [type, screenshotName, screenshotNode, modifyNode]); + + return { loading, screenshot }; +}; + +const makeScreenshot = async ({ + type, + name, + node, + modifyNode, +}: { + type: "png" | "svg"; + name: string; + node: HTMLElement; + modifyNode?: (d: HTMLElement) => void; +}) => { + const isUsingSafari = + typeof window !== "undefined" + ? /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + : false; + // Add wrapper node to prevent overflow issues in the screenshot + const wrapperNode = document.createElement("div"); + wrapperNode.style.width = `${node.offsetWidth}px`; + document.body.appendChild(wrapperNode); + const clonedNode = node.cloneNode(true) as HTMLElement; + wrapperNode.appendChild(clonedNode); + modifyNode?.(clonedNode); + + // There's a bug with embedding the fonts in Safari, which appears only when + // downloading the image for the first time. On subsequent downloads, the + // font is embedded correctly. To work around this issue, we call the toPng + // function twice. + if (isUsingSafari && type === "png") { + await toPng(wrapperNode); + } + + await (type === "png" ? toPng : toSvg)(wrapperNode) + .then((dataUrl) => { + const a = document.createElement("a"); + a.href = dataUrl; + a.download = `${name}.${type}`; + a.click(); + }) + .catch((error) => console.error(error)) + .finally(() => wrapperNode.remove()); +}; + +export const DISABLE_SCREENSHOT_ATTR_KEY = "data-disable-screenshot"; +/** Apply this attribute to elements that should not be included in the screenshot. */ +export const DISABLE_SCREENSHOT_ATTR = { + [DISABLE_SCREENSHOT_ATTR_KEY]: true, +}; +const removeDisabledElements = (node: HTMLElement) => { + node + .querySelectorAll(`[${DISABLE_SCREENSHOT_ATTR_KEY}="true"]`) + .forEach((el) => el.remove()); +}; From 2ef79112830ed021da1fc66a0e3a2d308ee71680 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Tue, 3 Dec 2024 09:44:18 +0100 Subject: [PATCH 03/33] fix: Making screenshots of canvas elements --- app/charts/map/map.tsx | 2 ++ app/utils/use-screenshot.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/charts/map/map.tsx b/app/charts/map/map.tsx index bb2a3fd5e..b1b8037ec 100644 --- a/app/charts/map/map.tsx +++ b/app/charts/map/map.tsx @@ -403,6 +403,8 @@ export const MapComponent = () => { initialViewState={defaultViewState} mapLib={maplibregl} mapStyle={mapStyle} + // Important so we can take a screenshot of the map + preserveDrawingBuffer style={{ position: "absolute", left: 0, diff --git a/app/utils/use-screenshot.ts b/app/utils/use-screenshot.ts index 880218e0a..2a3728019 100644 --- a/app/utils/use-screenshot.ts +++ b/app/utils/use-screenshot.ts @@ -1,3 +1,4 @@ +import { select } from "d3-selection"; import { toPng, toSvg } from "html-to-image"; import { useCallback, useState } from "react"; @@ -65,7 +66,24 @@ const makeScreenshot = async ({ const wrapperNode = document.createElement("div"); wrapperNode.style.width = `${node.offsetWidth}px`; document.body.appendChild(wrapperNode); + const clonedNode = node.cloneNode(true) as HTMLElement; + + const canvasNodes = select(node) + .selectAll("canvas") + .nodes(); + const clonedCanvasNodes = select(clonedNode) + .selectAll("canvas") + .nodes(); + + for (const canvasNode of canvasNodes) { + const clonedCanvasNode = clonedCanvasNodes[canvasNodes.indexOf(canvasNode)]; + // Cloning the canvas element does not copy the content, so we need to + // manually copy it. + const ctx = clonedCanvasNode.getContext("2d") as CanvasRenderingContext2D; + ctx.drawImage(canvasNode, 0, 0); + } + wrapperNode.appendChild(clonedNode); modifyNode?.(clonedNode); From 90c905ff9699833ecdf6c3c6f843d6101deacd74 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Tue, 3 Dec 2024 10:45:37 +0100 Subject: [PATCH 04/33] feat: Allow the MenuActionItem to be disabled --- app/components/menu-action-item.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/components/menu-action-item.tsx b/app/components/menu-action-item.tsx index f9a116e80..c66aaf384 100644 --- a/app/components/menu-action-item.tsx +++ b/app/components/menu-action-item.tsx @@ -17,6 +17,7 @@ const StyledMenuItem = styled(MenuItem)(({ theme, color }) => ({ whiteSpace: "normal", })) as typeof MenuItem; export type MenuActionProps = { + disabled?: boolean; label: string | NonNullable; trailingIconName?: IconName; leadingIconName?: IconName; @@ -50,7 +51,7 @@ export type MenuActionProps = { export const MenuActionItem = ( props: MenuActionProps & { as: "menuitem" | "button" } ) => { - const { label, trailingIconName, leadingIconName } = props; + const { disabled, label, trailingIconName, leadingIconName } = props; const { isOpen: isConfirmationOpen, open: openConfirmation, @@ -97,6 +98,7 @@ export const MenuActionItem = ( return (