From d043d8d31cec5ce741362bfb1dfb08d91d7a0a42 Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Thu, 3 Oct 2024 18:29:58 +0800 Subject: [PATCH] Use Tooltip for evaluation result copying Displaying a dialog is too visually obtrusive for such a small action. --- src/Components/Main.tsx | 40 ++++++++++++++++++++++----------- src/Components/Tooltip.tsx | 14 +++++++----- src/Components/TooltipLabel.tsx | 5 ++++- src/utils.tsx | 32 -------------------------- 4 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/Components/Main.tsx b/src/Components/Main.tsx index a21c944..b9b6b99 100644 --- a/src/Components/Main.tsx +++ b/src/Components/Main.tsx @@ -13,7 +13,7 @@ import { allOptions, defaultArticle } from "../consts"; import evaluate from "../evaluate"; import { listenArticle } from "../options"; import initialState, { stateStorageLocation } from "../state"; -import { copy, notifyError } from "../utils"; +import TooltipLabel from "./TooltipLabel"; import type { MainState, Option, ReactNode } from "../consts"; @@ -176,11 +176,19 @@ export default function Main({ evaluateHandlerRef }: { evaluateHandlerRef: Mutab const [loading, setLoading] = useState(false); - const handleCopy = useCallback(() => { - const txt = ref.current.textContent?.trim(); - if (txt) copy(txt); - else notifyError("請先進行操作,再匯出結果"); + const [copyTooltipText, setCopyTooltipText] = useState("複製到剪貼簿"); + const copyEvaluationResult = useCallback(async () => { + const content = ref.current.textContent?.trim(); + if (content) { + try { + await navigator.clipboard.writeText(content); + setCopyTooltipText("成功複製到剪貼簿"); + } catch { + setCopyTooltipText("無法複製到剪貼簿"); + } + } }, []); + const onHideTooltip = useCallback(() => setCopyTooltipText("複製到剪貼簿"), []); // XXX Please Rewrite useEffect(() => { @@ -287,14 +295,20 @@ export default function Main({ evaluateHandlerRef }: { evaluateHandlerRef: Mutab <span>推導結果</span> - <CopyButton title="匯出至剪貼簿" hidden={loading} onClick={handleCopy}> - <FontAwesomeIcon icon={faCopy} size="sm" /> - </CopyButton> - <form method="dialog"> - <CloseButton type="submit" className="swal2-close" title="關閉" hidden={loading}> - × - </CloseButton> - </form> + {!loading && ( + <> + <TooltipLabel description={copyTooltipText} onHideTooltip={onHideTooltip}> + <CopyButton onClick={copyEvaluationResult}> + <FontAwesomeIcon icon={faCopy} size="sm" /> + </CopyButton> + </TooltipLabel> + <form method="dialog"> + <CloseButton type="submit" className="swal2-close" title="關閉"> + × + </CloseButton> + </form> + </> + )} {evaluationResult} diff --git a/src/Components/Tooltip.tsx b/src/Components/Tooltip.tsx index 4e56caf..581e828 100644 --- a/src/Components/Tooltip.tsx +++ b/src/Components/Tooltip.tsx @@ -40,10 +40,6 @@ const root = createRoot(div); let tooltipTarget: symbol | null = null; -function hideTooltip() { - div.style.visibility = "hidden"; -} - function TooltipAnchor({ relativeToNodeBox, children, @@ -80,10 +76,12 @@ export default function Tooltip({ element, children, fixedWidth = true, + onHideTooltip, }: { element: ReactElement; children: ReactElement; fixedWidth?: boolean; + onHideTooltip?: (() => void) | undefined; }) { const selfRef = useRef(Symbol("Tooltip")); const boxRef = useRef(null); @@ -109,14 +107,20 @@ export default function Tooltip({ renderTooltip(); } }, [renderTooltip]); + + const hideTooltip = useCallback(() => { + div.style.visibility = "hidden"; + onHideTooltip?.(); + }, [onHideTooltip]); useEffect( () => () => { if (tooltipTarget === selfRef.current) { hideTooltip(); } }, - [], + [hideTooltip], ); + return cloneElement(children, { onMouseEnter: showTooltip, onTouchStart: showTooltip, diff --git a/src/Components/TooltipLabel.tsx b/src/Components/TooltipLabel.tsx index 359c6a6..1c10a58 100644 --- a/src/Components/TooltipLabel.tsx +++ b/src/Components/TooltipLabel.tsx @@ -22,9 +22,11 @@ const Option = styled.label` export default function TooltipLabel({ description, children, + onHideTooltip, }: { description?: string | undefined; children: ReactNode; + onHideTooltip?: (() => void) | undefined; }) { return typeof description === "string" && description ? ( {line}

))} - }> + } + onHideTooltip={onHideTooltip}>
) : ( diff --git a/src/utils.tsx b/src/utils.tsx index 13488fe..81439ad 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -53,38 +53,6 @@ export function notifyError(msg: string, err?: unknown) { return new Error(msg, err instanceof Error ? { cause: err } : {}); } -export async function copy(txt: string) { - if ( - await (async () => { - try { - await navigator.clipboard.writeText(txt); - return true; - } catch { - const textArea = document.createElement("textarea"); - textArea.value = txt; - textArea.style.position = "fixed"; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - try { - return document.execCommand("copy"); - } catch { - return false; - } finally { - document.body.removeChild(textArea); - } - } - })() - ) - Swal.fire({ - icon: "success", - title: "成功", - text: "已成功匯出至剪貼簿", - confirmButtonText: "確定", - }); - else notifyError("瀏覽器不支援匯出至剪貼簿,操作失敗"); -} - export async function fetchFile(input: string) { try { const text = await (await fetch(input, { cache: "no-cache" })).text();