import { GetStaticProps } from "next"; import { useCallback, useMemo, useRef, useState } from "react"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Button } from "components/Inputs/Button"; import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { ToolTip } from "components/ToolTip"; import { getOpenGraph } from "helpers/openGraph"; import { getLangui } from "graphql/fetchLocalData"; /* * ╭─────────────╮ * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── */ const SIZE_MULTIPLIER = 1000; /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppLayoutRequired {} const replaceSelection = ( text: string, selectionStart: number, selectionEnd: number, newSelectedText: string ) => text.substring(0, selectionStart) + newSelectedText + text.substring(selectionEnd); const swapChar = (char: string, swaps: string[]): string => { for (let index = 0; index < swaps.length; index++) { if (char === swaps[index]) { return swaps[(index + 1) % swaps.length]; } } return char; }; const Transcript = (props: Props): JSX.Element => { const [text, setText] = useState(""); const [fontSize, setFontSize] = useState(1); const [xOffset, setXOffset] = useState(0); const [lineIndex, setLineIndex] = useState(0); const textAreaRef = useRef(null); const updateDisplayedText = useCallback(() => { if (textAreaRef.current) { setText(textAreaRef.current.value); } }, []); const updateLineIndex = useCallback(() => { if (textAreaRef.current) { const subText = textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart); setLineIndex(subText.split("\n").length - 1); } }, []); const convertFullWidth = useCallback(() => { if (textAreaRef.current) { textAreaRef.current.value = textAreaRef.current.value // Numbers .replaceAll("0", "0") .replaceAll("1", "1") .replaceAll("2", "2") .replaceAll("3", "3") .replaceAll("4", "4") .replaceAll("5", "5") .replaceAll("6", "6") .replaceAll("7", "7") .replaceAll("8", "8") .replaceAll("9", "9") // Uppercase letters .replaceAll("A", "A") .replaceAll("B", "B") .replaceAll("C", "C") .replaceAll("D", "D") .replaceAll("E", "E") .replaceAll("F", "F") .replaceAll("G", "G") .replaceAll("H", "H") .replaceAll("I", "I") .replaceAll("J", "J") .replaceAll("K", "K") .replaceAll("L", "L") .replaceAll("M", "M") .replaceAll("N", "N") .replaceAll("O", "O") .replaceAll("P", "P") .replaceAll("Q", "Q") .replaceAll("R", "R") .replaceAll("S", "S") .replaceAll("T", "T") .replaceAll("U", "U") .replaceAll("V", "V") .replaceAll("W", "W") .replaceAll("X", "X") .replaceAll("Y", "Y") .replaceAll("Z", "Z") // Lowercase letters .replaceAll("a", "a") .replaceAll("b", "b") .replaceAll("c", "c") .replaceAll("d", "d") .replaceAll("e", "e") .replaceAll("f", "f") .replaceAll("g", "g") .replaceAll("h", "h") .replaceAll("i", "i") .replaceAll("j", "j") .replaceAll("k", "k") .replaceAll("l", "l") .replaceAll("m", "m") .replaceAll("n", "n") .replaceAll("o", "o") .replaceAll("p", "p") .replaceAll("q", "q") .replaceAll("r", "r") .replaceAll("s", "s") .replaceAll("t", "t") .replaceAll("u", "u") .replaceAll("v", "v") .replaceAll("w", "w") .replaceAll("x", "x") .replaceAll("y", "y") .replaceAll("z", "z") // Others .replaceAll(" ", " ") .replaceAll(",", ",") .replaceAll(".", ".") .replaceAll(":", ":") .replaceAll(";", ";") .replaceAll("!", "!") .replaceAll("?", "?") .replaceAll('"', """) .replaceAll("'", "'") .replaceAll("`", "`") .replaceAll("^", "^") .replaceAll("~", "~") .replaceAll("_", "_") .replaceAll("&", "&") .replaceAll("@", "@") .replaceAll("#", "#") .replaceAll("%", "%") .replaceAll("+", "+") .replaceAll("-", "-") .replaceAll("*", "*") .replaceAll("=", "=") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("(", "(") .replaceAll(")", ")") .replaceAll("[", "[") .replaceAll("]", "]") .replaceAll("{", "{") .replaceAll("}", "}") .replaceAll("|", "|") .replaceAll("$", "$") .replaceAll("£", "£") .replaceAll("¢", "¢") .replaceAll("₩", "₩") .replaceAll("¥", "¥"); updateDisplayedText(); } }, [updateDisplayedText]); const convertPunctuation = useCallback(() => { if (textAreaRef.current) { textAreaRef.current.value = textAreaRef.current.value .replaceAll("...", "⋯") .replaceAll("…", "⋯") .replaceAll(":::", "⋯⋯") .replaceAll(".", "。") .replaceAll(",", "、") .replaceAll("?", "?") .replaceAll("!", "!") .replaceAll(":", ":") .replaceAll("~", "~"); updateDisplayedText(); } }, [updateDisplayedText]); const toggleDakuten = useCallback(() => { if (textAreaRef.current) { const selectionStart = Math.min( textAreaRef.current.selectionStart, textAreaRef.current.selectionEnd ); const selectionEnd = Math.max( textAreaRef.current.selectionStart, textAreaRef.current.selectionEnd ); const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd); if (selection.length === 1) { let newSelection = selection; /* * Hiragana * a */ newSelection = swapChar(newSelection, ["か", "が"]); newSelection = swapChar(newSelection, ["さ", "ざ"]); newSelection = swapChar(newSelection, ["た", "だ"]); newSelection = swapChar(newSelection, ["は", "ば", "ぱ"]); // i newSelection = swapChar(newSelection, ["き", "ぎ"]); newSelection = swapChar(newSelection, ["し", "じ"]); newSelection = swapChar(newSelection, ["ち", "ぢ"]); newSelection = swapChar(newSelection, ["ひ", "び", "ぴ"]); // u newSelection = swapChar(newSelection, ["く", "ぐ"]); newSelection = swapChar(newSelection, ["す", "ず"]); newSelection = swapChar(newSelection, ["つ", "づ"]); newSelection = swapChar(newSelection, ["ふ", "ぶ", "ぷ"]); // e newSelection = swapChar(newSelection, ["け", "げ"]); newSelection = swapChar(newSelection, ["せ", "ぜ"]); newSelection = swapChar(newSelection, ["て", "で"]); newSelection = swapChar(newSelection, ["へ", "べ", "ぺ"]); // o newSelection = swapChar(newSelection, ["こ", "ご"]); newSelection = swapChar(newSelection, ["そ", "ぞ"]); newSelection = swapChar(newSelection, ["と", "ど"]); newSelection = swapChar(newSelection, ["ほ", "ぼ", "ぽ"]); // others newSelection = swapChar(newSelection, ["う", "ゔ"]); newSelection = swapChar(newSelection, ["ゝ", "ゞ"]); /* * Katakana * a */ newSelection = swapChar(newSelection, ["カ", "ガ"]); newSelection = swapChar(newSelection, ["サ", "ザ"]); newSelection = swapChar(newSelection, ["タ", "ダ"]); newSelection = swapChar(newSelection, ["ハ", "バ", "パ"]); // i newSelection = swapChar(newSelection, ["キ", "ギ"]); newSelection = swapChar(newSelection, ["シ", "ジ"]); newSelection = swapChar(newSelection, ["チ", "ヂ"]); newSelection = swapChar(newSelection, ["ヒ", "ビ", "ピ"]); // u newSelection = swapChar(newSelection, ["ク", "グ"]); newSelection = swapChar(newSelection, ["ス", "ズ"]); newSelection = swapChar(newSelection, ["ツ", "ヅ"]); newSelection = swapChar(newSelection, ["フ", "ブ", "プ"]); // e newSelection = swapChar(newSelection, ["ケ", "ゲ"]); newSelection = swapChar(newSelection, ["セ", "ゼ"]); newSelection = swapChar(newSelection, ["テ", "デ"]); newSelection = swapChar(newSelection, ["ヘ", "ベ", "ペ"]); // o newSelection = swapChar(newSelection, ["コ", "ゴ"]); newSelection = swapChar(newSelection, ["ソ", "ゾ"]); newSelection = swapChar(newSelection, ["ト", "ド"]); newSelection = swapChar(newSelection, ["ホ", "ボ", "ポ"]); // others newSelection = swapChar(newSelection, ["ゥ", "ヴ"]); newSelection = swapChar(newSelection, ["ヽ", "ヾ"]); if (newSelection !== selection) { textAreaRef.current.value = replaceSelection( textAreaRef.current.value, selectionStart, selectionEnd, newSelection ); textAreaRef.current.selectionStart = selectionStart; textAreaRef.current.selectionEnd = selectionEnd; textAreaRef.current.focus(); updateDisplayedText(); } } } }, [updateDisplayedText]); const toggleSmallForm = useCallback(() => { if (textAreaRef.current) { const selectionStart = Math.min( textAreaRef.current.selectionStart, textAreaRef.current.selectionEnd ); const selectionEnd = Math.max( textAreaRef.current.selectionStart, textAreaRef.current.selectionEnd ); const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd); if (selection.length === 1) { let newSelection = selection; // Hiragana newSelection = swapChar(newSelection, ["あ", "ぁ"]); newSelection = swapChar(newSelection, ["い", "ぃ"]); newSelection = swapChar(newSelection, ["う", "ぅ"]); newSelection = swapChar(newSelection, ["え", "ぇ"]); newSelection = swapChar(newSelection, ["お", "ぉ"]); newSelection = swapChar(newSelection, ["か", "ゕ"]); newSelection = swapChar(newSelection, ["け", "ゖ"]); newSelection = swapChar(newSelection, ["つ", "っ"]); newSelection = swapChar(newSelection, ["や", "ゃ"]); newSelection = swapChar(newSelection, ["ゆ", "ゅ"]); newSelection = swapChar(newSelection, ["よ", "ょ"]); newSelection = swapChar(newSelection, ["わ", "ゎ"]); // Katakana newSelection = swapChar(newSelection, ["ア", "ァ"]); newSelection = swapChar(newSelection, ["イ", "ィ"]); newSelection = swapChar(newSelection, ["ウ", "ゥ"]); newSelection = swapChar(newSelection, ["エ", "ェ"]); newSelection = swapChar(newSelection, ["オ", "ォ"]); newSelection = swapChar(newSelection, ["ツ", "ッ"]); newSelection = swapChar(newSelection, ["ヤ", "ャ"]); newSelection = swapChar(newSelection, ["ユ", "ュ"]); newSelection = swapChar(newSelection, ["ヨ", "ョ"]); if (newSelection !== selection) { textAreaRef.current.value = replaceSelection( textAreaRef.current.value, selectionStart, selectionEnd, newSelection ); textAreaRef.current.selectionStart = selectionStart; textAreaRef.current.selectionEnd = selectionEnd; textAreaRef.current.focus(); updateDisplayedText(); } } } }, [updateDisplayedText]); const insert = useCallback( (insertedText: string) => { if (textAreaRef.current) { const selectionEnd = Math.max( textAreaRef.current.selectionStart, textAreaRef.current.selectionEnd ); textAreaRef.current.value = replaceSelection( textAreaRef.current.value, selectionEnd, selectionEnd, insertedText ); textAreaRef.current.selectionStart = selectionEnd; textAreaRef.current.selectionEnd = selectionEnd + insertedText.length; textAreaRef.current.focus(); updateDisplayedText(); } }, [updateDisplayedText] ); const contentPanel = useMemo( () => (