apple2js/js/components/Apple2.tsx

171 lines
5.3 KiB
TypeScript
Raw Normal View History

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';
2022-07-14 03:34:50 +00:00
import { Debugger } from './debugger/Debugger';
import { ErrorModal } from './ErrorModal';
import { Inset } from './Inset';
import { Keyboard } from './Keyboard';
2022-07-10 14:58:29 +00:00
import { LanguageCard } from './LanguageCard';
2022-05-12 00:21:21 +00:00
import { Mouse } from './Mouse';
import { Screen } from './Screen';
import { Drives } from './Drives';
2022-05-12 14:59:12 +00:00
import { Slinky } from './Slinky';
import { ThunderClock } from './ThunderClock';
2022-06-16 01:44:58 +00:00
import { Videoterm } from './Videoterm';
import { spawn, Ready } from './util/promises';
import styles from './css/Apple2.module.css';
2022-07-06 21:00:18 +00:00
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<HTMLCanvasElement>(null);
const [apple2, setApple2] = useState<Apple2Impl>();
const [error, setError] = useState<unknown>();
const [ready, setReady] = useState(false);
2022-07-23 19:32:40 +00:00
const [showDebug, setShowDebug] = useState(false);
const drivesReady = useMemo(() => new Ready(setError), []);
2022-07-06 21:00:18 +00:00
const io = apple2?.getIO();
const cpu = apple2?.getCPU();
const vm = apple2?.getVideoModes();
2022-07-10 14:58:29 +00:00
const rom = apple2?.getROM();
2022-07-06 21:00:18 +00:00
const doPaste = useCallback((event: Event) => {
if (
(document.activeElement !== screenRef.current) &&
(document.activeElement !== document.body)
) {
return;
}
2022-07-06 21:00:18 +00:00
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;
}
2022-07-06 21:00:18 +00:00
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);
});
2022-07-06 21:00:18 +00:00
window.apple2 = apple2;
return () => controller.abort();
}
}, [props, drivesReady]);
2022-07-06 21:00:18 +00:00
useEffect(() => {
const { current } = screenRef;
2022-07-06 21:00:18 +00:00
window.addEventListener('paste', doPaste);
window.addEventListener('copy', doCopy);
current?.addEventListener('paste', doPaste);
current?.addEventListener('copy', doCopy);
2022-07-06 21:00:18 +00:00
return () => {
window.removeEventListener('paste', doPaste);
window.removeEventListener('copy', doCopy);
current?.removeEventListener('paste', doPaste);
current?.removeEventListener('copy', doCopy);
2022-07-06 21:00:18 +00:00
};
}, [doCopy, doPaste]);
const toggleDebugger = useCallback(() => {
setShowDebug((on) => !on);
}, []);
return (
<div className={styles.container}>
<div
className={cs(styles.outer, { apple2e: e, [styles.ready]: ready })}
>
<Screen screenRef={screenRef} />
2022-07-10 14:58:29 +00:00
{!e ? <LanguageCard cpu={cpu} io={io} rom={rom} slot={0} /> : null}
<Slinky io={io} slot={2} />
{!e ? <Videoterm io={io} slot={3} /> : null}
<Mouse cpu={cpu} screenRef={screenRef} io={io} slot={4} />
<ThunderClock io={io} slot={5} />
<Inset>
<Drives cpu={cpu} io={io} sectors={sectors} enhanced={enhanced} ready={drivesReady} />
</Inset>
<ControlStrip apple2={apple2} e={e} toggleDebugger={toggleDebugger} />
<Inset>
<Keyboard apple2={apple2} e={e} />
</Inset>
<ErrorModal error={error} setError={setError} />
</div>
2022-07-14 03:34:50 +00:00
{showDebug ? <Debugger apple2={apple2} /> : null}
</div>
);
};