diff --git a/entrypoints/windowBinder.js b/entrypoints/windowBinder.js index 68953c8e..7f7b8b39 100644 --- a/entrypoints/windowBinder.js +++ b/entrypoints/windowBinder.js @@ -79,6 +79,12 @@ window.fullReset = GraphicsInstance.fullReset.bind(GraphicsInstance); const ConsoleInstance = new Console(); window.readLine = ConsoleInstance.readLine.bind(ConsoleInstance); window.readInt = ConsoleInstance.readInt.bind(ConsoleInstance); +window.readFloat = ConsoleInstance.readFloat.bind(ConsoleInstance); +window.readBoolean = ConsoleInstance.readBoolean.bind(ConsoleInstance); +window.readLineAsync = ConsoleInstance.readLineAsync.bind(ConsoleInstance); +window.readIntAsync = ConsoleInstance.readIntAsync.bind(ConsoleInstance); +window.readFloatAsync = ConsoleInstance.readFloatAsync.bind(ConsoleInstance); +window.readBooleanAsync = ConsoleInstance.readBooleanAsync.bind(ConsoleInstance); window.println = ConsoleInstance.println.bind(ConsoleInstance); window.print = ConsoleInstance.print.bind(ConsoleInstance); window.clear = ConsoleInstance.clear.bind(ConsoleInstance); diff --git a/package-lock.json b/package-lock.json index e712af8f..24ebdfe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { "name": "chs-js-lib", - "version": "0.2.22", + "version": "0.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.2.22", + "version": "0.3.0", "license": "ISC", "dependencies": { + "http-server": "^14.1.0", "tone": "^14.7.77", "typescript": "^4.5.4" }, @@ -282,12 +283,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@babel/core/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/@babel/core/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -950,6 +945,17 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -1646,6 +1652,14 @@ "node": ">= 0.10" } }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -2303,8 +2317,7 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/execa": { "version": "5.1.1", @@ -2419,7 +2432,6 @@ "version": "1.14.4", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", - "dev": true, "engines": { "node": ">=4.0" } @@ -2741,6 +2753,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2773,7 +2804,6 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -2783,6 +2813,107 @@ "node": ">=8.0.0" } }, + "node_modules/http-server": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz", + "integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.5", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/http-server/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/http-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3999,8 +4130,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.isfinite": { "version": "3.3.2", @@ -4351,10 +4481,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/mitt": { "version": "1.2.0", @@ -4547,6 +4676,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/openurl": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", @@ -4780,6 +4917,51 @@ "semver-compare": "^1.0.0" } }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/portfinder/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/portscanner": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", @@ -5008,7 +5190,6 @@ "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true, "engines": { "node": ">=0.6" } @@ -5121,8 +5302,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "node_modules/requizzle": { "version": "0.2.3", @@ -5250,11 +5430,15 @@ "npm": ">=2.0.0" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/section-matter": { "version": "1.0.0", @@ -5269,6 +5453,11 @@ "node": ">=4" } }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -6133,6 +6322,17 @@ "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", "dev": true }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6151,6 +6351,11 @@ "node": ">= 0.8" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6184,6 +6389,28 @@ "node": ">=0.10.0" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6565,12 +6792,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7094,6 +7315,14 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -7682,6 +7911,11 @@ "vary": "^1" } }, + "corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=" + }, "cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -8219,8 +8453,7 @@ "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "execa": { "version": "5.1.1", @@ -8313,8 +8546,7 @@ "follow-redirects": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", - "dev": true + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" }, "fresh": { "version": "0.5.2", @@ -8552,6 +8784,19 @@ "has-symbols": "^1.0.2" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8583,13 +8828,82 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, + "http-server": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz", + "integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==", + "requires": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.5", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -9531,8 +9845,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.isfinite": { "version": "3.3.2", @@ -9797,10 +10110,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "mitt": { "version": "1.2.0", @@ -9937,6 +10249,11 @@ "mimic-fn": "^2.1.0" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" + }, "openurl": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", @@ -10110,6 +10427,47 @@ "semver-compare": "^1.0.0" } }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "portscanner": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", @@ -10314,8 +10672,7 @@ "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "queue-microtask": { "version": "1.2.3", @@ -10398,8 +10755,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "requizzle": { "version": "0.2.3", @@ -10491,11 +10847,15 @@ "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "section-matter": { "version": "1.0.0", @@ -10507,6 +10867,11 @@ "kind-of": "^6.0.0" } }, + "secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -11214,6 +11579,14 @@ "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", "dev": true }, + "union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "requires": { + "qs": "^6.4.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -11226,6 +11599,11 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -11250,6 +11628,24 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 2fb31246..f43d5b43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chs-js-lib", - "version": "0.2.22", + "version": "0.3.0", "description": "JavaScript graphics library used in CodeHS's platform.", "main": "dist/chs.cjs", "module": "dist/chs.mjs", @@ -21,6 +21,7 @@ "dist": "tsc && node build.js dist", "docs": "npm run build && cp -r dist/* ./site/assets/ && jsdoc --readme src/DOCSREADME.md src/**/*.js src/*.js --template node_modules/docdash --destination _site/docs", "prepare": "husky install", + "server": "npx http-server -c-1 --cors -p 8888", "site": "npm run docs && npx @11ty/eleventy", "site:watch": "npm run docs && npx @11ty/eleventy --watch" }, @@ -42,6 +43,7 @@ "prettier": "^2.4.1" }, "dependencies": { + "http-server": "^14.1.0", "tone": "^14.7.77", "typescript": "^4.5.4" } diff --git a/site/assets/types.d.ts.map b/site/assets/types.d.ts.map new file mode 100644 index 00000000..3eac98a4 --- /dev/null +++ b/site/assets/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/graphics/thing.js","../src/graphics/graphics-utils.js","../src/graphics/arc.js","../src/datastructures/vector.js","../src/randomizer.js","../src/graphics/color.js","../src/graphics/circle.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/site/examples/console/asyncReadline/asyncReadline.js b/site/examples/console/asyncReadline/asyncReadline.js deleted file mode 100644 index 6a0a242b..00000000 --- a/site/examples/console/asyncReadline/asyncReadline.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Console } from '/assets/chs.mjs'; -const c = new Console({ - onPrompt: message => { - return new Promise(resolve => { - setTimeout(() => { - resolve(message); - }, 1000); - }); - }, -}); - -(async () => { - const input = await c.readLine('Echo!'); - alert(input); -})(); diff --git a/site/examples/console/asyncReadline/asyncReadline.md b/site/examples/console/asyncReadline/asyncReadline.md deleted file mode 100644 index 5394236a..00000000 --- a/site/examples/console/asyncReadline/asyncReadline.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Console - Async Readline -code: asyncReadline.js -layout: example.11ty.js ---- - -Console can be configured to use async/await input methods. diff --git a/site/examples/console/readBoolean/readBoolean.js b/site/examples/console/readBoolean/readBoolean.js new file mode 100644 index 00000000..f56a1455 --- /dev/null +++ b/site/examples/console/readBoolean/readBoolean.js @@ -0,0 +1,6 @@ +const startButton = document.createElement('button'); +startButton.innerHTML = 'Click to run the program!'; +document.body.appendChild(startButton); +startButton.addEventListener('click', () => { + alert(readBoolean('Enter yes/no or true/false')); +}); diff --git a/site/examples/console/readBoolean/readBoolean.md b/site/examples/console/readBoolean/readBoolean.md new file mode 100644 index 00000000..cb042fd9 --- /dev/null +++ b/site/examples/console/readBoolean/readBoolean.md @@ -0,0 +1,10 @@ +--- +title: Console - readBoolean +code: readBoolean.js +layout: example.11ty.js +width: 1 +height: 1 +--- + +`readBoolean` will read a boolean from the user via the browser prompt. +`readBoolean` will reprompt the user for another boolean until it receives one, either 'true', 'false', 'yes', or 'no'. diff --git a/site/examples/console/readBooleanAsync/readBooleanAsync.js b/site/examples/console/readBooleanAsync/readBooleanAsync.js new file mode 100644 index 00000000..8ecb2a5b --- /dev/null +++ b/site/examples/console/readBooleanAsync/readBooleanAsync.js @@ -0,0 +1,46 @@ +let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + +const input = document.createElement('input'); +input.type = 'text'; +document.body.appendChild(input); + +const submitButton = document.createElement('button'); +submitButton.innerHTML = 'Submit!'; +document.body.appendChild(submitButton); + +const rerunButton = document.createElement('button'); +rerunButton.innerHTML = 'Rerun'; +document.body.appendChild(rerunButton); + +const console = new Console({ + input: promptMessage => { + const text = new Text(promptMessage); + text.color = darkMode ? 'white' : 'black'; + text.setPosition(getWidth() / 2, getHeight() / 2); + text.setAnchor({ vertical: 0.5, horizontal: 0.5 }); + add(text); + return new Promise(resolve => { + submitButton.disabled = false; + submitButton.addEventListener( + 'click', + () => { + resolve(input.value); + input.value = ''; + submitButton.disabled = true; + remove(text); + }, + { once: true } + ); + }); + }, +}); + +const run = async () => { + rerunButton.disabled = true; + const x = await console.readBooleanAsync('Give me a boolean in the input box!!'); + alert(`You input ${x}!`); + rerunButton.disabled = false; + rerunButton.addEventListener('click', run, { once: true }); +}; + +run(); diff --git a/site/examples/console/readBooleanAsync/readBooleanAsync.md b/site/examples/console/readBooleanAsync/readBooleanAsync.md new file mode 100644 index 00000000..8affdd62 --- /dev/null +++ b/site/examples/console/readBooleanAsync/readBooleanAsync.md @@ -0,0 +1,9 @@ +--- +title: Console - readBooleanAsync +code: readBooleanAsync.js +layout: example.11ty.js +--- + +`readBooleanAsync` will return a Promise that resolves with the input. It will re-prompt until it receives a boolean or `null`. +By default, `readBooleanAsync` will use `prompt` and block the window, but it can be configured to receive input other ways, like via an input. +In this case, it's configured to get input from an input box. diff --git a/site/examples/console/readFloat/readFloat.js b/site/examples/console/readFloat/readFloat.js new file mode 100644 index 00000000..f8bc57e0 --- /dev/null +++ b/site/examples/console/readFloat/readFloat.js @@ -0,0 +1,8 @@ +const startButton = document.createElement('button'); +startButton.innerHTML = 'Click to run the program!'; +document.body.appendChild(startButton); +startButton.addEventListener('click', () => { + let piGuess = readFloat('Enter as much of pi as you know! '); + let difference = Math.abs(Math.PI - piGuess); + alert(`You were off by ${difference}`); +}); diff --git a/site/examples/console/readFloat/readFloat.md b/site/examples/console/readFloat/readFloat.md new file mode 100644 index 00000000..c04fdcb7 --- /dev/null +++ b/site/examples/console/readFloat/readFloat.md @@ -0,0 +1,10 @@ +--- +title: Console - readFLoat +code: readFloat.js +layout: example.11ty.js +width: 1 +height: 1 +--- + +`readFloat` will read a floating point (decimal) number from the user via the browser prompt. +`readFloat` will reprompt the user for another input until it receives a float. diff --git a/site/examples/console/readFloatAsync/readFloatAsync.js b/site/examples/console/readFloatAsync/readFloatAsync.js new file mode 100644 index 00000000..544ecf33 --- /dev/null +++ b/site/examples/console/readFloatAsync/readFloatAsync.js @@ -0,0 +1,46 @@ +let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + +const input = document.createElement('input'); +input.type = 'text'; +document.body.appendChild(input); + +const submitButton = document.createElement('button'); +submitButton.innerHTML = 'Submit!'; +document.body.appendChild(submitButton); + +const rerunButton = document.createElement('button'); +rerunButton.innerHTML = 'Rerun'; +document.body.appendChild(rerunButton); + +const console = new Console({ + input: promptMessage => { + const text = new Text(promptMessage); + text.color = darkMode ? 'white' : 'black'; + text.setPosition(getWidth() / 2, getHeight() / 2); + text.setAnchor({ vertical: 0.5, horizontal: 0.5 }); + add(text); + return new Promise(resolve => { + submitButton.disabled = false; + submitButton.addEventListener( + 'click', + () => { + resolve(input.value); + input.value = ''; + submitButton.disabled = true; + remove(text); + }, + { once: true } + ); + }); + }, +}); + +const run = async () => { + rerunButton.disabled = true; + const x = await console.readFloatAsync('Give me a float in the input box!!'); + alert(`You input ${x}!`); + rerunButton.disabled = false; + rerunButton.addEventListener('click', run, { once: true }); +}; + +run(); diff --git a/site/examples/console/readFloatAsync/readFloatAsync.md b/site/examples/console/readFloatAsync/readFloatAsync.md new file mode 100644 index 00000000..ed860fc9 --- /dev/null +++ b/site/examples/console/readFloatAsync/readFloatAsync.md @@ -0,0 +1,9 @@ +--- +title: Console - readFloatAsync +code: readFloatAsync.js +layout: example.11ty.js +--- + +`readFloatAsync` will return a Promise that resolves with the input. It will re-prompt until it receives a number or `null`. +By default, `readFloatAsync` will use `prompt` and block the window, but it can be configured to receive input other ways, like via an input. +In this case, it's configured to get input from an input box. diff --git a/site/examples/console/readInt/readInt.js b/site/examples/console/readInt/readInt.js index f5dbd08d..85c69a92 100644 --- a/site/examples/console/readInt/readInt.js +++ b/site/examples/console/readInt/readInt.js @@ -1,3 +1,8 @@ -let x = readInt('Give me an x: '); -let y = readInt('Give me a y: '); -println('The y/x = ' + y / x); +const startButton = document.createElement('button'); +startButton.innerHTML = 'Click to run the program!'; +document.body.appendChild(startButton); +startButton.addEventListener('click', () => { + let x = readInt('Give me an x: '); + let y = readInt('Give me a y: '); + alert('y/x = ' + y / x); +}); diff --git a/site/examples/console/readInt/readInt.md b/site/examples/console/readInt/readInt.md index bc20453c..9f75e2d0 100644 --- a/site/examples/console/readInt/readInt.md +++ b/site/examples/console/readInt/readInt.md @@ -2,6 +2,9 @@ title: Console - readInt code: readInt.js layout: example.11ty.js +width: 1 +height: 1 --- -This program reads an integer from the user and multiplies it by 12. +`readInt` will read an integer from the user via the browser prompt. +`readInt` will reprompt the user for another input until it receives an integer. diff --git a/site/examples/console/readIntAsync/readIntAsync.js b/site/examples/console/readIntAsync/readIntAsync.js new file mode 100644 index 00000000..02f936b4 --- /dev/null +++ b/site/examples/console/readIntAsync/readIntAsync.js @@ -0,0 +1,46 @@ +let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + +const input = document.createElement('input'); +input.type = 'text'; +document.body.appendChild(input); + +const submitButton = document.createElement('button'); +submitButton.innerHTML = 'Submit!'; +document.body.appendChild(submitButton); + +const rerunButton = document.createElement('button'); +rerunButton.innerHTML = 'Rerun'; +document.body.appendChild(rerunButton); + +const console = new Console({ + input: promptMessage => { + const text = new Text(promptMessage); + text.color = darkMode ? 'white' : 'black'; + text.setPosition(getWidth() / 2, getHeight() / 2); + text.setAnchor({ vertical: 0.5, horizontal: 0.5 }); + add(text); + return new Promise(resolve => { + submitButton.disabled = false; + submitButton.addEventListener( + 'click', + () => { + resolve(input.value); + input.value = ''; + submitButton.disabled = true; + remove(text); + }, + { once: true } + ); + }); + }, +}); + +const run = async () => { + rerunButton.disabled = true; + const x = await console.readIntAsync('Give me an integer in the input box!!'); + alert(`You input ${x}!`); + rerunButton.disabled = false; + rerunButton.addEventListener('click', run, { once: true }); +}; + +run(); diff --git a/site/examples/console/readIntAsync/readIntAsync.md b/site/examples/console/readIntAsync/readIntAsync.md new file mode 100644 index 00000000..d8341b50 --- /dev/null +++ b/site/examples/console/readIntAsync/readIntAsync.md @@ -0,0 +1,9 @@ +--- +title: Console - readIntAsync +code: readIntAsync.js +layout: example.11ty.js +--- + +`readIntAsync` will return a Promise that resolves with an integer. It will re-prompt until it receives an integer or `null`. +By default, `readIntAsync` will use `prompt` and block the window, but it can be configured to receive input other ways, like via an input. +In this case, it's configured to get input from an input box. diff --git a/site/examples/console/readLine/readLine.js b/site/examples/console/readLine/readLine.js new file mode 100644 index 00000000..af77d1bc --- /dev/null +++ b/site/examples/console/readLine/readLine.js @@ -0,0 +1,7 @@ +const startButton = document.createElement('button'); +startButton.innerHTML = 'Click to run the program!'; +document.body.appendChild(startButton); +startButton.addEventListener('click', () => { + let input = readLine('Give me a line!'); + alert(input.toUpperCase()); +}); diff --git a/site/examples/console/readLine/readLine.md b/site/examples/console/readLine/readLine.md new file mode 100644 index 00000000..3bb720f0 --- /dev/null +++ b/site/examples/console/readLine/readLine.md @@ -0,0 +1,9 @@ +--- +title: Console - readLine +code: readLine.js +layout: example.11ty.js +width: 1 +height: 1 +--- + +`readLine` will read a line of text from the user via the browser prompt. diff --git a/site/examples/console/readLineAsync/readLineAsync.js b/site/examples/console/readLineAsync/readLineAsync.js new file mode 100644 index 00000000..098fe770 --- /dev/null +++ b/site/examples/console/readLineAsync/readLineAsync.js @@ -0,0 +1,46 @@ +let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + +const input = document.createElement('input'); +input.type = 'text'; +document.body.appendChild(input); + +const submitButton = document.createElement('button'); +submitButton.innerHTML = 'Submit!'; +document.body.appendChild(submitButton); + +const rerunButton = document.createElement('button'); +rerunButton.innerHTML = 'Rerun'; +document.body.appendChild(rerunButton); + +const console = new Console({ + input: promptMessage => { + const text = new Text(promptMessage); + text.color = darkMode ? 'white' : 'black'; + text.setPosition(getWidth() / 2, getHeight() / 2); + text.setAnchor({ vertical: 0.5, horizontal: 0.5 }); + add(text); + return new Promise(resolve => { + submitButton.disabled = false; + submitButton.addEventListener( + 'click', + () => { + resolve(input.value); + input.value = ''; + submitButton.disabled = true; + remove(text); + }, + { once: true } + ); + }); + }, +}); + +const run = async () => { + rerunButton.disabled = true; + const x = await console.readLineAsync('Give me text in the input box!!'); + alert(x.toUpperCase()); + rerunButton.disabled = false; + rerunButton.addEventListener('click', run, { once: true }); +}; + +run(); diff --git a/site/examples/console/readLineAsync/readLineAsync.md b/site/examples/console/readLineAsync/readLineAsync.md new file mode 100644 index 00000000..c57da9c0 --- /dev/null +++ b/site/examples/console/readLineAsync/readLineAsync.md @@ -0,0 +1,9 @@ +--- +title: Console - readLineAsync +code: readLineAsync.js +layout: example.11ty.js +--- + +`readLineAsync` will return a Promise that resolves with the input. +By default, `readLineAsync` will use `prompt` and block the window, but it can be configured to receive input other ways, like via an input. +In this case, it's configured to get input from an input box. diff --git a/site/examples/index.html b/site/examples/index.html index 8028e784..7406206a 100644 --- a/site/examples/index.html +++ b/site/examples/index.html @@ -279,10 +279,25 @@

Sound

Console

diff --git a/src/DOCSREADME.md b/src/DOCSREADME.md index f25e45cb..a56a9606 100644 --- a/src/DOCSREADME.md +++ b/src/DOCSREADME.md @@ -23,10 +23,14 @@ Welcome! This is documentation for the CodeHS JavaScript Library. ## Console -- {@link readInt} - {@link readLine} +- {@link readInt} - {@link readFloat} - {@link readBoolean} +- {@link readLineAsync} +- {@link readIntAsync} +- {@link readFloatAsync} +- {@link readBooleanAsync} - {@link print} - {@link println} diff --git a/src/console/index.js b/src/console/index.js index d76843d4..deb40360 100644 --- a/src/console/index.js +++ b/src/console/index.js @@ -2,22 +2,22 @@ * Console provides utilities for interacting with a text console. * {@link Console#readInt}, {@link Console#readFloat}, {@link Console#readBoolean}, and {@link Console#readLine} * prompt the user for input and parse it to the corresponding type. This prompt will use the blocking - * browser prompt by default, but can be configured using {@link Console#onPrompt}. + * browser prompt by default, but can be configured using {@link Console#onInput}. * * Console also exposes {@link Console#print} and {@link Console#println}, which are used for * emitting output. By default the output will print to the console, but can be configured using - * {@link Console#onPrint}. + * {@link Console#onOutput}. */ class Console { /** - * Function invoked when asking for user input. - * This function is invoked with the string of the prompt, i.e. readInt('give me an int!'). - * The result of invoking onPrompt will be subjected to parsing functions for confirming - * it's an appropriate data type (a float, in the case of readFloat, for example). If + * Function invoked when asking for asynchronous user input with the read*Async functions. + * This function is invoked with the string of the prompt, i.e. readIntAsync('give me an int!'). + * The result of invoking onPrompt will be awaited, then parsed to configrm it's the + * appropriate data type (a float, in the case of readFloat, for example). If * onPrompt is undefined, window.prompt is used as a fallback. - * @type {function} + * @type {Function} */ - onPrompt = window.prompt.bind(window); + onInput = async promptString => await prompt(promptString); /** * Function invoked when printing. * This function is invoked with any output, either in the case of explicit calls to `print` @@ -25,7 +25,7 @@ class Console { * is used as a fallback. * @type {function} */ - onPrint = window.console.log.bind(window.console); + onOutput = window.console.log.bind(window.console); /** * Function invoked when {@link Console#clear} is called. */ @@ -34,42 +34,44 @@ class Console { /** * Initialize the console class, additionally configuring any event handlers. * @constructor - * @param {function} options.onPrompt Function invoked when asking for user input. - * This function is invoked with the string of the prompt, i.e. readInt('give me an int!'). - * The result of invoking onPrompt will be subjected to parsing functions for confirming - * it's an appropriate data type (a float, in the case of readFloat, for example). If + * @param {Object} options + * @param {function} options.input Function invoked when asking for user input asynchronously. + * This function is invoked with the string of the prompt, i.e. readIntAsync('give me an int!'). + * The result of invoking onPrompt will be awaited, then parsed to configrm it's the + * appropriate data type (a float, in the case of readFloat, for example). If * onPrompt is undefined, window.prompt is used as a fallback. - * @param {function} options.onPrint Function invoked when printing. + * @param {function} options.output Function invoked when printing. * This function is invoked with any output, either in the case of explicit calls to `print` * or `println` or internal calls within the library. If onPrint is undefined, console.log * is used as a fallback. - * @param {function} options.onClear Function invoked when clear() is called. + * @param {function} options.clear Function invoked when clear() is called. */ constructor(options = {}) { - this.onPrompt = options.onPrompt ?? window.prompt.bind(window); - this.onPrint = options.onPrint ?? window.console.log.bind(window.console); - this.onClear = options.onClear ?? window.console.clear.bind(window.console); + this.onInput = options.input ?? (async promptString => await prompt(promptString)); + this.onOutput = options.output ?? window.console.log.bind(window.console); + this.onClear = options.clear ?? window.console.clear.bind(window.console); } /** * Configure the Console instance, providing methods it invokes * when prompting for input and emitting output. * - * @param {function} options.onPrompt Function invoked when asking for user input. - * This function is invoked with the string of the prompt, i.e. readInt('give me an int!'). - * The result of invoking onPrompt will be subjected to parsing functions for confirming - * it's an appropriate data type (a float, in the case of readFloat, for example). If + * @param {Object} options + * @param {function} options.input Function invoked when asking for user input asynchronously. + * This function is invoked with the string of the prompt, i.e. readIntAsync('give me an int!'). + * The result of invoking onPrompt will be awaited, then parsed to configrm it's the + * appropriate data type (a float, in the case of readFloat, for example). If * onPrompt is undefined, window.prompt is used as a fallback. - * @param {function} options.onPrint Function invoked when printing. + * @param {function} options.output Function invoked when printing. * This function is invoked with any output, either in the case of explicit calls to `print` * or `println` or internal calls within the library. If onPrint is undefined, console.log * is used as a fallback. - * @param {function} options.onClear Function invoked when clear() is called. + * @param {function} options.clear Function invoked when clear() is called. */ configure(options = {}) { - this.onPrompt = options.onPrompt ?? this.onPrompt; - this.onPrint = options.onPrint ?? this.onPrint; - this.onClear = options.onClear ?? this.onClear; + this.onInput = options.input ?? this.onInput; + this.onOutput = options.output ?? this.onOutput; + this.onClear = options.clear ?? this.onClear; } /** @@ -77,7 +79,16 @@ class Console { * @param {string} promptString - The line to be printed before prompting. */ readLinePrivate(promptString) { - const input = this.onPrompt(promptString); + const input = prompt(promptString); + return input; + } + + /** + * Private method used to read a line using the read*Async methods. + * @param {string} promptString - The line to be printed before prompting. + */ + readLinePrivateAsync(promptString) { + const input = this.onInput(promptString); return input; } @@ -98,7 +109,7 @@ class Console { if (args.length < 1) { throw new Error('You should pass at least 1 argument to print'); } - this.onPrint(...args); + this.onOutput(...args); } /** @@ -116,7 +127,8 @@ class Console { } /** - * Read a number from the user using JavaScripts prompt function. + * Read a number from the user using `prompt` or the Console's {@link Console#onInput} function, depending + * on whether the caller was an async method (readLineAsync) or not (readLine) * We make sure here to check a few things. * * 1. If the user checks "Prevent this page from creating additional dialogs," we handle @@ -126,61 +138,76 @@ class Console { * which actually does not even allow floats, even they they can properly be parsed as ints. * 3. The errorMsgType is a string helping us figure out what to print if it is not of the right * type. - * @param {string} str - The prompt string - * @param {function} parseFn - A function to parse the user input to determine if it satisfies + * @param {string} str The prompt string + * @param {function} parseFn A function to parse the user input to determine if it satisfies * the expected datatype. If the return value of parseFn satisfies `!isNaN`, the value is * returned. If the result is null, the prompt will repeat until satisfied, or 100 prompts have * occurred. - * @param {string} errorMsgType - A strinng to include in the error message to the user explaining + * @param {string} errorMsgType A string to include in the error message to the user explaining * why a given input was rejected. For example, the errorMsgType "an integer," would result in * printing "That was not an integer. Please try again." if parseFn failed. + * @param {boolean} asynchronous A boolean indicating whether this function is being invoked asynchronously. + * If it is, then {@link readLinePrivateAsync} will be used to read input, which calls {@link Console#onInput}. * @returns {number} - * @global + * @private */ - readNumber(str, parseFn, errorMsgType) { - const DEFAULT = 0; // If we get into an infinite loop, return DEFAULT. - const INFINITE_LOOP_CHECK = 100; + readNumber(str, parseFn, errorMsgType, asynchronous) { + const DEFAULT = 0; // If we get into an infinite recursion, return DEFAULT. + const MAX_RECURSION_DEPTH = 100; + // a special indicator that th program should be exiting + const ABORT = Symbol('ABORT'); let promptString = str; - let loopCount = 0; - /** - * indicates whether the parsing has been successful, meaning - * it hasn't hit the INFINITE_LOOP_CHECK or null cases, and the input from the user has - * satisfied parseFn. in this case, the input should be printed. - * @type {boolean} - * */ - let successful = false; let parsedResult; - // eslint-disable-next-line no-constant-condition - while (true) { - const result = this.readLinePrivate(promptString); + const parseInput = result => { if (result === null) { - parsedResult = null; - successful = false; - break; + return ABORT; } parsedResult = parseFn(result); if (!isNaN(parsedResult)) { - successful = true; - break; + return parsedResult; } - if (parsedResult === null) { - successful = false; - break; + return null; + }; + const attemptInput = (promptString, depth, asynchronous) => { + if (depth >= MAX_RECURSION_DEPTH) { + return DEFAULT; } - if (loopCount > INFINITE_LOOP_CHECK) { - successful = false; - parsedResult = DEFAULT; - break; + const result = asynchronous + ? this.readLinePrivateAsync(promptString) + : this.readLinePrivate(promptString); + const next = result => { + return attemptInput( + `'${result}' was not ${errorMsgType}. Please try again.\n${str}`, + depth + 1, + asynchronous + ); + }; + if (Promise.resolve(result) === result) { + return result.then(result => { + const parsedResult = parseInput(result); + if (parsedResult === ABORT) { + return null; + } + if (parsedResult === null) { + return next(result); + } else { + return parsedResult; + } + }); + } else { + const parsedResult = parseInput(result); + if (parsedResult === ABORT) { + return null; + } + if (parsedResult === null) { + return next(result); + } else { + return parsedResult; + } } - promptString = `'${result}' was not ${errorMsgType}. Please try again.\n${str}`; - loopCount++; - } - if (successful) { - this.print(str); - this.println(parsedResult); - } - return parsedResult; + }; + return attemptInput(promptString, 0, asynchronous); } /** @@ -200,6 +227,26 @@ class Console { return result; } + /** + * Read a line asynchronously from the user. + * This will receive input via the Console's configured {@link Console#onInput} function, which by default + * will return a Promise that resolves with the result of using `window.prompt`, which will + * block the browser. + * @param {str} str - A message associated with the modal asking for input. + * @returns {Promise} The result of the prompt. + * @global + */ + async readLineAsync(str) { + if (arguments.length !== 1) { + throw new Error('You should pass exactly 1 argument to readLine'); + } + + const result = await this.readLinePrivateAsync(str); + this.print(str); + this.println(result); + return result; + } + /** * Read a bool from the user. * @param {str} str - A message associated with the modal asking for input. @@ -229,6 +276,39 @@ class Console { ); } + /** + * Read a bool from the user asynchronously. + * This will receive input via the Console's configured {@link Console#onInput} function, which by default + * will return a Promise that resolves with the result of using `window.prompt`, which will + * block the browser. + * @param {str} str - A message associated with the modal asking for input. + * @returns {Promise} The result of the onPrompt function if it's a boolean, or 0. + * @global + */ + async readBooleanAsync(str) { + if (arguments.length !== 1) { + throw new Error('You should pass exactly 1 argument to readBooleanAsync'); + } + return await this.readNumber( + str, + line => { + if (line === null) { + return NaN; + } + line = line.toLowerCase(); + if (line === 'true' || line === 'yes') { + return true; + } + if (line === 'false' || line === 'no') { + return false; + } + return NaN; + }, + 'a boolean (true/false)', + true + ); + } + /** * Read an int with our special parseInt function which doesnt allow floats, even * though they are successfully parsed as ints. @@ -257,7 +337,36 @@ class Console { } /** - * Read a float with our safe helper function. + * Read an int from the user asynchronously. + * This will receive input via the Console's configured {@link Console#onInput} function, which by default + * will return a Promise that resolves with the result of using `window.prompt`, which will + * block the browser. + * @param {str} str - A message associated with the modal asking for input. + * @returns {Promise} The result of the onPrompt function if it's an int, or 0. + * @global + */ + async readIntAsync(str) { + if (arguments.length !== 1) { + throw new Error('You should pass exactly 1 argument to readIntAsync'); + } + return await this.readNumber( + str, + function (x) { + var resultInt = parseInt(x); + var resultFloat = parseFloat(x); + // Make sure the value when parsed as both an int and a float are the same + if (resultInt === resultFloat) { + return resultInt; + } + return NaN; + }, + 'an integer', + true + ); + } + + /** + * Read a float from the user. * @param {str} str - A message associated with the modal asking for input. * @returns {str} The result of the readFloat prompt. * @global @@ -269,6 +378,22 @@ class Console { return this.readNumber(str, parseFloat, 'a float'); } + + /** + * Read a float from the user asynchronously. + * This will receive input via the Console's configured {@link Console#onInput} function, which by default + * will return a Promise that resolves with the result of using `window.prompt`, which will + * block the browser. + * @param {str} str - A message associated with the modal asking for input. + * @returns {Promise} The result of the onPrompt function if it's a float, or 0. + * @global + */ + async readFloatAsync(str) { + if (arguments.length !== 1) { + throw new Error('You should pass exactly 1 argument to readFloat'); + } + return await this.readNumber(str, parseFloat, 'a float', true); + } } export default Console; diff --git a/test/console.test.js b/test/console.test.js index e22a746c..7eaf1d9f 100644 --- a/test/console.test.js +++ b/test/console.test.js @@ -4,19 +4,19 @@ describe('Console', () => { describe('The constructor', () => { it('Allows prompt configuration', () => { const promptSpy = jasmine.createSpy(); - const c = new Console({ onPrompt: promptSpy }); - c.readLine('Give me a line: '); + const c = new Console({ input: promptSpy }); + c.readLineAsync('Give me a line: '); expect(promptSpy).toHaveBeenCalledOnceWith('Give me a line: '); }); - it('Allows pront configuration', () => { + it('Allows print configuration', () => { const printSpy = jasmine.createSpy(); - const c = new Console({ onPrint: printSpy }); + const c = new Console({ output: printSpy }); c.print('Hey!'); expect(printSpy).toHaveBeenCalledOnceWith('Hey!'); }); it('Allows clear configuration', () => { const clearSpy = jasmine.createSpy(); - const c = new Console({ onClear: clearSpy }); + const c = new Console({ clear: clearSpy }); c.clear(); expect(clearSpy).toHaveBeenCalledTimes(1); }); @@ -26,7 +26,7 @@ describe('Console', () => { it('Allows an async prompt', async () => { const c = new Console(); c.configure({ - onPrompt: async () => { + input: async () => { return await new Promise(resolve => setInterval(() => { resolve('nice'); @@ -34,12 +34,12 @@ describe('Console', () => { ); }, }); - expect(await c.readLine('Give me a line: ')).toBe('nice'); + expect(await c.readLineAsync('Give me a line: ')).toBe('nice'); }); it('Allows an async prompt to be treated as a Promise', () => { const c = new Console(); c.configure({ - onPrompt: async () => { + input: async () => { return await new Promise(resolve => setInterval(() => { resolve('nice'); @@ -47,7 +47,7 @@ describe('Console', () => { ); }, }); - return c.readLine('Give me a line: ').then(input => { + return c.readLineAsync('Give me a line: ').then(input => { expect(input).toBe('nice'); }); }); @@ -56,13 +56,13 @@ describe('Console', () => { spyOn(window, 'prompt').and.returnValue('nice'); c.configure({ // native prompt - onPrompt: window.prompt, + input: window.prompt, }); expect(c.readLine('Give me a line: ')).toBe('nice'); }); - it('Defaults to built-in onPrompt', () => { + it('Defaults to built-in window.prompt', () => { const c = new Console(); - const promptSpy = spyOn(c, 'onPrompt').and.returnValue('Nice!'); + const promptSpy = spyOn(window, 'prompt').and.returnValue('Nice!'); c.configure({}); expect(c.readLine('Give me a line: ')).toBe('Nice!'); expect(promptSpy).toHaveBeenCalledOnceWith('Give me a line: '); @@ -71,7 +71,7 @@ describe('Console', () => { describe('Configuring clear', () => { it('Defaults to an empty function', () => { const c = new Console(); - const clearSpy = spyOn(c, 'onClear'); + const clearSpy = spyOn(c, 'clear'); c.configure(); c.clear(); expect(clearSpy).toHaveBeenCalledTimes(1); @@ -79,7 +79,7 @@ describe('Console', () => { it('Allows a handler to be attached', () => { const c = new Console(); const clearSpy = jasmine.createSpy(); - c.configure({ onClear: clearSpy }); + c.configure({ clear: clearSpy }); c.clear(); expect(clearSpy).toHaveBeenCalledTimes(1); }); @@ -88,19 +88,30 @@ describe('Console', () => { it('Allows a handler to be attached', () => { const c = new Console(); const printSpy = jasmine.createSpy(); - c.configure({ onPrint: printSpy }); + c.configure({ output: printSpy }); c.print('Hellooo'); expect(printSpy).toHaveBeenCalledOnceWith('Hellooo'); }); }); }); describe('Input', () => { + describe('Aborting the prompt (default behavior in headless chrome)', () => { + it('Returns null for each input method', async () => { + const c = new Console(); + expect(c.readLine('')).toBeNull(); + expect(c.readInt('')).toBeNull(); + expect(c.readFloat('')).toBeNull(); + expect(c.readBoolean('')).toBeNull(); + expect(await c.readLineAsync('')).toBeNull(); + expect(await c.readIntAsync('')).toBeNull(); + expect(await c.readFloatAsync('')).toBeNull(); + expect(await c.readBooleanAsync('')).toBeNull(); + }); + }); describe('readLine', () => { it('Returns the input', () => { const c = new Console(); - // unfortunately we can't spy directly on the window.prompt, - // because it is bound within the constructor and therefore is a separate function - const promptSpy = spyOn(c, 'onPrompt').and.returnValue('Nice!'); + const promptSpy = spyOn(window, 'prompt').and.returnValue('Nice!'); expect(c.readLine('Next line? ')).toEqual('Nice!'); expect(promptSpy).toHaveBeenCalledOnceWith('Next line? '); }); @@ -110,6 +121,10 @@ describe('Console', () => { c.readLine('Next line? ', 'Oops'); }).toThrow(Error('You should pass exactly 1 argument to readLine')); }); + it('Defaults to null if the prompt is aborted', () => { + const c = new Console(); + expect(c.readLine('Next line?')).toBeNull(); + }); }); describe('readFloat', () => { it('Errors for >1 argument', () => { @@ -120,14 +135,8 @@ describe('Console', () => { }); it('Loops until a number is provided', () => { const inputs = ['one', 'two', 3.0]; - let i = 0; const c = new Console(); - const promptSpy = jasmine.createSpy(); - c.configure({ - onPrompt: (...args) => { - return promptSpy.and.returnValue(inputs[i++])(...args); - }, - }); + const promptSpy = spyOn(window, 'prompt').and.returnValues(...inputs); const int = c.readFloat('Give me a float: '); expect(promptSpy).toHaveBeenCalledTimes(3); expect(promptSpy.calls.allArgs()).toEqual([ @@ -149,12 +158,7 @@ describe('Console', () => { const inputs = ['one', 'two', 3]; let i = 0; const c = new Console(); - const promptSpy = jasmine.createSpy(); - c.configure({ - onPrompt: (...args) => { - return promptSpy.and.returnValue(inputs[i++])(...args); - }, - }); + const promptSpy = spyOn(window, 'prompt').and.returnValues(...inputs); const int = c.readInt('Give me a number: '); expect(promptSpy).toHaveBeenCalledTimes(3); expect(promptSpy.calls.allArgs()).toEqual([ @@ -164,16 +168,23 @@ describe('Console', () => { ]); expect(int).toBe(3); }); - it('Exits with default after 100 invalid inputs', () => { + it('Exits with default after 100 invalid inputs with default prompt', () => { + const c = new Console(); + const promptSpy = spyOn(window, 'prompt').and.returnValue('invalid'); + const int = c.readInt('Give me a number: '); + expect(promptSpy).toHaveBeenCalledTimes(100); + expect(int).toBe(0); + }); + it('Exits with default after 100 invalid inputs with async prompt', async () => { const c = new Console(); const promptSpy = jasmine.createSpy(); c.configure({ - onPrompt: (...args) => { + input: (...args) => { return promptSpy.and.returnValue('invalid')(...args); }, }); - const int = c.readInt('Give me a number: '); - expect(promptSpy).toHaveBeenCalledTimes(102); + const int = await c.readIntAsync('Give me a number: '); + expect(promptSpy).toHaveBeenCalledTimes(100); expect(int).toBe(0); }); }); @@ -188,11 +199,7 @@ describe('Console', () => { const c = new Console(); const inputs = ['yes', 'no']; let i = 0; - c.configure({ - onPrompt: (...args) => { - return inputs[i++]; - }, - }); + spyOn(window, 'prompt').and.returnValues(...inputs); expect(c.readBoolean('')).toBeTrue(); expect(c.readBoolean('')).toBeFalse(); }); @@ -200,11 +207,7 @@ describe('Console', () => { const c = new Console(); const inputs = ['true', 'false']; let i = 0; - c.configure({ - onPrompt: (...args) => { - return inputs[i++]; - }, - }); + spyOn(window, 'prompt').and.returnValues(...inputs); expect(c.readBoolean('')).toBeTrue(); expect(c.readBoolean('')).toBeFalse(); }); @@ -212,12 +215,7 @@ describe('Console', () => { const inputs = ['nope', 'yep', 'yes']; let i = 0; const c = new Console(); - const promptSpy = jasmine.createSpy(); - c.configure({ - onPrompt: (...args) => { - return promptSpy.and.returnValue(inputs[i++])(...args); - }, - }); + const promptSpy = spyOn(window, 'prompt').and.returnValues(...inputs); const bool = c.readBoolean('Give me a bool: '); expect(promptSpy).toHaveBeenCalledTimes(3); expect(promptSpy.calls.allArgs()).toEqual([ @@ -240,7 +238,7 @@ describe('Console', () => { it("Doesn't add a newline to its output", () => { const c = new Console(); const printSpy = jasmine.createSpy(); - c.configure({ onPrint: printSpy }); + c.configure({ output: printSpy }); c.print('Hello!'); expect(printSpy).toHaveBeenCalledOnceWith('Hello!'); }); @@ -260,17 +258,135 @@ describe('Console', () => { it('Prints an empty newline for no input', () => { const c = new Console(); const printSpy = jasmine.createSpy(); - c.configure({ onPrint: printSpy }); + c.configure({ output: printSpy }); c.println(); expect(printSpy).toHaveBeenCalledOnceWith('', '\n'); }); it('Adds a newline to its output', () => { const c = new Console(); const printSpy = jasmine.createSpy(); - c.configure({ onPrint: printSpy }); + c.configure({ output: printSpy }); c.println('Hello!'); expect(printSpy).toHaveBeenCalledOnceWith('Hello!', '\n'); }); }); }); + describe('Asynchronous inputs', () => { + describe('readLineAsync', () => { + it('Returns a promise', () => { + const c = new Console(); + expect(typeof c.readLineAsync('give me a line!').then).toBe('function'); + }); + it('Will not reprompt', async () => { + const c = new Console({ + input: promptMessage => { + return promptMessage; + }, + }); + expect(await c.readLineAsync('echo')).toEqual('echo'); + }); + }); + describe('readIntAsync', () => { + it('Returns a promise', () => { + const c = new Console(); + expect(typeof c.readIntAsync('Give me an int!').then).toBe('function'); + }); + it('Will reprompt', async () => { + let i = 0; + const promptSpy = jasmine.createSpy(); + const c = new Console({ + input: promptMessage => { + promptSpy(promptMessage); + i += 1; + if (i < 3) { + return 'huh?'; + } + return 12321; + }, + }); + expect(await c.readIntAsync('input pls')).toEqual(12321); + expect(promptSpy.calls.allArgs()).toEqual([ + ['input pls'], + ["'huh?' was not an integer. Please try again.\ninput pls"], + ["'huh?' was not an integer. Please try again.\ninput pls"], + ]); + }); + it('Will default after 100 loops', async () => { + const promptSpy = jasmine.createSpy(); + const c = new Console({ + input: promptMessage => { + promptSpy(promptMessage); + return 'ack'; + }, + }); + expect(await c.readIntAsync('input pls')).toEqual(0); + expect(promptSpy).toHaveBeenCalledTimes(100); + }); + }); + describe('readFloatAsync', () => { + it('Returns a promise', () => { + const c = new Console(); + expect(typeof c.readFloatAsync('give me a float!').then).toBe('function'); + }); + it('Will reprompt', async () => { + let i = 0; + const promptSpy = jasmine.createSpy(); + const c = new Console({ + input: promptMessage => { + promptSpy(promptMessage); + i += 1; + if (i < 3) { + return 'huh?'; + } + return 12.1212; + }, + }); + expect(await c.readFloatAsync('input pls')).toEqual(12.1212); + expect(promptSpy.calls.allArgs()).toEqual([ + ['input pls'], + ["'huh?' was not a float. Please try again.\ninput pls"], + ["'huh?' was not a float. Please try again.\ninput pls"], + ]); + }); + }); + describe('readBooleanAsync', () => { + it('Returns a promise', () => { + const c = new Console(); + expect(typeof c.readBooleanAsync('Give me a boolean').then).toBe('function'); + }); + it('Will reprompt', async () => { + let i = 0; + const promptSpy = jasmine.createSpy(); + const c = new Console({ + input: promptMessage => { + promptSpy(promptMessage); + i += 1; + if (i < 3) { + return 'huh?'; + } + return 'true'; + }, + }); + expect(await c.readBooleanAsync('input pls')).toEqual(true); + expect(promptSpy.calls.allArgs()).toEqual([ + ['input pls'], + ["'huh?' was not a boolean (true/false). Please try again.\ninput pls"], + ["'huh?' was not a boolean (true/false). Please try again.\ninput pls"], + ]); + }); + }); + describe('Not configuring an inputAsync', () => { + it('Will still return a promise', () => { + const c = new Console(); + const result = c.readLineAsync('Give me a line!'); + // this is how you check that something is a Promise! + expect(Promise.resolve(result)).toEqual(result); + }); + it('Will still abort with null', async () => { + const c = new Console(); + const result = c.readIntAsync('Give me an int!'); + expect(await result).toEqual(null); + }); + }); + }); });