mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 14:49:58 -07:00
website: snapshot
This commit is contained in:
@@ -1,37 +1,111 @@
|
||||
import { ios, canShare } from "../utils/env.js";
|
||||
import { domToBlob } from "../modules/modern-screenshot/4.6.7/dist/index.mjs";
|
||||
import { style } from "../utils/elements.js";
|
||||
import { colors } from "./colors.js";
|
||||
|
||||
export const canCapture = !ios || canShare;
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Element} args.element
|
||||
* @param {string} args.name
|
||||
* @param {HTMLCanvasElement} args.screenshot
|
||||
* @param {number} args.chartWidth
|
||||
* @param {HTMLElement} args.parent
|
||||
* @param {{ top: { element: HTMLElement }, bottom: { element: HTMLElement } }} args.legends
|
||||
*/
|
||||
export async function capture({ element, name }) {
|
||||
const blob = await domToBlob(element, {
|
||||
scale: 2,
|
||||
});
|
||||
export function capture({ screenshot, chartWidth, parent, legends }) {
|
||||
const dpr = screenshot.width / chartWidth;
|
||||
const pad = Math.round(16 * dpr);
|
||||
const fontSize = Math.round(14 * dpr);
|
||||
const titleFontSize = Math.round(20 * dpr);
|
||||
const circleRadius = Math.round(5 * dpr);
|
||||
const legendHeight = Math.round(28 * dpr);
|
||||
const titleHeight = Math.round(36 * dpr);
|
||||
|
||||
if (ios) {
|
||||
const file = new File(
|
||||
[blob],
|
||||
`bitview-${name}-${new Date().toJSON().split(".")[0]}.png`,
|
||||
{ type: "image/png" },
|
||||
);
|
||||
const title = (parent.querySelector("h1")?.textContent ?? "").toUpperCase();
|
||||
const hasTitle = title.length > 0;
|
||||
const hasTopLegend = legends.top.element.children.length > 0;
|
||||
const hasBottomLegend = legends.bottom.element.children.length > 0;
|
||||
const titleOffset = hasTitle ? titleHeight : 0;
|
||||
const topLegendOffset = hasTopLegend ? legendHeight : 0;
|
||||
const bottomOffset = hasBottomLegend ? legendHeight : 0;
|
||||
|
||||
try {
|
||||
await navigator.share({
|
||||
files: [file],
|
||||
title: `${name} on ${window.document.location.hostname}`,
|
||||
});
|
||||
return;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = screenshot.width + pad * 2;
|
||||
canvas.height =
|
||||
screenshot.height + pad * 2 + titleOffset + topLegendOffset + bottomOffset;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
// Background
|
||||
const bodyBg = getComputedStyle(document.body).backgroundColor;
|
||||
const htmlBg = getComputedStyle(document.documentElement).backgroundColor;
|
||||
ctx.fillStyle = bodyBg === "rgba(0, 0, 0, 0)" ? htmlBg : bodyBg;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
/** @param {HTMLElement} legendEl @param {number} y */
|
||||
const drawLegend = (legendEl, y) => {
|
||||
ctx.font = `${fontSize}px ${style.fontFamily}`;
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
let x = pad;
|
||||
for (const div of legendEl.children) {
|
||||
const label = div.querySelector("label");
|
||||
if (!label) continue;
|
||||
const input = label.querySelector("input");
|
||||
if (input && !input.checked) continue;
|
||||
// Draw color circles
|
||||
const colorSpans = label.querySelectorAll(".colors span");
|
||||
for (const span of colorSpans) {
|
||||
ctx.fillStyle = /** @type {HTMLElement} */ (span).style.backgroundColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + circleRadius, y, circleRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
x += circleRadius * 2 + Math.round(2 * dpr);
|
||||
}
|
||||
// Draw name
|
||||
const name = label.querySelector(".name")?.textContent ?? "";
|
||||
ctx.fillStyle = colors.default();
|
||||
ctx.fillText(name, x + Math.round(4 * dpr), y);
|
||||
x += ctx.measureText(name).width + Math.round(20 * dpr);
|
||||
}
|
||||
};
|
||||
|
||||
// Title
|
||||
if (hasTitle) {
|
||||
ctx.font = `${titleFontSize}px ${style.fontFamily}`;
|
||||
ctx.fillStyle = colors.default();
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(title, pad, pad + titleHeight / 2);
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, "_blank");
|
||||
setTimeout(() => URL.revokeObjectURL(url), 100);
|
||||
// Top legend
|
||||
if (hasTopLegend) {
|
||||
drawLegend(legends.top.element, pad + titleOffset + topLegendOffset / 2);
|
||||
}
|
||||
|
||||
// Chart
|
||||
ctx.drawImage(screenshot, pad, pad + titleOffset + topLegendOffset);
|
||||
|
||||
// Bottom legend
|
||||
if (hasBottomLegend) {
|
||||
drawLegend(
|
||||
legends.bottom.element,
|
||||
pad + titleOffset + topLegendOffset + screenshot.height + legendHeight / 2,
|
||||
);
|
||||
}
|
||||
|
||||
// Watermark
|
||||
ctx.fillStyle = colors.gray();
|
||||
ctx.font = `${fontSize}px ${style.fontFamily}`;
|
||||
ctx.textAlign = "right";
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.fillText(window.location.host, canvas.width - pad, canvas.height - pad / 2);
|
||||
|
||||
// Open in new tab
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return;
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, "_blank");
|
||||
setTimeout(() => URL.revokeObjectURL(url), 100);
|
||||
}, "image/png");
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
// } from "../modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.development.mjs";
|
||||
} from "../modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.mjs";
|
||||
import { createLegend } from "./legend.js";
|
||||
import { capture, canCapture } from "./capture.js";
|
||||
import { capture } from "./capture.js";
|
||||
import { colors } from "./colors.js";
|
||||
import { createChoiceField } from "../utils/dom.js";
|
||||
import { createPersistedValue } from "../utils/persisted.js";
|
||||
@@ -76,14 +76,12 @@ const lineWidth = /** @type {any} */ (1.5);
|
||||
* @param {HTMLElement} args.parent
|
||||
* @param {BrkClient} args.brk
|
||||
* @param {true} [args.fitContent]
|
||||
* @param {HTMLElement} [args.captureElement]
|
||||
*/
|
||||
export function createChart({
|
||||
parent,
|
||||
id: chartId,
|
||||
brk,
|
||||
fitContent,
|
||||
captureElement,
|
||||
}) {
|
||||
const baseUrl = brk.baseUrl.replace(/\/$/, "");
|
||||
|
||||
@@ -1513,33 +1511,19 @@ export function createChart({
|
||||
},
|
||||
};
|
||||
|
||||
if (captureElement && canCapture) {
|
||||
const domain = document.createElement("p");
|
||||
domain.innerText = window.location.host;
|
||||
domain.id = "domain";
|
||||
|
||||
fieldsets.addIfNeeded({
|
||||
id: "capture",
|
||||
paneIndex: 0,
|
||||
position: "ne",
|
||||
createChild() {
|
||||
const button = document.createElement("button");
|
||||
button.id = "capture";
|
||||
button.innerText = "capture";
|
||||
button.title = "Capture chart as image";
|
||||
button.addEventListener("click", async () => {
|
||||
captureElement.dataset.screenshot = "true";
|
||||
captureElement.append(domain);
|
||||
try {
|
||||
await capture({ element: captureElement, name: chartId });
|
||||
} catch {}
|
||||
captureElement.removeChild(domain);
|
||||
captureElement.dataset.screenshot = "false";
|
||||
});
|
||||
return button;
|
||||
},
|
||||
const captureButton = document.createElement("button");
|
||||
captureButton.className = "capture";
|
||||
captureButton.innerText = "capture";
|
||||
captureButton.title = "Capture chart as image";
|
||||
captureButton.addEventListener("click", () => {
|
||||
capture({
|
||||
screenshot: ichart.takeScreenshot(),
|
||||
chartWidth: elements.chart.clientWidth,
|
||||
parent,
|
||||
legends,
|
||||
});
|
||||
}
|
||||
});
|
||||
elements.chart.append(captureButton);
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user