Switch to scss, better scope rules (#170)

Restrict element types and global styling to improve embedability
This commit is contained in:
Will Scullin 2023-01-11 18:14:44 -08:00 committed by GitHub
parent 3d59142ee9
commit 9173bff1ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 428 additions and 288 deletions

View File

@ -10,6 +10,12 @@
<link rel="apple-touch-icon" size="72x72" href="img/webapp-ipad.png" />
<link rel="shortcut icon" href="img/logoicon.png" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.1.1/css/all.css" />
<style>
body {
background-color: #c4c1a0;
margin: 16px 0;
}
</style>
</head>
<body>

View File

@ -3,6 +3,7 @@ module.exports = {
'^js/(.*)': '<rootDir>/js/$1',
'^test/(.*)': '<rootDir>/test/$1',
'\\.css$': 'identity-obj-proxy',
'\\.scss$': 'identity-obj-proxy',
},
'roots': [
'js/',

View File

@ -1,4 +1,5 @@
import 'preact/debug';
import cs from 'classnames';
import { h } from 'preact';
import { Header } from './Header';
import { Apple2 } from './Apple2';
@ -7,7 +8,8 @@ import { SYSTEM_TYPE_APPLE2E } from '../ui/system';
import { SCREEN_GL } from '../ui/screen';
import { defaultSystem, systemTypes } from './util/systems';
import styles from './css/App.module.css';
import styles from './css/App.module.scss';
import componentStyles from './css/Components.module.scss';
/**
* Top level application component, provides the parameters
@ -26,7 +28,7 @@ export const App = () => {
};
return (
<div className={styles.container}>
<div className={cs(styles.container, componentStyles.components)}>
<Header e={system.e} />
<Apple2
gl={gl}

View File

@ -16,7 +16,7 @@ import { ThunderClock } from './ThunderClock';
import { Videoterm } from './Videoterm';
import { spawn, Ready } from './util/promises';
import styles from './css/Apple2.module.css';
import styles from './css/Apple2.module.scss';
import { SupportedSectors } from 'js/formats/types';
declare global {

View File

@ -8,7 +8,8 @@ import { DiskDragTarget } from './DiskDragTarget';
import { DownloadModal } from './DownloadModal';
import { ErrorModal } from './ErrorModal';
import styles from './css/BlockDisk.module.css';
import styles from './css/BlockDisk.module.scss';
import { ControlButton } from './ControlButton';
/**
* Storage structure for drive state returned via callbacks.
@ -84,12 +85,8 @@ export const BlockDisk = ({ smartPort, number, on, name }: BlockDiskProps) => {
id={`disk${number}`}
className={cs(styles.diskLight, { [styles.on]: on })}
/>
<button title="Load Disk" onClick={onOpenModal}>
<i className="fas fa-folder-open" />
</button>
<button title="Save Disk" onClick={onOpenDownloadModal}>
<i className="fas fa-save" />
</button>
<ControlButton title="Load Disk" onClick={onOpenModal} icon="folder-open" />
<ControlButton title="Save Disk" onClick={onOpenDownloadModal} icon="save" />
<div
id={`disk-label${number}`}
className={styles.diskLabel}

View File

@ -9,7 +9,7 @@ import SmartPort from 'js/cards/smartport';
import { useHash } from './hooks/useHash';
import { noAwait } from './util/promises';
import styles from './css/BlockFileModal.module.css';
import styles from './css/BlockFileModal.module.scss';
const DISK_TYPES: FilePickerAcceptType[] = [
{
@ -25,7 +25,7 @@ interface BlockFileModalProps {
onClose: (closeBox?: boolean) => void;
}
export const BlockFileModal = ({ smartPort, driveNo: number, onClose, isOpen } : BlockFileModalProps) => {
export const BlockFileModal = ({ smartPort, driveNo: number, onClose, isOpen }: BlockFileModalProps) => {
const [handles, setHandles] = useState<FileSystemFileHandle[]>();
const [busy, setBusy] = useState<boolean>(false);
const [empty, setEmpty] = useState<boolean>(true);

View File

@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { Apple2 as Apple2Impl } from '../apple2';
import type { Stats } from '../apple2';
import styles from './css/CPUMeter.module.css';
import styles from './css/CPUMeter.module.scss';
/**
* Interface for CPUMeter.

View File

@ -1,7 +1,7 @@
import { h, JSX } from 'preact';
import cs from 'classnames';
import styles from './css/ControlButton.module.css';
import styles from './css/ControlButton.module.scss';
/**
* Interface for ControlButton.
@ -23,7 +23,7 @@ export interface ControlButtonProps {
* @returns Control Button component
*/
export const ControlButton = ({ active, icon, title, onClick, ...props }: ControlButtonProps) => (
<button onClick={onClick} title={title} {...props} >
<button className={styles.iconButton} onClick={onClick} title={title} {...props} >
<i className={cs('fas', `fa-${icon}`, { [styles.active]: active })}></i>
</button>
);

View File

@ -4,7 +4,7 @@ import { CPUMeter } from './CPUMeter';
import { Inset } from './Inset';
import { useHotKey } from './hooks/useHotKey';
import { AudioControl } from './AudioControl';
import { OptionsModal} from './OptionsModal';
import { OptionsModal } from './OptionsModal';
import { OptionsContext } from './OptionsContext';
import { Printer } from './Printer';
import { ControlButton } from './ControlButton';
@ -13,7 +13,7 @@ import { JoyStick } from '../ui/joystick';
import { Screen, SCREEN_FULL_PAGE } from '../ui/screen';
import { System } from '../ui/system';
import styles from './css/ControlStrip.module.css';
import styles from './css/ControlStrip.module.scss';
import Apple2IO from 'js/apple2io';
const README = 'https://github.com/whscullin/apple2js#readme';
@ -57,26 +57,26 @@ export const ControlStrip = ({ apple2, e, toggleDebugger }: ControlStripProps) =
const doReset = useCallback(() =>
apple2?.reset()
, [apple2]);
, [apple2]);
const doReadme = useCallback(() =>
window.open(README, '_blank')
, []);
, []);
const doShowOptions = useCallback(() =>
setShowOptions(true)
, []);
, []);
const doCloseOptions = useCallback(() =>
setShowOptions(false)
, []);
, []);
const doToggleFullPage = useCallback(() =>
options.setOption(
SCREEN_FULL_PAGE,
!options.getOption(SCREEN_FULL_PAGE)
)
, [options]);
, [options]);
useHotKey('F2', doToggleFullPage);
useHotKey('F4', doShowOptions);
@ -90,7 +90,7 @@ export const ControlStrip = ({ apple2, e, toggleDebugger }: ControlStripProps) =
<ControlButton onClick={toggleDebugger} title="Toggle Debugger" icon="bug" />
<AudioControl apple2={apple2} />
<Printer io={io} slot={1} />
<div style={{flexGrow: 1}} />
<div style={{ flexGrow: 1 }} />
<ControlButton onClick={doReadme} title="About" icon="info" />
<ControlButton onClick={doShowOptions} title="Options (F4)" icon="cog" />
</Inset>

