import React, { useLayoutEffect, useRef, useState } from "react"; import { newTextElement } from "@excalidraw/element"; import type { ChartType } from "@excalidraw/element/types"; import { trackEvent } from "../analytics"; import { isSpreadsheetValidForChartType, renderSpreadsheet } from "../charts"; import { t } from "../i18n"; import { exportToSvg } from "../scene/export"; import { useUIAppState } from "../context/ui-appState"; import { useApp } from "./App"; import { Dialog } from "./Dialog"; import "./PasteChartDialog.scss"; import { bucketFillIcon } from "./icons"; import type { ChartElements, Spreadsheet } from "../charts"; type OnPlainTextPaste = (rawText: string) => void; type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void; const getChartTypeLabel = (chartType: ChartType) => { switch (chartType) { case "bar": return t("labels.chartType_bar"); case "line": return t("labels.chartType_line"); case "radar": return t("labels.chartType_radar"); default: return chartType; } }; const ChartPreviewBtn = (props: { spreadsheet: Spreadsheet | null; chartType: ChartType; colorSeed: number; onClick: OnInsertChart; }) => { const previewRef = useRef(null); const [chartElements, setChartElements] = useState( null, ); const { theme } = useUIAppState(); useLayoutEffect(() => { if (!props.spreadsheet) { setChartElements(null); return; } const elements = renderSpreadsheet( props.chartType, props.spreadsheet, 0, 0, props.colorSeed, ); if (!elements) { setChartElements(null); previewRef.current?.replaceChildren(); return; } setChartElements(elements); let svg: SVGSVGElement; const previewNode = previewRef.current!; (async () => { svg = await exportToSvg( elements, { exportBackground: false, viewBackgroundColor: "#fff", exportWithDarkMode: theme === "dark", }, null, // files { skipInliningFonts: true, }, ); svg.querySelector(".style-fonts")?.remove(); previewNode.replaceChildren(); previewNode.appendChild(svg); })(); return () => { previewNode.replaceChildren(); }; }, [props.spreadsheet, props.chartType, props.colorSeed, theme]); const chartTypeLabel = getChartTypeLabel(props.chartType); return ( ); }; const PlainTextPreviewBtn = (props: { rawText: string; onClick: OnPlainTextPaste; }) => { const previewRef = useRef(null); const { theme } = useUIAppState(); useLayoutEffect(() => { if (!props.rawText) { return; } const textElement = newTextElement({ text: props.rawText, x: 0, y: 0, }); const previewNode = previewRef.current!; (async () => { const svg = await exportToSvg( [textElement], { exportBackground: false, viewBackgroundColor: "#fff", exportWithDarkMode: theme === "dark", }, null, { skipInliningFonts: true, }, ); svg.querySelector(".style-fonts")?.remove(); previewNode.replaceChildren(); previewNode.appendChild(svg); })(); return () => { previewNode.replaceChildren(); }; }, [props.rawText, theme]); return ( ); }; export const PasteChartDialog = ({ data, rawText, onClose, }: { data: Spreadsheet; rawText: string; onClose: () => void; }) => { const { onInsertElements, focusContainer } = useApp(); const [colorSeed, setColorSeed] = useState(Math.random()); const handleReshuffleColors = React.useCallback(() => { setColorSeed(Math.random()); }, []); const handleClose = React.useCallback(() => { if (onClose) { onClose(); } }, [onClose]); const handleChartClick = (chartType: ChartType, elements: ChartElements) => { onInsertElements(elements); trackEvent("paste", "chart", chartType); onClose(); focusContainer(); }; const handlePlainTextClick = (rawText: string) => { const textElement = newTextElement({ text: rawText, x: 0, y: 0, }); onInsertElements([textElement]); trackEvent("paste", "chart", "plaintext"); onClose(); focusContainer(); }; return (
{t("labels.pasteCharts")}
{ if (event.key === "Enter" || event.key === " ") { event.preventDefault(); handleReshuffleColors(); } }} > {bucketFillIcon}
} className={"PasteChartDialog"} autofocus={false} >
{(["bar", "line", "radar"] as const).map((chartType) => { if (!isSpreadsheetValidForChartType(data, chartType)) { return null; } return ( ); })} {rawText && ( )}
); };