import React, { FC, useEffect, useRef, useState } from "react"; import { ButtonSwitch, Info, Pair, PanelTab } from "emukit"; import { GameboyEmulator, bufferToDataUrl } from "../../../ts"; import "./serial-section.css"; const DEVICE_ICON: { [key: string]: string } = { Null: "🛑", Logger: "📜", Printer: "🖨️" }; export type LoggerCallback = (data: Uint8Array) => void; export type PrinterCallback = (imageBuffer: Uint8Array) => void; type SerialSectionProps = { emulator: GameboyEmulator; style?: string[]; onLogger?: (onLoggerData: LoggerCallback) => void; onPrinter?: (onPrinterData: PrinterCallback) => void; }; export const SerialSection: FC<SerialSectionProps> = ({ emulator, style = [], onLogger, onPrinter }) => { const classes = () => ["serial-section", ...style].join(" "); const [loggerData, setLoggerData] = useState<string>(); const [printerImageUrls, setPrinterImageUrls] = useState<string[]>(); const loggerDataRef = useRef<string[]>([]); const printerDataRef = useRef<string[]>([]); const loggerRef = useRef<HTMLDivElement>(null); const imagesRef = useRef<HTMLDivElement>(null); const onLoggerData = (data: Uint8Array) => { const byte = data[0]; const charByte = String.fromCharCode(byte); loggerDataRef.current.push(charByte); setLoggerData(loggerDataRef.current.join("")); }; const onPrinterData = (imageBuffer: Uint8Array) => { const imageUrl = bufferToDataUrl(imageBuffer, 160); printerDataRef.current.unshift(imageUrl); setPrinterImageUrls([...printerDataRef.current]); }; useEffect(() => { if (loggerRef.current) { onLogger && onLogger(onLoggerData); } }, [loggerRef, loggerRef.current]); useEffect(() => { if (imagesRef.current) { onPrinter && onPrinter(onPrinterData); } }, [imagesRef, imagesRef.current]); const onEngineChange = (option: string) => { switch (option) { case "Null": emulator.loadNullDevice(); break; case "Logger": emulator.loadLoggerDevice(); break; case "Printer": emulator.loadPrinterDevice(); break; } const optionIcon = DEVICE_ICON[option] ?? ""; emulator.handlers.showToast?.( `${optionIcon} ${option} attached to the serial port & active` ); }; const getTabs = () => { return [ <Info> <Pair key="button-device" name={"Device"} valueNode={ <ButtonSwitch options={["Null", "Logger", "Printer"]} size={"large"} style={["simple"]} onChange={onEngineChange} /> } /> <Pair key="baud-rate" name={"Baud Rate"} value={"1 KB/s"} /> </Info>, <div className="logger" ref={loggerRef}> <div className="logger-data"> {loggerData || "Logger contents are empty."} </div> </div>, <div className="printer" ref={imagesRef}> <div className="printer-lines"> {printerImageUrls ? ( printerImageUrls.map((url, index) => ( <img key={index} className="printer-line" src={url} /> )) ) : ( <span className="placeholder"> Printer contents are empty. </span> )} </div> </div> ]; }; const getTabNames = () => { return ["Settings", "Logger", "Printer"]; }; return ( <div className={classes()}> <PanelTab tabs={getTabs()} tabNames={getTabNames()} selectors={true} /> </div> ); }; export default SerialSection;