View File

@ -5,10 +5,11 @@ import Disk2 from '../cards/disk2';
import { ErrorModal } from './ErrorModal';
import { FileModal } from './FileModal';
import styles from './css/DiskII.module.css';
import styles from './css/DiskII.module.scss';
import { DiskDragTarget } from './DiskDragTarget';
import { FLOPPY_FORMATS } from 'js/formats/types';
import { DownloadModal } from './DownloadModal';
import { ControlButton } from './ControlButton';
/**
* Storage structure for Disk II state returned via callbacks.
@ -78,12 +79,8 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
/>
<ErrorModal error={error} setError={setError} />
<div className={cs(styles.diskLight, { [styles.on]: on })} />
<button title="Load Disk" onClick={onOpenModal}>
<i className="fas fa-folder-open" />
</button>
<button title="Save Disk" onClick={onOpenDownloadModal}>
<i className="fas fa-save" />
</button>
<ControlButton title="Load Disk" onClick={onOpenModal} icon="folder-open" />
<ControlButton title="Save Disk" onClick={onOpenDownloadModal} icon="save" />
<div className={styles.diskLabel}>
{label}
</div>

View File

@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'preact/hooks';
import { DriveNumber, MassStorage } from '../formats/types';
import { Modal, ModalContent, ModalFooter } from './Modal';
import styles from './css/DownloadModal.module.css';
import styles from './css/DownloadModal.module.scss';
interface DownloadModalProps {
isOpen: boolean;
@ -12,7 +12,7 @@ interface DownloadModalProps {
onClose: (closeBox?: boolean) => void;
}
export const DownloadModal = ({ massStorage, driveNo, onClose, isOpen } : DownloadModalProps) => {
export const DownloadModal = ({ massStorage, driveNo, onClose, isOpen }: DownloadModalProps) => {
const [href, setHref] = useState('');
const [downloadName, setDownloadName] = useState('');
const doCancel = useCallback(() => onClose(true), [onClose]);
@ -44,7 +44,7 @@ export const DownloadModal = ({ massStorage, driveNo, onClose, isOpen } : Downlo
<Modal title="Save File" isOpen={isOpen} onClose={onClose}>
<ModalContent>
<div className={styles.modalContent}>
{ href
{href
? (
<>
<span>Disk Name: {downloadName}</span>

View File

@ -13,7 +13,7 @@ import { useHash } from './hooks/useHash';
import { DISK_FORMATS, DRIVE_NUMBERS, SupportedSectors } from 'js/formats/types';
import { spawn, Ready } from './util/promises';
import styles from './css/Drives.module.css';
import styles from './css/Drives.module.scss';
import { DiskDragTarget } from './DiskDragTarget';
/**

View File

@ -2,7 +2,7 @@ import { h } from 'preact';
import { useCallback } from 'preact/hooks';
import { Modal, ModalContent, ModalFooter } from './Modal';
import styles from './css/ErrorModal.module.css';
import styles from './css/ErrorModal.module.scss';
export interface ErrorProps {
error: unknown | undefined;

View File

@ -10,7 +10,7 @@ import { noAwait, spawn } from './util/promises';
import { useHash } from './hooks/useHash';
import { FileChooser, FilePickerAcceptType } from './FileChooser';
import styles from './css/FileModal.module.css';
import styles from './css/FileModal.module.scss';
const DISK_TYPES: FilePickerAcceptType[] = [
{

View File

@ -1,6 +1,6 @@
import { h } from 'preact';
import styles from './css/Header.module.css';
import styles from './css/Header.module.scss';
const README = 'https://github.com/whscullin/apple2js#readme';

View File

@ -1,7 +1,7 @@
import { h, ComponentChildren, JSX } from 'preact';
import cs from 'classnames';
import styles from './css/Inset.module.css';
import styles from './css/Inset.module.scss';
interface InsetProps extends JSX.HTMLAttributes<HTMLDivElement> {
children: ComponentChildren;

View File

@ -10,7 +10,7 @@ import {
mapKeyboardEvent
} from './util/keyboard';
import styles from './css/Keyboard.module.css';
import styles from './css/Keyboard.module.scss';
/**
* Convenience function for massaging key labels for upper
@ -23,7 +23,7 @@ const buildLabel = (key: string) => {
const small = key.length > 1 && !key.startsWith('&');
return (
<span
className={cs({[styles.small]: small})}
className={cs({ [styles.small]: small })}
dangerouslySetInnerHTML={{ __html: key }}
/>
);
@ -110,7 +110,7 @@ export interface KeyboardProps {
export const Keyboard = ({ apple2, e }: KeyboardProps) => {
const [pressed, setPressed] = useState<string[]>([]);
const [active, setActive] = useState<string[]>(['LOCK']);
const keys = useMemo(() => keysAsTuples(e ? keys2e : keys2 ), [e]);
const keys = useMemo(() => keysAsTuples(e ? keys2e : keys2), [e]);
// Set global keystroke handler
useEffect(() => {
@ -125,7 +125,7 @@ export const Keyboard = ({ apple2, e }: KeyboardProps) => {
event.preventDefault();
const {key, keyCode, keyLabel} = mapKeyboardEvent(event, active.includes('LOCK'), active.includes('CTRL'));
const { key, keyCode, keyLabel } = mapKeyboardEvent(event, active.includes('LOCK'), active.includes('CTRL'));
setPressed(pressed => pressed.concat([keyLabel]));
setActive(active => active.concat([keyLabel]));
@ -152,7 +152,7 @@ export const Keyboard = ({ apple2, e }: KeyboardProps) => {
if (!apple2) {
return;
}
const {key, keyLabel} = mapKeyboardEvent(event);
const { key, keyLabel } = mapKeyboardEvent(event);
setPressed(pressed => pressed.filter(k => k !== keyLabel));
setActive(active => active.filter(k => k !== keyLabel));

View File

@ -1,9 +1,12 @@
import { h, ComponentChildren } from 'preact';
import cs from 'classnames';
import { createPortal } from 'preact/compat';
import { useCallback } from 'preact/hooks';
import { useHotKey } from './hooks/useHotKey';
import styles from './css/Modal.module.css';
import styles from './css/Modal.module.scss';
import componentStyles from './css/Components.module.scss';
import { ControlButton } from './ControlButton';
/**
* ModalOverlay creates a semi-transparent overlay in which the
@ -39,9 +42,9 @@ export const ModalContent = ({ children }: { children: ComponentChildren }) => {
*/
export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
return (
<footer className={styles.modalFooter}>
<div role="contentinfo" className={styles.modalFooter}>
{children}
</footer>
</div>
);
};
@ -64,9 +67,7 @@ export const ModalCloseButton = ({ onClose }: ModalCloseButtonProp) => {
useHotKey('Escape', doClose);
return (
<button onClick={doClose} title="Close">
{'\u2715'}
</button>
<ControlButton onClick={doClose} title="Close" icon="xmark" />
);
};
@ -91,14 +92,14 @@ export interface ModalHeaderProps {
*/
export const ModalHeader = ({ onClose, title, icon }: ModalHeaderProps) => {
return (
<header className={styles.modalHeader}>
<div role="banner" className={styles.modalHeader}>
<span className={styles.modalTitle}>
{icon && <i className={`fa-solid fa-${icon}`} role="img" />}
{' '}
{title}
</span>
{onClose && <ModalCloseButton onClose={onClose} />}
</header>
</div>
);
};
@ -132,7 +133,7 @@ export const Modal = ({
return (
isOpen ? createPortal((
<ModalOverlay>
<div className={styles.modal} role="dialog">
<div className={cs(styles.modal, componentStyles.components)} role="dialog">
{title && <ModalHeader title={title} {...props} />}
{children}
</div>

View File

@ -11,7 +11,7 @@ import {
SelectOption,
} from '../options';
import styles from './css/OptionsModal.module.css';
import styles from './css/OptionsModal.module.scss';
/**
* Boolean property interface
@ -29,7 +29,7 @@ interface BooleanProps {
* @param setValue Value setter
* @returns Boolean component
*/
const Boolean = ({ option, value, setValue } : BooleanProps) => {
const Boolean = ({ option, value, setValue }: BooleanProps) => {
const { label, name } = option;
const onChange = useCallback(
(event: JSX.TargetedMouseEvent<HTMLInputElement>) =>
@ -67,7 +67,7 @@ interface SelectProps {
* @param setValue Value setter
* @returns Select component
*/
const Select = ({ option, value, setValue } : SelectProps) => {
const Select = ({ option, value, setValue }: SelectProps) => {
const { label, name } = option;
const onChange = useCallback(
(event: JSX.TargetedMouseEvent<HTMLSelectElement>) => {
@ -110,7 +110,7 @@ export interface OptionsModalProps {
export const OptionsModal = ({ isOpen, onClose }: OptionsModalProps) => {
const options = useContext(OptionsContext);
const sections = options.getSections();
const setValue = useCallback(( name: string, value: string | boolean ) => {
const setValue = useCallback((name: string, value: string | boolean) => {
options.setOption(name, value);
}, [options]);

View File

@ -4,7 +4,7 @@ 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 styles from './css/Printer.module.scss';
import { ControlButton } from './ControlButton';
import { byte } from 'js/types';

View File

@ -1,7 +1,7 @@
import { h } from 'preact';
import { Modal, ModalContent } from './Modal';
import styles from './css/ProgressModal.module.css';
import styles from './css/ProgressModal.module.scss';
export interface ErrorProps {
title: string;
@ -9,7 +9,7 @@ export interface ErrorProps {
total: number | undefined;
}
export const ProgressModal = ({ title, current, total } : ErrorProps) => {
export const ProgressModal = ({ title, current, total }: ErrorProps) => {
if (current && total) {
return (
<Modal title={title} isOpen={true}>

View File

@ -1,6 +1,6 @@
import { h, Ref } from 'preact';
import styles from './css/Screen.module.css';
import styles from './css/Screen.module.scss';
/**
* Screen properties

View File

@ -2,7 +2,7 @@ import { ComponentChild, ComponentChildren, h } from 'preact';
import { useCallback, useState } from 'preact/hooks';
import cs from 'classnames';
import styles from './css/Tabs.module.css';
import styles from './css/Tabs.module.scss';
export interface TabProps {
children: ComponentChildren;

View File

@ -1,77 +0,0 @@
body {
margin: 16px 0;
font-size: 14px;
background-color: #c4c1a0; /* Pantone 453 */
font-family: sans-serif;
}
:global(.full-page) {
background: black;
}
a[role="button"] {
text-decoration: none;
}
button,
a[role="button"],
input[type="file"]::file-selector-button {
background: #44372c;
color: #fff;
padding: 2px 8px;
border: 1px outset #66594e;
border-radius: 3px;
font-size: 15px;
min-width: 75px;
}
button:hover,
a[role="button"]:hover,
input[type="file"]::file-selector-button {
background-color: #55473d;
border: 1px outset #66594e;
}
button:active,
a[role="button"]:active,
input[type="file"]::file-selector-button {
background-color: #22150a;
border: 1px outset #44372c;
}
button:focus,
a[role="button"]:focus,
input[type="file"]::file-selector-button {
outline: none;
}
.container {
display: flex;
flex-direction: column;
}
input[type="checkbox"] {
appearance: none;
background-color: #65594d;
border: 1px inset #65594d;
padding: 7px;
top: 7px;
border-radius: 3px;
display: inline-block;
position: relative;
}
input[type="checkbox"]:checked {
background-color: #65594d;
border: 1px inset #65594d;
color: #0d0;
}
input[type="checkbox"]:checked::after {
content: "\2716";
font-size: 12px;
position: absolute;
top: 0;
left: 2px;
color: #0d0;
}

View File

@ -0,0 +1,13 @@
:global(.full-page) {
background: black;
}
.container {
font-size: 14px;
font-family: sans-serif;
line-height: normal;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}

View File

@ -6,8 +6,8 @@
.outer {
width: 620px;
display: none;
}
.outer.ready {
display: block;
&.ready {
display: block;
}
}

View File

@ -0,0 +1,71 @@
.components {
font-size: 14px;
font-family: sans-serif;
a[role="button"] {
text-decoration: none;
}
button,
a[role="button"],
input[type="file"]::file-selector-button {
background: #44372c;
color: #fff;
padding: 2px 8px;
border: 1px outset #66594e;
border-radius: 3px;
font-size: 15px;
min-width: 75px;
&.iconButton {
min-width: 36px;
margin: 0 2px;
}
}
button:hover,
a[role="button"]:hover,
input[type="file"]::file-selector-button {
background-color: #55473d;
border: 1px outset #66594e;
}
button:active,
a[role="button"]:active,
input[type="file"]::file-selector-button {
background-color: #22150a;
border: 1px outset #44372c;
}
button:focus,
a[role="button"]:focus,
input[type="file"]::file-selector-button {
outline: none;
}
input[type="checkbox"] {
appearance: none;
background-color: #65594d;
border: 1px inset #65594d;
padding: 7px;
top: 7px;
border-radius: 3px;
display: inline-block;
position: relative;
}
input[type="checkbox"]:checked {
background-color: #65594d;
border: 1px inset #65594d;
color: #0d0;
}
input[type="checkbox"]:checked::after {
content: "\2716";
font-size: 12px;
position: absolute;
top: 0;
left: 2px;
color: #0d0;
}
}

View File

@ -1,3 +0,0 @@
.active {
color: #0f0;
}

View File

@ -0,0 +1,8 @@
.active {
color: #0f0;
}
.iconButton {
min-width: 36px !important;
margin: 0 2px;
}

View File

@ -17,19 +17,20 @@
justify-content: center;
align-items: center;
user-select: none;
}
.reset:hover {
background: #44372c;
border: 3px outset #66594e;
}
.reset:active {
background-color: #22150a;
border-left: 3px solid #44372c;
border-top: 3px solid #44372c;
border-right: 3px solid #000;
border-bottom: 3px solid #000;
&:hover {
background: #44372c;
border: 3px outset #66594e;
}
&:active {
background-color: #22150a;
border-left: 3px solid #44372c;
border-top: 3px solid #44372c;
border-right: 3px solid #000;
border-bottom: 3px solid #000;
}
}
:global(.full-page) .reset {

View File

@ -10,8 +10,3 @@
:global(.full-page) .inset {
display: none;
}
.inset button {
min-width: 36px;
margin: 0 2px;
}

View File

@ -70,6 +70,26 @@
/* border: 5px outset #66594E; */
border-radius: 5px;
cursor: pointer;
.active {
color: #4f4;
}
.small {
font-size: 7px;
}
div {
border-radius: 3px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
span {
font-size: 13px;
}
}
.pressed {
@ -82,14 +102,6 @@
/* border: 5px outset #44372C; */
}
.key div {
border-radius: 3px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
.vCenter div {
bottom: 10px;
}
@ -99,22 +111,6 @@
line-height: 10px;
}
.key span {
font-size: 13px;
}
.key .small {
font-size: 7px;
}
.key-SHIFT {
width: 53px;
}
.active {
color: #4f4;
}
.key-RETURN {
width: 52px;
}
@ -134,10 +130,10 @@
background-color: #ffd;
color: black;
border-radius: 2px;
}
.key-POWER div {
bottom: 15px;
div {
bottom: 15px;
}
}
.key-nbsp {

View File

@ -9,11 +9,11 @@
justify-content: center;
background: rgb(0 0 0 / 60%);
z-index: 1;
}
.modalOverlay button {
min-width: 36px;
margin: 0 2px;
button {
min-width: 36px;
margin: 0 2px;
}
}
.modal {
@ -36,10 +36,10 @@
border: 1px outset #66594e;
border-radius: 3px;
user-select: none;
}
.modalHeader button {
min-width: 36px;
button {
min-width: 36px;
}
}
.modalTitle {
@ -62,10 +62,11 @@
.modalFooter {
text-align: right;
user-select: none;
}
.modalFooter a[role="button"],
.modalFooter button {
margin: 0 0 0 5px;
min-width: 75px;
a[role="button"],
button {
margin: 0 0 0 5px;
min-width: 75px;
}
}

View File

@ -6,13 +6,13 @@
font-weight: bold;
padding: 4px;
border-radius: 4px 4px 0 0;
}
.tab.selected {
background-color: #c4c1a0;
border-bottom: none;
margin-bottom: -2px;
color: #080;
.selected {
background-color: #c4c1a0;
border-bottom: none;
margin-bottom: -2px;
color: #080;
}
}
.tabs {

View File

@ -6,8 +6,8 @@ import ApplesoftDecompiler from 'js/applesoft/decompiler';
import { ApplesoftHeap, ApplesoftVariable } from 'js/applesoft/heap';
import { Apple2 as Apple2Impl } from 'js/apple2';
import styles from './css/Applesoft.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/Applesoft.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
export interface ApplesoftProps {
apple2: Apple2Impl | undefined;

View File

@ -8,8 +8,8 @@ import { loadLocalBinaryFile } from '../util/files';
import { spawn } from '../util/promises';
import { toHex } from 'js/util';
import styles from './css/CPU.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/CPU.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
export interface CPUProps {
apple2: Apple2Impl | undefined;
@ -29,7 +29,7 @@ const VALID_PAGE = /^[0-9A-F]{1,2}$/i;
const VALID_ADDRESS = /^[0-9A-F]{1,4}$/i;
const ERROR_ICON = (
<div className={styles.invalid}>
<div className={styles.errorIcon}>
<i
className="fa-solid fa-triangle-exclamation"
title="Invalid hex address"

View File

@ -10,7 +10,7 @@ import { Disks } from './Disks';
import { Memory } from './Memory';
import { VideoModes } from './VideoModes';
import styles from './css/Debugger.module.css';
import styles from './css/Debugger.module.scss';
interface DebuggerProps {
apple2: Apple2 | undefined;

View File

@ -15,8 +15,8 @@ import { FileEntry } from 'js/formats/prodos/file_entry';
import { VDH } from 'js/formats/prodos/vdh';
import { toHex } from 'js/util';
import styles from './css/Disks.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/Disks.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
import { useCallback, useState } from 'preact/hooks';
import { DOS33, FileEntry as DOSEntry, isMaybeDOS33 } from 'js/formats/dos/dos33';
import createDiskFromDOS from 'js/formats/do';

View File

@ -3,7 +3,7 @@ import { h, Fragment } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks';
import { Modal, ModalContent, ModalFooter } from '../Modal';
import styles from './css/FileViewer.module.css';
import styles from './css/FileViewer.module.scss';
/**
* Binary and text representation of file to be previewed

View File

@ -6,8 +6,8 @@ import { Apple2 as Apple2Impl } from 'js/apple2';
import MMU from 'js/mmu';
import LanguageCard from 'js/cards/langcard';
import styles from './css/Memory.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/Memory.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
/**
* Encapsulates the read/write status of a bank
@ -20,7 +20,7 @@ interface ReadWrite {
/**
* Encapsulates the read/write status of a language card
*/
interface LC extends ReadWrite {
interface LC extends ReadWrite {
bank0: ReadWrite;
bank1: ReadWrite;
rom: ReadWrite;
@ -204,7 +204,7 @@ interface LanguageCardMapProps {
* @param children label component
* @returns LanguageCard component
*/
const LanguageCardMap = ({lc, children}: LanguageCardMapProps) => {
const LanguageCardMap = ({ lc, children }: LanguageCardMapProps) => {
return (
<div className={cs(styles.bank)}>
<div className={cs(styles.lc, rw(lc))}>

View File

@ -5,8 +5,8 @@ import cs from 'classnames';
import { Apple2 as Apple2Impl } from 'js/apple2';
import { VideoPage } from 'js/videomodes';
import styles from './css/VideoModes.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/VideoModes.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
export interface VideoModesProps {
apple2: Apple2Impl | undefined;
@ -62,13 +62,13 @@ export const VideoModes = ({ apple2 }: VideoModesProps) => {
return (
<div className={styles.pages}>
<div className={debuggerStyles.row}>
<div className={cs(styles.page, {[styles.active]: (text || !hires) && !page2})}>
<div className={cs(styles.page, { [styles.active]: (text || !hires) && !page2 })}>
<div className={debuggerStyles.heading}>
Text/Lores Page 1
</div>
<canvas width="560" height="192" ref={canvas1} />
</div>
<div className={cs(styles.page, {[styles.active]: (text || !hires) && page2})}>
<div className={cs(styles.page, { [styles.active]: (text || !hires) && page2 })}>
<div className={debuggerStyles.heading}>
Text/Lores Page 2
</div>
@ -76,13 +76,13 @@ export const VideoModes = ({ apple2 }: VideoModesProps) => {
</div>
</div>
<div className={debuggerStyles.row}>
<div className={cs(styles.page, {[styles.active]: (!text && hires) && !page2})}>
<div className={cs(styles.page, { [styles.active]: (!text && hires) && !page2 })}>
<div className={debuggerStyles.heading}>
Hires Page 1
</div>
<canvas width="560" height="192" ref={canvas3} />
</div>
<div className={cs(styles.page, {[styles.active]: (!text && hires) && page2})}>
<div className={cs(styles.page, { [styles.active]: (!text && hires) && page2 })}>
<div className={debuggerStyles.heading}>
Hires Page 2
</div>

View File

@ -1,48 +0,0 @@
.listing {
width: calc(100% - 12px);
height: 320px;
overflow: auto;
white-space: pre-wrap;
}
.variables {
width: 100%;
height: 320px;
overflow: auto;
}
.variables table {
width: 100%;
}
.variables td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
}
.internals {
width: 100%;
}
.internals table {
width: 100%;
}
.internals td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
width: 30%;
}
.internals th {
width: 20%;
text-align: right;
}
.stack {
width: 10em;
}

View File

@ -0,0 +1,49 @@
.listing {
width: calc(100% - 12px);
height: 320px;
overflow: auto;
white-space: pre-wrap;
}
.variables {
width: 100%;
height: 320px;
overflow: auto;
table {
width: 100%;
}
td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
}
}
.internals {
width: 100%;
table {
width: 100%;
}
td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
width: 30%;
}
th {
width: 20%;
text-align: right;
}
}
.stack {
width: 10em;
}

View File

@ -23,13 +23,13 @@
color: #f00;
}
div.invalid {
.errorIcon {
position: relative;
display: inline-block;
}
div.invalid i {
position: absolute;
top: -9px;
left: -16px;
i {
position: absolute;
top: -9px;
left: -16px;
}
}

View File

@ -130,19 +130,19 @@
position: relative;
}
div.read {
.read {
background-color: #8f8;
}
div.write {
.write {
background-color: #f88;
}
div.read.write {
.read.write {
background-color: #88f;
}
div.inactive {
.inactive {
background-color: #bbb;
}

117
package-lock.json generated
View File

@ -44,6 +44,8 @@
"node-forge": "^1.3.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.2",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"stylelint": "^14.9.1",
"stylelint-config-css-modules": "^4.1.0",
@ -4960,9 +4962,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001342",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz",
"integrity": "sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==",
"version": "1.0.30001442",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
"dev": true,
"funding": [
{
@ -7730,6 +7732,12 @@
"node": ">= 4"
}
},
"node_modules/immutable": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.1.tgz",
"integrity": "sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==",
"dev": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -11631,6 +11639,15 @@
"node": ">=6"
}
},
"node_modules/klona": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
"integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/known-css-properties": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz",
@ -13482,6 +13499,61 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"node_modules/sass": {
"version": "1.57.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz",
"integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/sass-loader": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz",
"integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
"dev": true,
"dependencies": {
"klona": "^2.0.4",
"neo-async": "^2.6.2"
},
"engines": {
"node": ">= 14.15.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"fibers": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
}
}
},
"node_modules/saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@ -19663,9 +19735,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001342",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz",
"integrity": "sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==",
"version": "1.0.30001442",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
"dev": true
},
"canvas": {
@ -21753,6 +21825,12 @@
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"dev": true
},
"immutable": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.1.tgz",
"integrity": "sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==",
"dev": true
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -24709,6 +24787,12 @@
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dev": true
},
"klona": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
"integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==",
"dev": true
},
"known-css-properties": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz",
@ -26079,6 +26163,27 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"sass": {
"version": "1.57.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz",
"integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
"sass-loader": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz",
"integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
"dev": true,
"requires": {
"klona": "^2.0.4",
"neo-async": "^2.6.2"
}
},
"saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",

View File

@ -53,6 +53,8 @@
"node-forge": "^1.3.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.2",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"stylelint": "^14.9.1",
"stylelint-config-css-modules": "^4.1.0",

5
types/styles.d.ts vendored
View File

@ -2,3 +2,8 @@ declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}

View File

@ -38,6 +38,24 @@ const baseConfig = {
'css-loader'
],
exclude: /\.module\.css$/
},
{
test: /\.module\.s(a|c)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
import: false,
modules: {
localIdentName: '[path][name]__[local]',
}
}
},
{
loader: 'sass-loader',
}
],
}
],
},