diff --git a/package-lock.json b/package-lock.json index 00df0a9bf..395458288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "axios": "^1.7.4", "dayjs": "1.11.13", "flag-icons": "7.3.2", + "html2canvas": "1.4.1", "i18next": "^24.2.2", "immutability-helper": "^3.1.1", "joi": "17.13.3", @@ -9384,6 +9385,15 @@ "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", "license": "MIT" }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -10446,6 +10456,15 @@ "tiny-invariant": "^1.0.6" } }, + "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==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -12444,6 +12463,19 @@ "void-elements": "3.1.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -17888,6 +17920,15 @@ "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" }, + "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==", + "license": "MIT", + "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", @@ -18418,6 +18459,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index 9df235cdd..5072650b8 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "axios": "^1.7.4", "dayjs": "1.11.13", "flag-icons": "7.3.2", + "html2canvas": "1.4.1", "i18next": "^24.2.2", "immutability-helper": "^3.1.1", "joi": "17.13.3", diff --git a/src/Components/ShareComponent/index.jsx b/src/Components/ShareComponent/index.jsx new file mode 100644 index 000000000..073ed2243 --- /dev/null +++ b/src/Components/ShareComponent/index.jsx @@ -0,0 +1,67 @@ +import html2canvas from "html2canvas"; +import ShareIcon from "@mui/icons-material/Share"; +import { Button } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => { + const theme = useTheme(); + const captureAndShare = async () => { + try { + // Temporarily apply styles directly to the element + const originalBackground = elementToCapture.current.style.background; + const originalPadding = elementToCapture.current.style.padding; + + elementToCapture.current.style.background = `radial-gradient(circle, ${theme.palette.gradient.color1}, ${theme.palette.gradient.color2}, ${theme.palette.gradient.color3}, ${theme.palette.gradient.color4}, ${theme.palette.gradient.color5})`; + elementToCapture.current.style.padding = `${theme.spacing(20)}`; + + // Capture the element directly + const canvas = await html2canvas(elementToCapture.current, { + useCORS: true, + scale: 2, + allowTaint: true, + backgroundColor: null, + }); + + // Restore original styles + elementToCapture.current.style.background = originalBackground; + elementToCapture.current.style.padding = originalPadding; + + const imageBlob = await new Promise((resolve) => + canvas.toBlob(resolve, "image/png") + ); + + const file = new File([imageBlob], `${fileName}.png`, { + type: "image/png", + }); + + if (navigator.share) { + await navigator.share({ + files: [file], + title: "Screenshot", + text: "Check out this screenshot!", + }); + } else { + const url = URL.createObjectURL(imageBlob); + const a = document.createElement("a"); + a.href = url; + a.download = `${fileName}.png`; + a.click(); + URL.revokeObjectURL(url); + } + } catch (error) { + console.error(error); + } + }; + return ( + + ); +}; + +export default ShareComponent; diff --git a/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx index 940b59351..7617666eb 100644 --- a/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx +++ b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx @@ -5,7 +5,6 @@ import { useTheme } from "@mui/material/styles"; import maplibregl from "maplibre-gl"; import { useSelector } from "react-redux"; import buildStyle from "./buildStyle"; -import { ColContainer } from "../../../../../Components/StandardContainer"; const DistributedUptimeMap = ({ width = "100%", checks }) => { const mapContainer = useRef(null); @@ -29,12 +28,17 @@ const DistributedUptimeMap = ({ width = "100%", checks }) => { useEffect(() => { if (mapContainer.current && !map.current) { const initialStyle = buildStyle(initialTheme.current, initialMode.current); + map.current = new maplibregl.Map({ container: mapContainer.current, style: initialStyle, center: [0, 20], zoom: 0.8, attributionControl: false, + canvasContextAttributes: { + antialias: true, + preserveDrawingBuffer: true, + }, }); } map.current.on("load", () => { diff --git a/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx b/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx index c5a861fca..5db44a559 100644 --- a/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx +++ b/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx @@ -4,19 +4,17 @@ import { Stack, Typography } from "@mui/material"; import PulseDot from "../../../../../Components/Animated/PulseDot"; import LastUpdate from "../LastUpdate"; import ChatBot from "../Chatbot"; +import ShareComponent from "../../../../../Components/ShareComponent"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -const StatusHeader = ({ monitor, connectionStatus }) => { - console.log(monitor); - +const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => { const theme = useTheme(); - const COLOR_MAP = { - up: theme.palette.distributedStatusHeader.up, - down: theme.palette.distributedStatusHeader.down, + up: theme.palette.successSecondary.main, + down: theme.palette.error.lowContrast, }; const MSG_MAP = { @@ -35,43 +33,51 @@ const StatusHeader = ({ monitor, connectionStatus }) => { - - - - + + + - {MSG_MAP[connectionStatus]} - + + {MSG_MAP[connectionStatus]} + + + Uptime: {(monitor.totalUptime * 100).toFixed(2)}% + + - Uptime: {(monitor.totalUptime * 100).toFixed(2)}% + Last updated{" "} + - - Last updated{" "} - - + diff --git a/src/Pages/DistributedUptimeStatus/Status/index.jsx b/src/Pages/DistributedUptimeStatus/Status/index.jsx index a714f6976..a1c6b473f 100644 --- a/src/Pages/DistributedUptimeStatus/Status/index.jsx +++ b/src/Pages/DistributedUptimeStatus/Status/index.jsx @@ -19,7 +19,7 @@ import StatusHeader from "../../DistributedUptime/Details/Components/StatusHeade //Utils import { useTheme } from "@mui/material/styles"; -import { useState } from "react"; +import { useState, useRef } from "react"; import { useParams } from "react-router-dom"; import { useSubscribeToDetails } from "../../DistributedUptime/Details/Hooks/useSubscribeToDetails"; import { useStatusPageFetchByUrl } from "./Hooks/useStatusPageFetchByUrl"; @@ -31,6 +31,7 @@ const DistributedUptimeStatus = () => { const location = useLocation(); const isPublic = location.pathname.startsWith("/status/distributed/public"); + const elementToCapture = useRef(null); // Local State const [dateRange, setDateRange] = useState("day"); const [isDeleteOpen, setIsDeleteOpen] = useState(false); @@ -135,9 +136,12 @@ const DistributedUptimeStatus = () => { return ( {!isPublic && } { ({ }, }, ], + height: 34, fontWeight: 400, borderRadius: 4, boxShadow: "none",