Guest User

XCKD Apr1 comic.js

a guest
Apr 1st, 2021
239
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /******/ (() => { // webpackBootstrap
  2. /******/ var __webpack_modules__ = ({
  3.  
  4. /***/ 79:
  5. /***/ ((module) => {
  6.  
  7. module.exports = {
  8. name: 'Beep',
  9. alt: 'Check check check ... chhecck chhecck chhecck ... check check check',
  10. url: '/2445',
  11. width: 740,
  12. height: 448,
  13. apiEndpoint: '/2445/morse',
  14. }
  15.  
  16.  
  17. /***/ }),
  18.  
  19. /***/ 369:
  20. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  21.  
  22. "use strict";
  23.  
  24. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  25. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  26. return new (P || (P = Promise))(function (resolve, reject) {
  27. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  28. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  29. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  30. step((generator = generator.apply(thisArg, _arguments || [])).next());
  31. });
  32. };
  33. Object.defineProperty(exports, "__esModule", ({ value: true }));
  34. const comic_1 = __webpack_require__(79);
  35. const SEND_DELAY = 3000;
  36. const DOT_LENGTH = 150;
  37. const HUD_DELAY = 3000;
  38. const IDLE_DELAY = 30 * 1000;
  39. function clickTimesToMorse(clickTimes) {
  40. return clickTimes
  41. .map((t) => {
  42. if (t < 0) {
  43. return Math.abs(t) > DOT_LENGTH * 3 ? ' ' : '';
  44. }
  45. else {
  46. return t > DOT_LENGTH * 3 ? '-' : '.';
  47. }
  48. })
  49. .join('')
  50. .replace(/^ /, ''); // Strip a preceding space
  51. }
  52. class Client {
  53. escape(t) {
  54. return t.replace(/ /g, '_');
  55. }
  56. say(morse) {
  57. return __awaiter(this, void 0, void 0, function* () {
  58. if (morse === '.' || morse === '..') {
  59. // OK, so this is funny: after taking great pains to make our API entirely
  60. // use morse code paths, we discovered that the paths '.' and '..' would
  61. // not be sent by the browser. As it turns out, these paths have special
  62. // meaning: they refer to the current and parent directory, respectively,
  63. // and the browser actually changes the URL before sending it down the
  64. // wire!
  65. morse = '_' + morse;
  66. }
  67. const url = [
  68. comic_1.apiEndpoint,
  69. '/.../',
  70. this.stateId ? this.escape(this.stateId) + '/' : '',
  71. this.escape(morse),
  72. ].join('');
  73. const resp = yield fetch(url);
  74. if (!resp.ok) {
  75. throw new Error(`Unexpected response ${resp}`);
  76. }
  77. const data = yield resp.text();
  78. const [state, ...respMorse] = data.split(' / ');
  79. this.stateId = state.trim();
  80. return respMorse.join(' / ').trim();
  81. });
  82. }
  83. open(key) {
  84. const morseKey = window.morse.encode(key);
  85. window.open(`${comic_1.apiEndpoint}/.-./${this.escape(morseKey)}`);
  86. }
  87. }
  88. class Speaker {
  89. constructor(parentEl) {
  90. this.TARGET_GAIN = 0.2;
  91. this.isEnabled = false;
  92. this.ctx = null;
  93. this.gainNode = null;
  94. this.parentEl = parentEl;
  95. this.el = document.createElement('button');
  96. this.el.setAttribute('style', `
  97. position: absolute;
  98. bottom: 6px;
  99. right: 6px;
  100. background: none;
  101. border: none;
  102. font-size: 20px;
  103. line-height: 20px;
  104. text-align: center;
  105. width: 30px;
  106. height: 30px;
  107. padding: 2px;
  108. box-sizing: content-box;
  109. opacity: 0;
  110. filter: grayscale(1);
  111. transition: all 5s linear;
  112. `);
  113. this.el.addEventListener('click', () => {
  114. if (this.isEnabled) {
  115. this.disable();
  116. }
  117. else {
  118. this.enable();
  119. }
  120. });
  121. this.parentEl.appendChild(this.el);
  122. this.update();
  123. }
  124. show() {
  125. this.el.style.opacity = '.2';
  126. }
  127. update() {
  128. this.el.innerText = this.isEnabled ? '🔊' : '🔇';
  129. this.el.title = this.isEnabled ? 'Mute sound' : 'Enable sound';
  130. }
  131. enable() {
  132. this.isEnabled = true;
  133. this.update();
  134. }
  135. disable() {
  136. this.isEnabled = false;
  137. this.off();
  138. this.update();
  139. }
  140. on() {
  141. if (!this.isEnabled) {
  142. return;
  143. }
  144. if (!this.ctx) {
  145. this.ctx = new AudioContext();
  146. const oscNode = this.ctx.createOscillator();
  147. this.gainNode = this.ctx.createGain();
  148. oscNode.type = 'sine';
  149. oscNode.frequency.value = 440;
  150. oscNode.connect(this.gainNode);
  151. this.gainNode.gain.value = 0;
  152. this.gainNode.connect(this.ctx.destination);
  153. oscNode.start();
  154. }
  155. this.gainNode.gain.cancelScheduledValues(this.ctx.currentTime);
  156. this.gainNode.gain.linearRampToValueAtTime(this.TARGET_GAIN, this.ctx.currentTime + 0.01);
  157. }
  158. off() {
  159. if (!this.ctx) {
  160. return;
  161. }
  162. this.gainNode.gain.linearRampToValueAtTime(0, this.ctx.currentTime + DOT_LENGTH / 1000 / 2);
  163. }
  164. }
  165. class MorseHUD {
  166. constructor(parentEl) {
  167. this.HUD_LENGTH = 6;
  168. this.HUD_CHAR_WIDTH = 50;
  169. this.HUD_CHAR_SIZE = 7;
  170. this.HUD_ANIM_DURATION = DOT_LENGTH;
  171. this.HUD_FAST_ANIM_DURATION = DOT_LENGTH / 2;
  172. this.parentEl = parentEl;
  173. this.el = document.createElement('div');
  174. this.el.setAttribute('style', `
  175. position: absolute;
  176. bottom: 50px;
  177. width: ${this.HUD_LENGTH * this.HUD_CHAR_WIDTH}px;
  178. height: ${this.HUD_CHAR_WIDTH}px;
  179. left: 50%;
  180. opacity: 0;
  181. transform: translateX(-50%);
  182. transition: all 5s linear;
  183. pointer-events: none;
  184. `);
  185. this.parentEl.appendChild(this.el);
  186. this.lastMorse = '';
  187. this.elStack = [];
  188. this.shown = false;
  189. }
  190. show() {
  191. this.el.style.opacity = '1';
  192. }
  193. update(morse) {
  194. const lastMorse = this.lastMorse;
  195. this.lastMorse = morse;
  196. if (morse.length === 0) {
  197. const oldStack = this.elStack;
  198. this.elStack = [];
  199. for (const el of oldStack) {
  200. el.style.opacity = '0';
  201. el.style.transitionDuration = `${this.HUD_ANIM_DURATION * 2}ms`;
  202. }
  203. setTimeout(() => {
  204. for (const el of oldStack) {
  205. el.parentElement.removeChild(el);
  206. }
  207. }, this.HUD_ANIM_DURATION);
  208. return;
  209. }
  210. const newCount = morse.length - lastMorse.length;
  211. for (let i = 0; i < newCount; i++) {
  212. const newBoxEl = document.createElement('div');
  213. newBoxEl.setAttribute('style', `
  214. position: absolute;
  215. right: 0;
  216. display: flex;
  217. align-items: center;
  218. justify-content: center;
  219. width: ${this.HUD_CHAR_WIDTH}px;
  220. opacity: 0;
  221. transition: all ${this.HUD_ANIM_DURATION}ms ease-out;
  222. transform: translateY(7px);
  223. `);
  224. const newCharEl = document.createElement('div');
  225. newCharEl.setAttribute('style', `
  226. background: #999;
  227. transition: all ${this.HUD_FAST_ANIM_DURATION}ms ease-out;
  228. height: ${this.HUD_CHAR_SIZE}px;
  229. `);
  230. newBoxEl.appendChild(newCharEl);
  231. this.el.appendChild(newBoxEl);
  232. this.el.offsetTop; // Force layout to set transition start values
  233. this.elStack.unshift(newBoxEl);
  234. }
  235. for (const [idx, boxEl] of this.elStack.entries()) {
  236. const char = morse[morse.length - 1 - idx];
  237. const charEl = boxEl.children[0];
  238. if (char === '.') {
  239. charEl.style.width = `${this.HUD_CHAR_SIZE}px`;
  240. charEl.style.borderRadius = `${this.HUD_CHAR_SIZE}px`;
  241. charEl.style.opacity = '1';
  242. }
  243. else if (char === '-') {
  244. charEl.style.width = `${Math.floor(3.5 * this.HUD_CHAR_SIZE)}px`;
  245. charEl.style.borderRadius = '2px';
  246. charEl.style.opacity = '1';
  247. }
  248. else {
  249. charEl.style.opacity = '0';
  250. }
  251. boxEl.style.opacity = '1';
  252. const x = -idx * this.HUD_CHAR_WIDTH;
  253. boxEl.style.transform = `translateX(${Math.floor(x)}px)`;
  254. }
  255. while (this.elStack.length > this.HUD_LENGTH) {
  256. const oldBoxEl = this.elStack.pop();
  257. oldBoxEl.style.opacity = '0';
  258. setTimeout(() => {
  259. this.el.removeChild(oldBoxEl);
  260. }, this.HUD_ANIM_DURATION);
  261. }
  262. }
  263. }
  264. class Comic {
  265. constructor(el) {
  266. this.el = el;
  267. this.hud = new MorseHUD(el);
  268. this.speaker = new Speaker(this.el);
  269. this.isHUDShown = false;
  270. this.hasInteracted = false;
  271. this.sendTimeout = null;
  272. this.updateTimeout = null;
  273. this.client = new Client();
  274. this.clickTimes = [];
  275. this.playbackDelay = 250;
  276. }
  277. start() {
  278. const { el } = this;
  279. const inputEl = el.querySelector('input');
  280. const labelEl = el.querySelector('label');
  281. let keyHeld = false;
  282. this.lastOff = Date.now();
  283. this.lastOn = 0;
  284. const setOn = (isOn) => {
  285. inputEl.checked = isOn;
  286. if (isOn) {
  287. this.lastOn = Date.now();
  288. this.speaker.on();
  289. }
  290. else {
  291. this.lastOff = Date.now();
  292. this.speaker.off();
  293. }
  294. this.clickTimes.push(this.lastOff - this.lastOn);
  295. };
  296. const handleDown = (ev) => {
  297. ev.preventDefault();
  298. clearTimeout(this.playTimeout);
  299. clearTimeout(this.sendTimeout);
  300. setOn(true);
  301. this.hasInteracted = true;
  302. this.showHUD();
  303. this.updateHUD();
  304. };
  305. const handleUp = (ev) => {
  306. // Since this is hooked up to global event handlers, we must ensure the checkbox is actually being held.
  307. if (this.lastOn < this.lastOff) {
  308. return;
  309. }
  310. ev.preventDefault();
  311. setOn(false);
  312. this.updateHUD();
  313. this.interpretClicks();
  314. inputEl.focus();
  315. };
  316. labelEl.addEventListener('mousedown', handleDown);
  317. labelEl.addEventListener('touchstart', handleDown);
  318. labelEl.addEventListener('keydown', (ev) => {
  319. if (!this.el.contains(ev.target)) {
  320. return;
  321. }
  322. if (!keyHeld && (ev.key === ' ' || ev.key === 'Enter')) {
  323. keyHeld = true;
  324. handleDown(ev);
  325. }
  326. });
  327. window.addEventListener('mouseup', handleUp);
  328. window.addEventListener('touchend', handleUp);
  329. window.addEventListener('keyup', (ev) => {
  330. if (ev.key === ' ' || ev.key === 'Enter') {
  331. keyHeld = false;
  332. handleUp(ev);
  333. }
  334. });
  335. labelEl.addEventListener('click', (ev) => {
  336. ev.preventDefault();
  337. });
  338. this.printIntro();
  339. setTimeout(() => {
  340. if (!this.hasInteracted) {
  341. this.playback(window.morse.encode('CQ'));
  342. }
  343. }, IDLE_DELAY);
  344. }
  345. printIntro() {
  346. const lines = [];
  347. lines.push(' MORSE CODE ');
  348. lines.push('------------');
  349. for (const [char, code] of window.morse.table) {
  350. lines.push(` ${char} [${code}]`);
  351. }
  352. console.log(lines.join('\n'));
  353. }
  354. showHUD() {
  355. if (!this.isHUDShown) {
  356. this.isHUDShown = true;
  357. setTimeout(() => {
  358. this.hud.show();
  359. }, HUD_DELAY);
  360. setTimeout(() => {
  361. this.speaker.show();
  362. }, HUD_DELAY * 2);
  363. }
  364. }
  365. updateHUD() {
  366. // Display as if the user took a final action now.
  367. let clickTimes;
  368. const now = Date.now();
  369. if (this.lastOn > this.lastOff) {
  370. clickTimes = [...this.clickTimes, now - this.lastOn + 1];
  371. }
  372. else {
  373. clickTimes = [...this.clickTimes, this.lastOff - now - 1];
  374. }
  375. let morse = clickTimesToMorse(clickTimes);
  376. if (this.lastOff > this.lastOn) {
  377. // Add a blank space for the next character if not holding down.
  378. morse += ' ';
  379. }
  380. this.hud.update(morse);
  381. clearTimeout(this.updateTimeout);
  382. this.updateTimeout = window.setTimeout(() => {
  383. this.updateHUD();
  384. }, DOT_LENGTH);
  385. }
  386. interpretClicks() {
  387. const morse = clickTimesToMorse(this.clickTimes);
  388. clearTimeout(this.sendTimeout);
  389. this.sendTimeout = window.setTimeout(() => {
  390. this.send(morse);
  391. }, this.impatient ? DOT_LENGTH * 7 : SEND_DELAY);
  392. }
  393. send(morse) {
  394. return __awaiter(this, void 0, void 0, function* () {
  395. this.hasInteracted = true;
  396. const newClickTimes = [];
  397. this.clickTimes = newClickTimes;
  398. this.hud.update('');
  399. const text = window.morse.decode(morse);
  400. console.log(`Said: [${morse}] "${text}"`);
  401. this.handleCommand(text);
  402. const responseMorse = yield this.client.say(morse);
  403. if (this.clickTimes != newClickTimes || this.clickTimes.length) {
  404. // If the user has input anything since, disregard this response.
  405. return;
  406. }
  407. this.playback(responseMorse);
  408. });
  409. }
  410. playback(morse) {
  411. const text = window.morse.decode(morse);
  412. if (this.impatient) {
  413. console.log(`Received: [${window.morse.encode(text)}] "${text}"`);
  414. }
  415. this.handleAction(text);
  416. const inputEl = this.el.querySelector('input');
  417. const delays = [];
  418. for (const c of morse) {
  419. if (c === '.') {
  420. delays.push([true, this.playbackDelay]);
  421. delays.push([false, this.playbackDelay]);
  422. }
  423. else if (c === '-') {
  424. delays.push([true, this.playbackDelay * 3]);
  425. delays.push([false, this.playbackDelay]);
  426. }
  427. else if (c === ' ') {
  428. delays.push([false, this.playbackDelay * 3]);
  429. }
  430. }
  431. delays.push([false, this.playbackDelay * 7]);
  432. let idx = 0;
  433. let hasPrinted = false;
  434. const tick = () => {
  435. const [isOn, delay] = delays[idx];
  436. inputEl.checked = isOn;
  437. if (isOn) {
  438. this.speaker.on();
  439. }
  440. else {
  441. this.speaker.off();
  442. }
  443. if (!this.impatient && idx === delays.length - 1 && !hasPrinted) {
  444. console.log(`Received: [${window.morse.encode(text)}] "${text}"`);
  445. hasPrinted = true;
  446. }
  447. idx = (idx + 1) % delays.length;
  448. clearTimeout(this.playTimeout);
  449. this.playTimeout = window.setTimeout(tick, delay);
  450. };
  451. tick();
  452. }
  453. handleCommand(text) {
  454. if (text === 'BEEP') {
  455. this.speaker.enable();
  456. }
  457. else if (text === 'MUTE' || text === 'QUIET') {
  458. this.speaker.disable();
  459. }
  460. else if (text === 'QRS') {
  461. this.playbackDelay = Math.min(500, this.playbackDelay * 1.2);
  462. }
  463. else if (text === 'QRQ') {
  464. this.playbackDelay = Math.max(50, this.playbackDelay * (1 / 1.2));
  465. }
  466. }
  467. handleAction(text) {
  468. if (text.startsWith('//')) {
  469. this.client.open(text.substr(2));
  470. }
  471. }
  472. }
  473. exports.default = Comic;
  474.  
  475.  
  476. /***/ }),
  477.  
  478. /***/ 607:
  479. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  480.  
  481. "use strict";
  482.  
  483. var __importDefault = (this && this.__importDefault) || function (mod) {
  484. return (mod && mod.__esModule) ? mod : { "default": mod };
  485. };
  486. Object.defineProperty(exports, "__esModule", ({ value: true }));
  487. exports.BeepComicGlobal = void 0;
  488. const comic_1 = __importDefault(__webpack_require__(79));
  489. const Comic_1 = __importDefault(__webpack_require__(369));
  490. class BeepComicGlobal {
  491. constructor() {
  492. this.comicEl = null;
  493. }
  494. draw(comicEl) {
  495. comicEl.setAttribute('style', `
  496. position: relative;
  497. display: inline-flex;
  498. width: ${comic_1.default.width}px;
  499. height: ${comic_1.default.height}px;
  500. box-sizing: border-box;
  501. border: 2px solid black;
  502. `);
  503. comicEl.innerHTML = `
  504. <label style="display: flex; width: 100%; height: 100%; align-items: center; justify-content: center">
  505. <input type="checkbox" style="outline: none">
  506. </label>
  507. `;
  508. comicEl.title = comic_1.default.alt;
  509. this.comicEl = comicEl;
  510. }
  511. }
  512. exports.BeepComicGlobal = BeepComicGlobal;
  513. function main() {
  514. const { comicEl } = window.BeepComic;
  515. const comic = new Comic_1.default(comicEl);
  516. comic.start();
  517. window.BeepComic.send = (morse) => {
  518. comic.send(morse);
  519. };
  520. window.BeepComic.hurryUp = () => {
  521. comic.impatient = true;
  522. return "Ok fine. But you're like, totally breaking the immersion.";
  523. };
  524. }
  525. document.addEventListener('DOMContentLoaded', main);
  526. window.BeepComic = new BeepComicGlobal();
  527.  
  528.  
  529. /***/ })
  530.  
  531. /******/ });
  532. /************************************************************************/
  533. /******/ // The module cache
  534. /******/ var __webpack_module_cache__ = {};
  535. /******/
  536. /******/ // The require function
  537. /******/ function __webpack_require__(moduleId) {
  538. /******/ // Check if module is in cache
  539. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  540. /******/ if (cachedModule !== undefined) {
  541. /******/ return cachedModule.exports;
  542. /******/ }
  543. /******/ // Create a new module (and put it into the cache)
  544. /******/ var module = __webpack_module_cache__[moduleId] = {
  545. /******/ // no module.id needed
  546. /******/ // no module.loaded needed
  547. /******/ exports: {}
  548. /******/ };
  549. /******/
  550. /******/ // Execute the module function
  551. /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  552. /******/
  553. /******/ // Return the exports of the module
  554. /******/ return module.exports;
  555. /******/ }
  556. /******/
  557. /************************************************************************/
  558. /******/
  559. /******/ // startup
  560. /******/ // Load entry module and return exports
  561. /******/ // This entry module is referenced by other modules so it can't be inlined
  562. /******/ var __webpack_exports__ = __webpack_require__(607);
  563. /******/
  564. /******/ })()
  565. ;
RAW Paste Data