mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Preact printer (#129)
This commit is contained in:
parent
c9b7987c4c
commit
9686eda3a8
@ -1,4 +1,7 @@
|
||||
import { h, JSX } from 'preact';
|
||||
import cs from 'classnames';
|
||||
|
||||
import styles from './css/ControlButton.module.css';
|
||||
|
||||
/**
|
||||
* Interface for ControlButton.
|
||||
@ -7,6 +10,7 @@ export interface ControlButtonProps {
|
||||
icon: string;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
onClick: JSX.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
@ -18,8 +22,8 @@ export interface ControlButtonProps {
|
||||
* @param onClick Click callback
|
||||
* @returns Control Button component
|
||||
*/
|
||||
export const ControlButton = ({ icon, title, onClick, ...props }: ControlButtonProps) => (
|
||||
export const ControlButton = ({ active, icon, title, onClick, ...props }: ControlButtonProps) => (
|
||||
<button onClick={onClick} title={title} {...props} >
|
||||
<i className={`fas fa-${icon}`}></i>
|
||||
<i className={cs('fas', `fa-${icon}`, { [styles.active]: active })}></i>
|
||||
</button>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import { AudioControl } from './AudioControl';
|
||||
import { OptionsModal} from './OptionsModal';
|
||||
import { OptionsContext } from './OptionsContext';
|
||||
import { PauseControl } from './PauseControl';
|
||||
import { Printer } from './Printer';
|
||||
import { ControlButton } from './ControlButton';
|
||||
import { Apple2 as Apple2Impl } from '../apple2';
|
||||
import { JoyStick } from '../ui/joystick';
|
||||
@ -14,6 +15,7 @@ import { Screen, SCREEN_FULL_PAGE } from '../ui/screen';
|
||||
import { System } from '../ui/system';
|
||||
|
||||
import styles from './css/ControlStrip.module.css';
|
||||
import Apple2IO from 'js/apple2io';
|
||||
|
||||
const README = 'https://github.com/whscullin/apple2js#readme';
|
||||
|
||||
@ -33,12 +35,14 @@ interface ControlStripProps {
|
||||
*/
|
||||
export const ControlStrip = ({ apple2, e }: ControlStripProps) => {
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
const [io, setIO] = useState<Apple2IO>();
|
||||
const options = useContext(OptionsContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (apple2) {
|
||||
const io = apple2.getIO();
|
||||
const vm = apple2.getVideoModes();
|
||||
setIO(io);
|
||||
|
||||
const system = new System(io, e);
|
||||
options.addOptions(system);
|
||||
@ -85,6 +89,7 @@ export const ControlStrip = ({ apple2, e }: ControlStripProps) => {
|
||||
<CPUMeter apple2={apple2} />
|
||||
<PauseControl apple2={apple2} />
|
||||
<AudioControl apple2={apple2} />
|
||||
<Printer io={io} slot={1} />
|
||||
<div style={{flexGrow: 1}} />
|
||||
<ControlButton onClick={doReadme} title="About" icon="info" />
|
||||
<ControlButton onClick={doShowOptions} title="Options (F4)" icon="cog" />
|
||||
|
107
js/components/Printer.tsx
Normal file
107
js/components/Printer.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { h, Fragment } from 'preact';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import Apple2IO, { slot } from 'js/apple2io';
|
||||
import Parallel, { ParallelOptions } from 'js/cards/parallel';
|
||||
import { Modal, ModalContent, ModalFooter } from './Modal';
|
||||
|
||||
import styles from './css/Printer.module.css';
|
||||
import { ControlButton } from './ControlButton';
|
||||
import { byte } from 'js/types';
|
||||
|
||||
export interface PrinterProps {
|
||||
io: Apple2IO | undefined;
|
||||
slot: slot;
|
||||
}
|
||||
|
||||
export const Printer = ({ io, slot }: PrinterProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [content, setContent] = useState('');
|
||||
const raw = useRef(new Uint8Array(1024));
|
||||
const rawLength = useRef(0);
|
||||
const [href, setHref] = useState('');
|
||||
|
||||
const cbs = useMemo<ParallelOptions>(() => ({
|
||||
putChar: (val: byte) => {
|
||||
const ascii = val & 0x7f;
|
||||
const visible = val >= 0x20;
|
||||
const char = String.fromCharCode(ascii);
|
||||
|
||||
if (char === '\r') {
|
||||
// Skip for once
|
||||
} else if (char === '\t') {
|
||||
// possibly not right due to tab stops
|
||||
setContent((content) => content += ' ');
|
||||
} else if (ascii === 0x04) {
|
||||
setContent((content) => content = content.slice(0, -1));
|
||||
return;
|
||||
} else if (visible) {
|
||||
setContent((content) => content += char);
|
||||
}
|
||||
|
||||
raw.current[rawLength.current++] = val;
|
||||
if (rawLength.current > raw.current.length) {
|
||||
const newRaw = new Uint8Array(raw.current.length * 2);
|
||||
newRaw.set(raw.current);
|
||||
raw.current = newRaw;
|
||||
}
|
||||
}
|
||||
}), [rawLength]);
|
||||
|
||||
useEffect(() => {
|
||||
if (io) {
|
||||
const parallel = new Parallel(cbs);
|
||||
io.setSlot(slot, parallel);
|
||||
}
|
||||
}, [cbs, io, slot]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
const blob = new Blob(
|
||||
[raw.current.slice(0, rawLength.current)],
|
||||
{ type: 'application/octet-stream' }
|
||||
);
|
||||
const href = window.URL.createObjectURL(blob);
|
||||
setHref(href);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const onClear = useCallback(() => {
|
||||
setContent('');
|
||||
rawLength.current = 0;
|
||||
}, []);
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
const onOpen = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Printer">
|
||||
<ModalContent>
|
||||
<pre className={styles.printer}>
|
||||
{content}
|
||||
</pre>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<a
|
||||
download="raw_printer_output.bin"
|
||||
href={href}
|
||||
role="button"
|
||||
>
|
||||
Download Raw
|
||||
</a>
|
||||
<button onClick={onClear}>Clear</button>
|
||||
<button onClick={onClose}>OK</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<ControlButton
|
||||
icon="print"
|
||||
title="Printer"
|
||||
onClick={onOpen}
|
||||
active={content.length > 0}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -9,7 +9,7 @@ body {
|
||||
background: black;
|
||||
}
|
||||
|
||||
button {
|
||||
button, a[role="button"] {
|
||||
background: #44372C;
|
||||
color: #fff;
|
||||
padding: 2px 8px;
|
||||
@ -19,16 +19,20 @@ button {
|
||||
min-width: 75px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
button:hover, a[role="button"]:hover {
|
||||
background-color: #55473D;
|
||||
border: 1px outset #66594E;
|
||||
}
|
||||
|
||||
button:active {
|
||||
button:active, a[role="button"]:active {
|
||||
background-color: #22150A;
|
||||
border: 1px outset #44372C;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
button:focus, a[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a[role="button"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
3
js/components/css/ControlButton.module.css
Normal file
3
js/components/css/ControlButton.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.active {
|
||||
color: #0f0;
|
||||
}
|
@ -35,6 +35,7 @@
|
||||
padding: 5px 11px;
|
||||
border: 1px outset #66594E;
|
||||
border-radius: 3px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modalHeader button {
|
||||
@ -60,4 +61,10 @@
|
||||
|
||||
.modalFooter {
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modalFooter a[role="button"], .modalFooter button {
|
||||
margin: 0 0 0 5px;
|
||||
min-width: 75px;
|
||||
}
|
||||
|
9
js/components/css/Printer.module.css
Normal file
9
js/components/css/Printer.module.css
Normal file
@ -0,0 +1,9 @@
|
||||
.printer {
|
||||
background: white;
|
||||
font: fixed;
|
||||
width: 60em;
|
||||
padding: 5em;
|
||||
height: 60vh;
|
||||
overflow: auto;
|
||||
border: 2px inset #f0edd0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user