import { h } from 'preact'; import cs from 'classnames'; import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { Apple2 as Apple2Impl } from '../apple2'; import { ControlStrip } from './ControlStrip'; import { Debugger } from './debugger/Debugger'; import { ErrorModal } from './ErrorModal'; import { Inset } from './Inset'; import { Keyboard } from './Keyboard'; import { LanguageCard } from './LanguageCard'; import { Mouse } from './Mouse'; import { Screen } from './Screen'; import { Drives } from './Drives'; import { Slinky } from './Slinky'; import { ThunderClock } from './ThunderClock'; import { Videoterm } from './Videoterm'; import { spawn, Ready } from './util/promises'; import styles from './css/Apple2.module.css'; declare global { interface Window { apple2: Apple2Impl; } } /** * Interface for the Apple2 component. */ export interface Apple2Props { characterRom: string; enhanced: boolean; e: boolean; gl: boolean; rom: string; sectors: number; } /** * Component to bind various UI components together to form * the application layout. Includes the screen, drives, * emulator controls and keyboard. Bootstraps the core * Apple2 emulator. * * @param props Apple2 initialization props * @returns */ export const Apple2 = (props: Apple2Props) => { const { e, enhanced, sectors } = props; const screenRef = useRef(null); const [apple2, setApple2] = useState(); const [error, setError] = useState(); const [ready, setReady] = useState(false); const [showDebug, setShowDebug] = useState(false); const drivesReady = useMemo(() => new Ready(setError), []); const io = apple2?.getIO(); const cpu = apple2?.getCPU(); const vm = apple2?.getVideoModes(); const rom = apple2?.getROM(); const doPaste = useCallback((event: Event) => { if ( (document.activeElement !== screenRef.current) && (document.activeElement !== document.body) ) { return; } if (io) { const paste = (event.clipboardData || window.clipboardData)?.getData('text'); if (paste) { io.setKeyBuffer(paste); } } event.preventDefault(); }, [io]); const doCopy = useCallback((event: Event) => { if ( (document.activeElement !== screenRef.current) && (document.activeElement !== document.body) ) { return; } if (vm) { event.clipboardData?.setData('text/plain', vm.getText()); } event.preventDefault(); }, [vm]); useEffect(() => { if (screenRef.current) { const options = { canvas: screenRef.current, tick: () => { /* do nothing */ }, ...props, }; const apple2 = new Apple2Impl(options); const controller = spawn(async (signal) => { try { await apple2.ready; if (signal.aborted) { return; } setApple2(apple2); await drivesReady.ready; if (signal.aborted) { setApple2(undefined); return; } apple2.reset(); apple2.run(); } catch (e) { setError(e); } setReady(true); }); window.apple2 = apple2; return () => controller.abort(); } }, [props, drivesReady]); useEffect(() => { const { current } = screenRef; window.addEventListener('paste', doPaste); window.addEventListener('copy', doCopy); current?.addEventListener('paste', doPaste); current?.addEventListener('copy', doCopy); return () => { window.removeEventListener('paste', doPaste); window.removeEventListener('copy', doCopy); current?.removeEventListener('paste', doPaste); current?.removeEventListener('copy', doCopy); }; }, [doCopy, doPaste]); const toggleDebugger = useCallback(() => { setShowDebug((on) => !on); }, []); return (
{!e ? : null} {!e ? : null}
{showDebug ? : null}
); };