+
{
+ nodeToImage(configRef.current, props)
+ },
+ []
+ )
+
return {
fields,
settings,
@@ -96,6 +110,8 @@ export function useConfigMain() {
showAdvanced,
setShowAdvanced,
remoteError,
+ takeScreenshot,
+ configRef,
}
}
diff --git a/apps/hostd/contexts/metrics/useNowAtInterval.tsx b/apps/hostd/contexts/metrics/useNowAtInterval.tsx
index 2dcc4223c..1ff91aa93 100644
--- a/apps/hostd/contexts/metrics/useNowAtInterval.tsx
+++ b/apps/hostd/contexts/metrics/useNowAtInterval.tsx
@@ -9,7 +9,6 @@ export function useNowAtInterval(dataInterval: DataInterval) {
setNow(new Date().getTime())
const i = setInterval(() => {
setNow(new Date().getTime())
- console.log('reset time range')
}, getDataIntervalInMs(dataInterval))
return () => clearInterval(i)
}, [dataInterval])
diff --git a/apps/renterd/components/Config/ConfigActions.tsx b/apps/renterd/components/Config/ConfigActions.tsx
index 59e6c1dbb..43d3c66d6 100644
--- a/apps/renterd/components/Config/ConfigActions.tsx
+++ b/apps/renterd/components/Config/ConfigActions.tsx
@@ -9,6 +9,7 @@ import {
} from '@siafoundation/design-system'
import { Reset16, Save16, Settings16 } from '@siafoundation/react-icons'
import { useConfig } from '../../contexts/config'
+import { ConfigContextMenu } from './ConfigContextMenu'
export function ConfigActions() {
const {
@@ -72,6 +73,7 @@ export function ConfigActions() {
+
)
}
diff --git a/apps/renterd/components/Config/ConfigContextMenu.tsx b/apps/renterd/components/Config/ConfigContextMenu.tsx
new file mode 100644
index 000000000..efdd7e37e
--- /dev/null
+++ b/apps/renterd/components/Config/ConfigContextMenu.tsx
@@ -0,0 +1,49 @@
+import {
+ DropdownMenu,
+ DropdownMenuItem,
+ Button,
+ DropdownMenuLeftSlot,
+ DropdownMenuLabel,
+} from '@siafoundation/design-system'
+import {
+ Copy16,
+ Download16,
+ OverflowMenuHorizontal16,
+} from '@siafoundation/react-icons'
+import { useConfig } from '../../contexts/config'
+
+export function ConfigContextMenu() {
+ const { takeScreenshot } = useConfig()
+ return (
+
+
{
+ nodeToImage(configRef.current, props)
+ },
+ []
+ )
+
return {
onSubmit,
revalidateAndResetForm,
@@ -215,6 +229,8 @@ export function useConfigMain() {
showAdvanced,
setShowAdvanced,
remoteError,
+ configRef,
+ takeScreenshot,
}
}
diff --git a/libs/design-system/package.json b/libs/design-system/package.json
index b354dd92d..5521b60e1 100644
--- a/libs/design-system/package.json
+++ b/libs/design-system/package.json
@@ -50,6 +50,7 @@
"clipboard-polyfill": "^4.0.1",
"@visx/xychart": "^2.18.0",
"react-dropzone": "^14.2.3",
+ "html-to-image": "^1.11.11",
"react-number-format": "^5.3.1",
"@radix-ui/react-radio-group": "^1.0.0",
"@radix-ui/react-accordion": "^1.0.0",
diff --git a/libs/design-system/src/index.ts b/libs/design-system/src/index.ts
index 7bf296ac6..c58d506c0 100644
--- a/libs/design-system/src/index.ts
+++ b/libs/design-system/src/index.ts
@@ -177,5 +177,6 @@ export * from './lib/chartStats'
export * from './lib/ipRegex'
export * from './lib/getOs'
export * from './lib/countryEmoji'
+export * from './lib/nodeToImage'
export { colors } from './config/colors'
diff --git a/libs/design-system/src/lib/clipboard.ts b/libs/design-system/src/lib/clipboard.ts
index d244cd440..e088403cc 100644
--- a/libs/design-system/src/lib/clipboard.ts
+++ b/libs/design-system/src/lib/clipboard.ts
@@ -1,5 +1,5 @@
import React from 'react'
-import { writeText } from 'clipboard-polyfill'
+import { writeText, write } from 'clipboard-polyfill'
import { ToastOptions, triggerToast, triggerToastNode } from './toast'
export const copyToClipboard = (text: string, entity?: string) => {
@@ -10,6 +10,18 @@ export const copyToClipboard = (text: string, entity?: string) => {
writeText(text)
}
+export const copyImageToClipboard = (
+ image: Blob,
+ type: string,
+ entity?: string
+) => {
+ const message = entity
+ ? `Copied ${entity} to clipboard`
+ : 'Copied to clipboard'
+ triggerToast(message)
+ write([new ClipboardItem({ [type]: image })])
+}
+
export const copyToClipboardCustom = (
text: string,
message: React.ReactNode,
diff --git a/libs/design-system/src/lib/nodeToImage.tsx b/libs/design-system/src/lib/nodeToImage.tsx
new file mode 100644
index 000000000..ce39bd751
--- /dev/null
+++ b/libs/design-system/src/lib/nodeToImage.tsx
@@ -0,0 +1,33 @@
+import * as htmlToImage from 'html-to-image'
+import { copyImageToClipboard } from './clipboard'
+
+export async function nodeToImage(
+ node: HTMLElement,
+ {
+ name,
+ quality,
+ copy,
+ download,
+ }: {
+ name: string
+ quality?: number
+ copy?: boolean
+ download?: boolean
+ }
+) {
+ if (!node) {
+ throw Error('HTML node required')
+ }
+ const dataUrl = await htmlToImage.toPng(node, { quality: quality || 0.5 })
+ if (download) {
+ const link = document.createElement('a')
+ link.download = `${name}.png`
+ link.href = dataUrl
+ link.click()
+ }
+ if (copy) {
+ const fetchResponse = await fetch(dataUrl)
+ const blob = await fetchResponse.blob()
+ copyImageToClipboard(blob, 'image/png', name)
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 0f2ec5dec..e65421cb2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -75,6 +75,7 @@
"formik": "^2.2.9",
"framer-motion": "^7.6.5",
"gray-matter": "^4.0.3",
+ "html-to-image": "^1.11.11",
"identicon.js": "^2.3.3",
"jest-environment-jsdom": "29.4.3",
"lowdb": "^3.0.0",
@@ -302,11 +303,12 @@
"date-fns": "^2.28.0",
"formik": "^2.2.9",
"framer-motion": "^7.6.5",
+ "html-to-image": "^1.11.11",
"next-themes": "^0.2.1",
- "react-currency-input-field": "^3.6.5",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.2.0",
"react-idle-timer": "^5.7.2",
+ "react-number-format": "^5.3.1",
"react-qr-code": "^2.0.7",
"yup": "^0.32.11"
},
@@ -15824,6 +15826,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-deceiver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
@@ -22334,14 +22341,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-currency-input-field": {
- "version": "3.6.5",
- "resolved": "https://registry.npmjs.org/react-currency-input-field/-/react-currency-input-field-3.6.5.tgz",
- "integrity": "sha512-tP62WFAhkVv0RGOQVGEPE3MfgciQ4gkiASlmLBDu6tdfMC3jFhIkLU1uqUy3glUvRHyT4avnX/YVVbbXKHLtnA==",
- "peerDependencies": {
- "react": "^16.9.0 || ^17.0.0 || ^18.0.0"
- }
- },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -33295,11 +33294,12 @@
"date-fns": "^2.28.0",
"formik": "^2.2.9",
"framer-motion": "^7.6.5",
+ "html-to-image": "^1.11.11",
"next-themes": "^0.2.1",
- "react-currency-input-field": "^3.6.5",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.2.0",
"react-idle-timer": "^5.7.2",
+ "react-number-format": "^5.3.1",
"react-qr-code": "^2.0.7",
"yup": "^0.32.11"
}
@@ -39596,6 +39596,11 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
+ "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=="
+ },
"http-deceiver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
@@ -44138,12 +44143,6 @@
"loose-envify": "^1.1.0"
}
},
- "react-currency-input-field": {
- "version": "3.6.5",
- "resolved": "https://registry.npmjs.org/react-currency-input-field/-/react-currency-input-field-3.6.5.tgz",
- "integrity": "sha512-tP62WFAhkVv0RGOQVGEPE3MfgciQ4gkiASlmLBDu6tdfMC3jFhIkLU1uqUy3glUvRHyT4avnX/YVVbbXKHLtnA==",
- "requires": {}
- },
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
diff --git a/package.json b/package.json
index 6e14938ac..5c0f1c5d0 100644
--- a/package.json
+++ b/package.json
@@ -87,6 +87,7 @@
"formik": "^2.2.9",
"framer-motion": "^7.6.5",
"gray-matter": "^4.0.3",
+ "html-to-image": "^1.11.11",
"identicon.js": "^2.3.3",
"jest-environment-jsdom": "29.4.3",
"lowdb": "^3.0.0",