diff --git a/package-lock.json b/package-lock.json
index 2b3b42f..ab8374f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
+ "copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0",
"eslint": "^8.44.0",
"sass": "^1.71.0",
@@ -262,6 +263,18 @@
"node": ">= 8"
}
},
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
+ "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@types/eslint": {
"version": "8.56.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
@@ -753,6 +766,45 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-formats/node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -1028,6 +1080,127 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/copy-webpack-plugin": {
+ "version": "12.0.2",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz",
+ "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==",
+ "dev": true,
+ "dependencies": {
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.1",
+ "globby": "^14.0.0",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "ajv": "^8.8.2"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/globby": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
+ "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
+ "dev": true,
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^2.1.0",
+ "fast-glob": "^3.3.2",
+ "ignore": "^5.2.4",
+ "path-type": "^5.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
+ "node_modules/copy-webpack-plugin/node_modules/path-type": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
+ "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/schema-utils": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+ "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2474,6 +2647,15 @@
"node": ">= 10.13.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -2985,6 +3167,18 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
+ "node_modules/unicorn-magic": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
+ "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
diff --git a/package.json b/package.json
index f2e6cbe..cca2efa 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
+ "copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0",
"eslint": "^8.44.0",
"sass": "^1.71.0",
diff --git a/src/events.ts b/src/events.ts
index 50e8113..68e184b 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -72,6 +72,12 @@ export enum UiEvent {
// Ui Events
EditOn,
EditOff,
+ ConsoleOn,
+ ConsoleOff,
+ ExplainerOn,
+ ExplainerOff,
+ VideoOn,
+ VideoOff,
}
interface UiEventMap {
diff --git a/src/include/explainer.png b/src/include/explainer.png
new file mode 100644
index 0000000..7ed74b8
Binary files /dev/null and b/src/include/explainer.png differ
diff --git a/src/include/index.html b/src/include/index.html
new file mode 100644
index 0000000..58ad3e9
--- /dev/null
+++ b/src/include/index.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ Virtual 8-Bit Computer
+
+
+
+
+
VIRTUAL 8-BIT COMPUTER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/include/pencil.png b/src/include/pencil.png
new file mode 100644
index 0000000..e36d1d6
Binary files /dev/null and b/src/include/pencil.png differ
diff --git a/src/include/texout.png b/src/include/texout.png
new file mode 100644
index 0000000..7b8aab3
Binary files /dev/null and b/src/include/texout.png differ
diff --git a/src/include/tv.png b/src/include/tv.png
new file mode 100644
index 0000000..7c0aba0
Binary files /dev/null and b/src/include/tv.png differ
diff --git a/src/index.ts b/src/index.ts
index 01e474a..65f9d24 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,7 +10,7 @@ import { generate_isa } from "./isaGenerator";
import { UI } from "./ui";
import { u8 } from "./num";
-import "./style.scss";
+import "./style/style.scss";
import { CpuEvent } from "./events";
declare global {
diff --git a/src/style.scss b/src/style.scss
deleted file mode 100644
index 347237e..0000000
--- a/src/style.scss
+++ /dev/null
@@ -1,310 +0,0 @@
-* {
- box-sizing: border-box;
-}
-
-pre {
- font-size: 0.5em;
-}
-
-:root {
- --Border: #ffff00;
- --mem-instruction: #3af78f;
- --mem-memory: #ff26a8;
- --mem-register: #9e0ef7;
- --mem-constant: #19f7f0;
- --mem-invalid: #bf2e2e;
-}
-
-img {
- image-rendering: pixelated;
-}
-
-body {
- color: #808080;
- background-color: black;
- font-size: 2.5em;
- font-family: monospace;
-}
-
-#labelcontainer {
- column-gap: 18px;
- font-size: 0.85em;
- display: flex;
- align-items: center;
- user-select: none;
- grid-area: regmemlabel;
-}
-
-#main {
- justify-content: center;
- display: grid;
- grid-template-columns: min-content max-content max-content min-content 500px;
- grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
- grid-template-areas:
- "cycles registers regmemlabel . explainer "
- "title . . ribbon explainer "
- "title . . ribbon ."
- "title . . ribbon printout "
- "title . . . printout "
- ". buttons buttons . .";
- #memory {
- grid-column: 2/4;
- grid-row: 2/6;
- }
- #cycles {
- grid-area: cycles;
- text-align: left;
- align-self: center;
- font-size: 0.48em;
- }
- #title {
- grid-area: title;
- writing-mode: vertical-lr;
- text-align: left;
- user-select: none;
- transform: scale(-1, -1);
- }
- #ribbon_menu {
- grid-area: ribbon;
- }
-}
-
-#printout {
- border: 4px dashed var(--Border);
- grid-area: printout;
- padding: 10px;
- word-wrap: break-word;
- word-break: break-all;
- max-height: 200px;
- overflow-x: scroll;
-}
-
-#instruction_explainer {
- grid-area: explainer;
- display: flex;
- flex-direction: column;
- gap: 5px;
- border: 5px dashed var(--Border);
- #expl_box {
- padding-inline: 20px;
- padding-block-start: 10px;
- }
-
- #expl_text {
- font-size: 0.6em;
- text-align: center;
- vertical-align: center;
- }
-
- #expl_icon {
- margin-inline-end: 0.5em;
- font-size: 30px;
- padding: 5px;
- color: var(--color);
- outline: 3px dashed var(--color);
- }
-}
-
-.invalid {
- --color: var(--mem-invalid);
-}
-.constant {
- --color: var(--mem-constant);
-}
-.register {
- --color: var(--mem-register);
-}
-.memory {
- --color: var(--mem-memory);
-}
-.instruction {
- --color: var(--mem-instruction);
-}
-
-div[contenteditable] {
- &.caret_selected {
- box-sizing: border-box;
- outline: 2px solid red;
- }
- outline: none;
-}
-
-.pending_edit {
- color: green;
-}
-#memory {
- grid-area: memory;
- display: grid;
- grid-template-columns: repeat(16, min-content);
- gap: 5px;
- padding: 10px;
- border: 5px solid yellow;
-
- div {
- user-select: none;
- caret-color: transparent;
- text-align: center;
- color: var(--color);
- }
-
- .program_counter {
- outline: 3px solid orange;
- }
-
- .instruction_argument,
- .current_instruction {
- outline: 3px dashed var(--color);
- }
- .recent_edit {
- color: lime;
- }
- div.last_access {
- color: orange;
- }
-
- .invalid {
- &::after {
- user-select: none;
- float: right;
- position: relative;
- right: 0.5em;
- width: 0px;
- font-size: 0.5em;
- content: "!";
- }
- }
-}
-
-div#main.editor {
- #memory,
- #registers {
- border-style: dashed;
- div {
- cursor: text;
- }
- }
-}
-
-#ribbon_menu {
- margin-inline: 8px;
- .editor_toggle {
- //TODO CHANGE COLORS WHen
- &.off {
- }
- &.on {
- }
- }
-}
-
-#registers {
- grid-area: registers;
- border: 5px solid yellow;
- border-bottom: none !important;
-
- grid-template-columns: repeat(8, min-content);
- max-width: fit-content;
- display: grid;
- column-gap: 5px;
- color: lightgray;
- padding: 10px;
-
- div {
- max-height: min-content;
- // margin-block: auto;
- }
-}
-
-button,
-label.button {
- border: 4px solid yellow;
- color: gray;
- margin: 10px;
- margin-inline: 0px;
- padding: 10px;
- font-size: 0.8em;
- font-family: monospace;
- background-color: transparent;
- cursor: pointer;
- user-select: none;
-}
-
-button:disabled {
- border: 4px solid rgb(255, 255, 128);
- color: orange;
- cursor: default;
-}
-
-input[type="range"]:disabled {
- cursor: default;
-}
-
-button.no_style {
- border: none;
- color: inherit;
- margin: 0;
- padding: 0;
- font-size: inherit;
- background-color: inherit;
-}
-
-button:hover,
-label.button:hover {
- color: white;
-}
-
-#controls_bar {
- grid-area: buttons;
-
- display: flex;
- gap: 10px;
- #controls_buttons {
- display: flex;
- gap: inherit;
- }
-}
-
-input[type="range"] {
- background-color: transparent;
- -webkit-appearance: none;
- appearance: none;
- margin: 18px 0;
-}
-
-input[type="range"]:focus {
- outline: none;
-}
-// 2024 and we still have to do this
-input[type="range"]::-webkit-slider-runnable-track {
- height: 0.5em;
- cursor: pointer;
- background: yellow;
- border-radius: 0px;
-}
-input[type="range"]::-webkit-slider-thumb {
- border: 4px solid yellow;
- background-color: black;
- height: 42px;
- width: 20px;
- border-radius: 0px;
- cursor: pointer;
- margin-top: -18px;
- -webkit-appearance: none;
-}
-
-input[type="range"]:focus::-webkit-slider-runnable-track {
- background: yellow;
-}
-input[type="range"]::-moz-range-track {
- height: 0.5em;
- cursor: pointer;
- background: yellow;
- border-radius: 0px;
-}
-input[type="range"]::-moz-range-thumb {
- border: 4px solid yellow;
- background-color: black;
- height: 36px;
- width: 16px;
- border-radius: 0px;
- cursor: pointer;
-}
diff --git a/src/style/buttons.scss b/src/style/buttons.scss
new file mode 100644
index 0000000..74b34d4
--- /dev/null
+++ b/src/style/buttons.scss
@@ -0,0 +1,101 @@
+@use "sass:math";
+
+button,
+label.button {
+ border: 4px solid yellow;
+ color: gray;
+ margin: 10px;
+ margin-inline: 0px;
+ padding: 10px;
+ font-size: 0.8em;
+ font-family: monospace;
+ background-color: transparent;
+ cursor: pointer;
+ user-select: none;
+}
+
+button:disabled {
+ border: 4px solid rgb(255, 255, 128);
+ color: orange;
+ cursor: default;
+}
+
+input[type="range"]:disabled {
+ cursor: default;
+}
+
+button.nostyle {
+ border: none;
+ color: inherit;
+ margin: 0;
+ padding: 0;
+ font-size: inherit;
+ background-color: inherit;
+}
+
+button:hover,
+label.button:hover {
+ color: white;
+}
+
+#controls_bar {
+ grid-area: buttons;
+
+ display: flex;
+ gap: 10px;
+ #controls_buttons {
+ display: flex;
+ gap: inherit;
+ }
+}
+
+input[type="range"] {
+ background-color: transparent;
+ -webkit-appearance: none;
+ appearance: none;
+ margin: 18px 0;
+ width: 100px;
+}
+
+input[type="range"]:focus {
+ outline: none;
+}
+
+// 2024 and we still have to do this
+
+@mixin sliderTrack {
+ height: 0.5em;
+ cursor: pointer;
+ background: yellow;
+ border-radius: 0px;
+}
+
+@mixin sliderThumb($height, $width) {
+ border: 4px solid yellow;
+ background-color: black;
+ height: $height;
+ width: $width;
+ border-radius: 0px;
+ cursor: pointer;
+}
+
+input[type="range"]::-webkit-slider-runnable-track {
+ @include sliderTrack;
+}
+input[type="range"]::-moz-range-track {
+ @include sliderTrack;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ @include sliderThumb(42px, 20px);
+ -webkit-appearance: none;
+ margin-top: -18px;
+}
+
+input[type="range"]::-moz-range-thumb {
+ @include sliderThumb(36px, 16px);
+}
+
+input[type="range"]:focus::-webkit-slider-runnable-track {
+ background: yellow;
+}
diff --git a/src/style/memory_registers.scss b/src/style/memory_registers.scss
new file mode 100644
index 0000000..8052540
--- /dev/null
+++ b/src/style/memory_registers.scss
@@ -0,0 +1,69 @@
+#memory {
+ grid-template-columns: repeat(16, min-content);
+
+ .program_counter {
+ outline: 3px solid orange;
+ }
+
+ .instruction_argument,
+ .current_instruction {
+ outline: 3px dashed var(--color);
+ }
+
+ .invalid {
+ &::after {
+ user-select: none;
+ float: right;
+ position: relative;
+ right: 0.5em;
+ width: 0px;
+ font-size: 0.5em;
+ content: "!";
+ }
+ }
+}
+#registers {
+ border-bottom: none !important;
+
+ grid-template-columns: repeat(8, min-content);
+ color: lightgray;
+}
+
+#labelcontainer {
+ column-gap: 18px;
+ font-size: 0.85em;
+ display: flex;
+ align-items: center;
+ user-select: none;
+}
+.celled_viewer {
+ display: grid;
+ max-width: fit-content;
+ gap: 5px;
+ border: 5px solid yellow;
+ padding: 10px;
+ user-select: none;
+
+ div {
+ .pending_edit {
+ color: green;
+ }
+ &.recent_edit {
+ color: lime;
+ }
+ &.last_access {
+ color: orange;
+ }
+ user-select: none;
+ caret-color: transparent;
+ text-align: center;
+ color: var(--color);
+ &[contenteditable] {
+ &.caret_selected {
+ box-sizing: border-box;
+ outline: 2px solid red;
+ }
+ outline: none;
+ }
+ }
+}
diff --git a/src/style/style.scss b/src/style/style.scss
new file mode 100644
index 0000000..64cd08d
--- /dev/null
+++ b/src/style/style.scss
@@ -0,0 +1,93 @@
+@use "memory_registers";
+@use "windows";
+@use "buttons";
+@use "vars";
+
+* {
+ box-sizing: border-box;
+}
+
+pre {
+ font-size: 0.5em;
+}
+
+img {
+ image-rendering: pixelated;
+}
+
+body {
+ color: #808080;
+ background-color: black;
+ font-size: 2.5em;
+ font-family: monospace;
+}
+
+main {
+ display: flex;
+ justify-content: center;
+}
+
+#grid {
+ justify-content: center;
+ display: grid;
+ grid-template-columns: min-content min-content min-content;
+ grid-template-rows: min-content min-content min-content;
+ grid-template-areas:
+ "cycles registers regmemlabel"
+ "title memory memory"
+ ". buttons buttons ";
+ #memory {
+ grid-area: memory;
+ // grid-column: 2/4;
+ // grid-row: 2/6;
+ }
+ #window_box {
+ grid-area: windowbox;
+ }
+ #registers {
+ grid-area: registers;
+ }
+ #labelcontainer {
+ grid-area: regmemlabel;
+ }
+ #cycles {
+ grid-area: cycles;
+ text-align: left;
+ align-self: center;
+ font-size: 0.48em;
+ user-select: none;
+ }
+ #title {
+ grid-area: title;
+ writing-mode: vertical-lr;
+ text-align: left;
+ user-select: none;
+ transform: scale(-1, -1);
+ }
+}
+
+.invalid {
+ --color: var(--mem-invalid);
+}
+.constant {
+ --color: var(--mem-constant);
+}
+.register {
+ --color: var(--mem-register);
+}
+.memory {
+ --color: var(--mem-memory);
+}
+.instruction {
+ --color: var(--mem-instruction);
+}
+
+div#main.editor {
+ #memory,
+ #registers {
+ border-style: dashed;
+ div {
+ cursor: text;
+ }
+ }
+}
diff --git a/src/style/vars.scss b/src/style/vars.scss
new file mode 100644
index 0000000..d47c6f9
--- /dev/null
+++ b/src/style/vars.scss
@@ -0,0 +1,8 @@
+:root {
+ --border: #ffff00;
+ --mem-instruction: #3af78f;
+ --mem-memory: #ff26a8;
+ --mem-register: #9e0ef7;
+ --mem-constant: #19f7f0;
+ --mem-invalid: #bf2e2e;
+}
diff --git a/src/style/windows.scss b/src/style/windows.scss
new file mode 100644
index 0000000..dc4b24d
--- /dev/null
+++ b/src/style/windows.scss
@@ -0,0 +1,108 @@
+#resize {
+ // background-color: red;
+ width: 100%;
+ height: 18px;
+ position: absolute;
+ bottom: 0;
+ border-bottom: 5px solid yellow;
+
+ cursor: s-resize;
+}
+
+#window_box {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-left: 10px;
+ max-width: 500px;
+ .window {
+ overflow-y: hidden;
+ position: relative;
+ border: 5px Solid var(--border);
+ border-bottom: unset;
+ &.collapsed {
+ .window_title {
+ // border-bottom: unset;
+ }
+ }
+ .window_title {
+ position: sticky;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 0.6em;
+ color: lightgray;
+ border-bottom: 5px solid var(--border);
+ background: repeating-linear-gradient(
+ to top,
+ transparent,
+ transparent 2px,
+ transparent 2px,
+ yellow 2px,
+ yellow 4px
+ ),
+ repeating-linear-gradient(to right, transparent, transparent 2px, transparent 2px, yellow 2px, yellow 4px);
+
+ #text {
+ display: inline-block;
+ text-align: center;
+ height: 100%;
+ padding-inline: 10px;
+ background-color: black;
+ }
+ #collapse_button {
+ height: 23px !important;
+ aspect-ratio: 1;
+ border: 2px solid white;
+ background-color: black;
+ margin-right: 3px;
+ }
+ }
+ }
+}
+
+.window#tv {
+ height: unset;
+ #screen {
+ max-width: 100%;
+ aspect-ratio: 1;
+ }
+}
+
+#instruction_explainer {
+ grid-area: explainer;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ height: 400px;
+ #expl_box {
+ padding-inline: 20px;
+ padding-block-start: 10px;
+ }
+
+ #expl_text {
+ font-size: 0.6em;
+ text-align: center;
+ vertical-align: center;
+ }
+
+ #expl_icon {
+ margin-inline-end: 0.5em;
+ font-size: 30px;
+ padding: 5px;
+ color: var(--color);
+ outline: 3px dashed var(--color);
+ }
+}
+
+#printout {
+ grid-area: printout;
+ height: 500px;
+ word-wrap: break-word;
+ word-break: break-all;
+ overflow-x: hidden;
+ #printout_text {
+ padding-inline: 10px;
+ }
+}
diff --git a/src/ui.ts b/src/ui.ts
index 84450b0..d29a03b 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -1,19 +1,16 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { $ } from "./etc";
-import { InstructionExplainer } from "./ui/instructionExplainer";
+import { InstructionExplainer } from "./ui/windows/instructionExplainer";
import { MemoryView } from "./ui/memoryView";
import { frequencyIndicator } from "./ui/frequencyIndicator";
import { RegisterView } from "./ui/registerView";
-import { Screen } from "./ui/screen";
-import { Ribbon } from "./ui/ribbon";
+import { Screen } from "./ui/windows/screen";
+import { EditButton } from "./ui/edit_button";
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
import { pausePlay } from "./ui/pausePlay";
+import { Printout } from "./ui/windows/printout";
export class UI {
- printout: HTMLElement;
-
- auto_running: boolean;
-
events: UiEventHandler = new UiEventHandler();
private components: Array;
@@ -30,27 +27,23 @@ export class UI {
this.register_component(frequencyIndicator, $("cycles"));
this.register_component(InstructionExplainer, $("instruction_explainer"));
this.register_component(RegisterView, $("registers"));
- this.register_component(Screen, $("screen") as HTMLCanvasElement);
- this.register_component(Ribbon, $("ribbon_menu"));
+ this.register_component(Screen, $("tv"));
+ this.register_component(Printout, $("printout"));
+ this.register_component(EditButton, $("edit_button"));
this.register_component(pausePlay, $("controls_buttons"));
- this.printout = $("printout");
- this.auto_running = false;
const pp_button = $("pause_play_button");
}
- private register_component(c: UiComponentConstructor, e: HTMLElement): void {
+ private register_component(ctor: UiComponentConstructor, e: HTMLElement): void {
if (e === undefined) {
- console.log(c);
+ console.log(ctor);
throw new Error("Could not find HTML element while registering UI component");
}
- const component = new c(e, this.events);
+ const component = new ctor(e, this.events);
this.components.push(component);
}
init_events(cpu_events: CpuEventHandler): void {
- cpu_events.listen(CpuEvent.Print, (char) => {
- this.printout.textContent = (this.printout.textContent ?? "") + char;
- });
cpu_events.listen(CpuEvent.Reset, () => {
this.reset();
});
@@ -64,6 +57,5 @@ export class UI {
for (const c of this.components) {
c.reset();
}
- this.printout.textContent = "";
}
}
diff --git a/src/ui/celledViewer.ts b/src/ui/celledViewer.ts
index b75520a..0e80068 100644
--- a/src/ui/celledViewer.ts
+++ b/src/ui/celledViewer.ts
@@ -19,6 +19,7 @@ export abstract class CelledViewer {
this.element = element;
this.width = width;
this.height = height;
+ this.element.classList.add("celled_viewer");
for (let i = 0; i < this.width * this.height; i++) {
const mem_cell_el = el("div");
mem_cell_el.append("0", "0");
diff --git a/src/ui/edit_button.ts b/src/ui/edit_button.ts
new file mode 100644
index 0000000..c9ee99b
--- /dev/null
+++ b/src/ui/edit_button.ts
@@ -0,0 +1,41 @@
+import { el, $ } from "../etc";
+import { UiEventHandler, UiEvent } from "../events";
+import { UiComponent } from "./uiComponent";
+
+export class EditButton implements UiComponent {
+ element: HTMLElement;
+ events: UiEventHandler;
+ constructor(element: HTMLElement, event: UiEventHandler) {
+ this.element = element;
+ this.events = event;
+
+ const image = el("img");
+ image.src = "pencil.png";
+ image.style.width = "20px";
+ image.style.height = "20px";
+ this.element.classList.add("editor_toggle");
+ this.element.addEventListener("click", () => this.edit_toggle());
+ this.element.appendChild(image);
+ }
+ reset(): void {
+ const is_on = this.element.classList.contains("on");
+ if (is_on) {
+ this.edit_toggle();
+ }
+ }
+
+ edit_toggle(): void {
+ const is_on = this.element.classList.contains("on");
+ if (is_on) {
+ this.element.classList.remove("on");
+ $("root").classList.remove("editor");
+ this.element.classList.add("off");
+ this.events.dispatch(UiEvent.EditOff);
+ } else {
+ this.events.dispatch(UiEvent.EditOn);
+ $("root").classList.add("editor");
+ this.element.classList.add("on");
+ this.element.classList.remove("off");
+ }
+ }
+}
diff --git a/src/ui/editableHex.ts b/src/ui/editableHex.ts
index 3c83ead..32f7fb1 100644
--- a/src/ui/editableHex.ts
+++ b/src/ui/editableHex.ts
@@ -24,6 +24,14 @@ export class EditorContext {
cell.addEventListener("keydown", (e) => {
this.keydown(e, i);
});
+ cell.addEventListener("input", (e) => {
+ const target = e.target as HTMLElement;
+ if (target === null) return;
+ const text = target.textContent ?? "";
+ if (text.length !== 2) {
+ target.textContent = text.substring(0, 2);
+ }
+ });
cell.addEventListener("focus", () => {
if (!this.enabled) return;
this.current_cell_info.old = cell.textContent ?? "00";
diff --git a/src/ui/frequencyIndicator.ts b/src/ui/frequencyIndicator.ts
index 0006fc5..a0f7c53 100644
--- a/src/ui/frequencyIndicator.ts
+++ b/src/ui/frequencyIndicator.ts
@@ -37,6 +37,9 @@ export class frequencyIndicator implements UiComponent {
this.element.textContent = `${value}hz`;
this.last_value = value;
}
+ if (value === 0) {
+ this.element.textContent = "";
+ }
this.last_time = new_time;
this.count = 0;
}
diff --git a/src/ui/pausePlay.ts b/src/ui/pausePlay.ts
index 7973a25..66312f8 100644
--- a/src/ui/pausePlay.ts
+++ b/src/ui/pausePlay.ts
@@ -1,5 +1,5 @@
import { el } from "../etc";
-import { UiEventHandler, CpuEventHandler, UiEvent } from "../events";
+import { UiEventHandler, UiEvent } from "../events";
import { UiComponent } from "./uiComponent";
const MAX_SLIDER = 1000;
diff --git a/src/ui/ribbon.ts b/src/ui/ribbon.ts
deleted file mode 100644
index 9c04d76..0000000
--- a/src/ui/ribbon.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { el, $ } from "../etc";
-import { UiEventHandler, UiEvent } from "../events";
-import { UiComponent } from "./uiComponent";
-
-function new_button(name: string, img_path: string, additional_class?: string): HTMLButtonElement {
- const button = el("button", "", "no_style ribbon_button");
- const image = el("img");
- image.src = img_path;
- image.width = 64;
- image.height = 64;
- if (additional_class !== undefined) {
- button.classList.add(additional_class);
- }
- button.appendChild(image);
- return button;
-}
-
-export class Ribbon implements UiComponent {
- element: HTMLElement;
- events: UiEventHandler;
- edit_button: HTMLButtonElement;
- console_button: HTMLButtonElement;
- display_button: HTMLButtonElement;
- explainer_button: HTMLButtonElement;
- constructor(element: HTMLElement, event: UiEventHandler) {
- this.element = element;
- this.events = event;
-
- this.edit_button = new_button("Edit", "pencil.png", "editor_toggle");
- this.console_button = new_button("Console", "texout.png");
- this.display_button = new_button("Video", "tv.png");
- this.explainer_button = new_button("Explainer", "explainer.png");
- this.edit_button.addEventListener("click", () => this.edit_toggle());
- this.element.appendChild(this.edit_button);
- this.element.appendChild(this.console_button);
- this.element.appendChild(this.display_button);
- this.element.appendChild(this.explainer_button);
- }
- reset(): void {
- const is_on = this.edit_button.classList.contains("on");
- if (is_on) {
- this.edit_toggle();
- }
- }
-
- edit_toggle(): void {
- const is_on = this.edit_button.classList.contains("on");
- if (is_on) {
- this.edit_button.classList.remove("on");
- $("main").classList.remove("editor");
- this.edit_button.classList.add("off");
- this.events.dispatch(UiEvent.EditOff);
- } else {
- this.events.dispatch(UiEvent.EditOn);
- $("main").classList.add("editor");
- this.edit_button.classList.add("on");
- this.edit_button.classList.remove("off");
- }
- }
-}
diff --git a/src/ui/uiComponent.ts b/src/ui/uiComponent.ts
index 9b2a16a..d970ea2 100644
--- a/src/ui/uiComponent.ts
+++ b/src/ui/uiComponent.ts
@@ -9,7 +9,9 @@ export interface UiComponent {
element: HTMLElement;
/** Allows listening and emitting UiEvents*/
events: UiEventHandler;
+ /** Completely reset the state of the component */
reset: () => void;
+ soft_reset?: () => void;
/** Allows listening CPUEvents*/
init_cpu_events?: (c: CpuEventHandler) => void;
}
diff --git a/src/ui/windowBox.ts b/src/ui/windowBox.ts
new file mode 100644
index 0000000..32415d6
--- /dev/null
+++ b/src/ui/windowBox.ts
@@ -0,0 +1,82 @@
+import { el } from "../etc";
+export abstract class WindowBox {
+ element: HTMLElement;
+ readonly title: string;
+ title_bar: HTMLElement;
+ private resize: HTMLElement;
+ private collapse_button: HTMLButtonElement;
+ private collapsed: boolean = false;
+ private resize_func: (e: MouseEvent) => void;
+
+ constructor(element: HTMLElement, title: string, options?: { collapsed?: boolean }) {
+ this.element = element;
+ this.title = title;
+ this.element.classList.add("window");
+ this.title_bar = el("div", undefined, "window_title");
+ this.element.appendChild(this.title_bar);
+ const title_bar_text_box = el("div", "text");
+ title_bar_text_box.textContent = title;
+ this.title_bar.appendChild(title_bar_text_box);
+ this.resize = el("div", "resize");
+ this.element.appendChild(this.resize);
+ this.resize_func = this.resize_move.bind(this);
+ this.collapse_button = el("button", "collapse_button", "nostyle");
+ this.collapse_button.addEventListener("click", () => {
+ this.toggle_collapse();
+ });
+ this.title_bar.appendChild(this.collapse_button);
+ this.resize.addEventListener("mousedown", (e) => {
+ window.addEventListener("mousemove", this.resize_func);
+ });
+ window.addEventListener("mouseup", () => {
+ this.remove_resize_listeners();
+ });
+ window.addEventListener("mouseleave", () => {
+ this.remove_resize_listeners();
+ });
+ if (options?.collapsed) {
+ this.collapse();
+ }
+ }
+
+ collapse(): void {
+ this.element.classList.add("collapsed");
+ this.remove_resize_listeners();
+ this.resize.style.visibility = "hidden";
+ this.element.style.height = `${this.title_bar.offsetHeight + 4}px`;
+ this.collapsed = true;
+ }
+
+ toggle_collapse(): void {
+ if (this.collapsed) {
+ this.uncollapse();
+ } else {
+ this.collapse();
+ }
+ }
+
+ uncollapse(): void {
+ this.element.classList.remove("collapsed");
+ this.resize.style.visibility = "unset";
+ this.element.style.height = `${this.title_bar.offsetHeight + 10 + 200}px`;
+ this.collapsed = false;
+ }
+
+ remove_resize_listeners(): void {
+ window.removeEventListener("mousemove", this.resize_func);
+ }
+
+ resize_move(e: MouseEvent): void {
+ if (this.collapsed) {
+ this.uncollapse();
+ this.remove_resize_listeners();
+ return;
+ }
+ const distance_to_title = e.clientY - this.element.offsetTop - this.title_bar.offsetHeight + window.scrollY + 5;
+ if (distance_to_title <= 5) {
+ this.collapse();
+ return;
+ }
+ this.element.style.height = `${e.clientY - this.element.offsetTop + window.scrollY + 8}px`;
+ }
+}
diff --git a/src/ui/bankIndicator.ts b/src/ui/windows/bankIndicator.ts
similarity index 71%
rename from src/ui/bankIndicator.ts
rename to src/ui/windows/bankIndicator.ts
index 19f429e..3109465 100644
--- a/src/ui/bankIndicator.ts
+++ b/src/ui/windows/bankIndicator.ts
@@ -1,6 +1,6 @@
-import { UiEventHandler, CpuEventHandler, CpuEvent } from "../events";
-import { u1, u2 } from "../num";
-import { UiComponent } from "./uiComponent";
+import { UiEventHandler, CpuEventHandler, CpuEvent } from "../../events";
+import { u1, u2 } from "../../num";
+import { UiComponent } from "../uiComponent";
class BankIndicator implements UiComponent {
element: HTMLElement;
diff --git a/src/ui/instructionExplainer.ts b/src/ui/windows/instructionExplainer.ts
similarity index 71%
rename from src/ui/instructionExplainer.ts
rename to src/ui/windows/instructionExplainer.ts
index 422d1cd..a83d334 100644
--- a/src/ui/instructionExplainer.ts
+++ b/src/ui/windows/instructionExplainer.ts
@@ -1,14 +1,14 @@
-import { el, format_hex } from "../etc";
-import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
-import { Instruction, ParamType, ParameterType } from "../instructionSet";
-import { u8 } from "../num";
-import { UiComponent } from "./uiComponent";
+import { el, format_hex } from "../../etc";
+import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events";
+import { Instruction, ParamType, ParameterType } from "../../instructionSet";
+import { u8 } from "../../num";
+import { WindowBox } from "../windowBox";
+import { UiComponent } from "../uiComponent";
-export class InstructionExplainer implements UiComponent {
- element: HTMLElement;
+export class InstructionExplainer extends WindowBox implements UiComponent {
events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
- this.element = element;
+ super(element, "Instruction Explainer");
this.events = e;
}
add_instruction(instr: Instruction, pos: u8, byte: u8): void {
@@ -29,7 +29,7 @@ export class InstructionExplainer implements UiComponent {
this.element.appendChild(instr_box);
}
- add_param(param: ParameterType, pos: u8, byte: u8): void {
+ add_parameter(param: ParameterType, pos: u8, byte: u8): void {
const t = param.type;
let name;
if (t === ParamType.Const) {
@@ -51,7 +51,7 @@ export class InstructionExplainer implements UiComponent {
init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => {
- this.add_param(param, pos, code);
+ this.add_parameter(param, pos, code);
});
c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
this.add_instruction(instr, pos, code);
@@ -62,6 +62,6 @@ export class InstructionExplainer implements UiComponent {
}
reset(): void {
- this.element.innerHTML = "";
+ this.element.querySelectorAll("#expl_box").forEach((e) => e.remove());
}
}
diff --git a/src/ui/windows/printout.ts b/src/ui/windows/printout.ts
new file mode 100644
index 0000000..f24e0bc
--- /dev/null
+++ b/src/ui/windows/printout.ts
@@ -0,0 +1,25 @@
+import { el } from "../../etc";
+import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events";
+import { WindowBox } from "../windowBox";
+import { UiComponent } from "../uiComponent";
+
+export class Printout extends WindowBox implements UiComponent {
+ events: UiEventHandler;
+ text_box: HTMLElement;
+ constructor(element: HTMLElement, events: UiEventHandler) {
+ super(element, "Printout");
+ this.events = events;
+ this.text_box = el("div", "printout_text");
+ this.element.appendChild(this.text_box);
+ }
+
+ init_cpu_events(c: CpuEventHandler): void {
+ c.listen(CpuEvent.Print, (c) => {
+ this.text_box.textContent += c;
+ });
+ }
+
+ reset(): void {
+ this.text_box.textContent = "";
+ }
+}
diff --git a/src/ui/screen.ts b/src/ui/windows/screen.ts
similarity index 59%
rename from src/ui/screen.ts
rename to src/ui/windows/screen.ts
index 67d96b5..058f7dc 100644
--- a/src/ui/screen.ts
+++ b/src/ui/windows/screen.ts
@@ -1,32 +1,39 @@
-import { UiEventHandler, CpuEventHandler, CpuEvent } from "../events";
-import { u4, u8 } from "../num";
-import { UiComponent } from "./uiComponent";
-export class Screen implements UiComponent {
- element: HTMLCanvasElement;
+import { el } from "../../etc";
+import { UiEventHandler, CpuEventHandler, CpuEvent } from "../../events";
+import { u4, u8 } from "../../num";
+import { UiComponent } from "../uiComponent";
+import { WindowBox } from "../windowBox";
+export class Screen extends WindowBox implements UiComponent {
events: UiEventHandler;
+ screen: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
scale: [number, number];
constructor(element: HTMLElement, event: UiEventHandler) {
- this.element = element as HTMLCanvasElement;
+ super(element, "TV", { collapsed: true });
+ this.screen = el("canvas", "screen");
this.events = event;
const canvas_size = [512, 512];
const data_size = [16, 16];
this.scale = [canvas_size[0] / data_size[0], canvas_size[1] / data_size[1]];
- [this.element.width, this.element.height] = canvas_size;
- const ctx = this.element.getContext("2d");
+ [this.screen.width, this.screen.height] = canvas_size;
+ const ctx = this.screen.getContext("2d");
if (ctx === null) {
- throw new Error("todo");
+ throw new Error("could not load screen");
}
this.ctx = ctx;
- // for (let x = 0; x < 16; x++) {
- // for (let y = 0; y < 16; y++) {
- // this.setPixel(x as u4, y as u4, (x + 16 * y) as u8);
- // }
- // }
+ this.element.appendChild(this.screen);
+ }
+
+ private test_pattern(): void {
+ for (let x = 0; x < 16; x++) {
+ for (let y = 0; y < 16; y++) {
+ this.setPixel(x as u4, y as u4, (x + 16 * y) as u8);
+ }
+ }
}
reset(): void {
- const ctx = this.element.getContext("2d");
+ const ctx = this.screen.getContext("2d");
if (ctx === null) {
throw new Error("todo");
}
diff --git a/svg.html b/svg.html
new file mode 100644
index 0000000..e826c0c
--- /dev/null
+++ b/svg.html
@@ -0,0 +1,74 @@
+
+
+
+
+
+ Document
+
+
+
+
+
CPU
+
+
+
Main
+
Bank 1
+
Bank 2
+
VRAM
+
+
+
+
diff --git a/todo.md b/todo.md
index 75ce1d9..d859ee2 100644
--- a/todo.md
+++ b/todo.md
@@ -3,7 +3,6 @@ Edit Mode
- Select where program counter is
Implement HCF
-Move start/stop/auto logic into computer
Speed control slider behavior
Speed control slider styling
@@ -11,9 +10,6 @@ Overclock Box
error in instruction when number out of range (fix new Error("todo"))
-UI for screen (toggling (click an icon?))
-UI for togging other UI elements
-
UI for showing which Memory bank is selected
VRAM select instruction
diff --git a/webpack.config.js b/webpack.config.js
index 60de58d..bf6fb8d 100755
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,4 +1,7 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+/* eslint-disable no-undef */
const path = require("path");
+const CopyPlugin = require("copy-webpack-plugin");
const config = {
entry: "./src/index.ts",
@@ -33,6 +36,11 @@ const config = {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
+ plugins: [
+ new CopyPlugin({
+ patterns: [{ from: "./src/include", to: "./" }],
+ }),
+ ],
};
module.exports = (env, argv) => {
if (argv.mode === "development") {