UNPKG

@bitblit/asyncglk

Version:
227 lines (223 loc) 7.19 kB
/* Generic GlkOte implementation ============================= Copyright (c) 2024 Dannii Willis MIT licenced https://github.com/curiousdannii/asyncglk */ import { Blorb } from '../../blorb/blorb.js'; import * as Constants from '../../common/constants.js'; import * as protocol from '../../common/protocol.js'; import { filetype_to_extension } from '../../dialog/common/common.js'; export class GlkOteBase { classname = 'GlkOte'; version = Constants.PACKAGE_VERSION; accept_func = () => { }; autorestoring = false; Blorb; current_metrics = Object.assign({}, Constants.DEFAULT_METRICS); Dialog; disabled = false; generation = 0; is_inited = false; options = {}; timer = null; waiting_for_update = false; async init(options) { if (!options) { return this.error('no options provided'); } if (!options.accept) { return this.error('an accept function was not given to GlkOte'); } this.options = options; this.accept_func = options.accept; if (options.Blorb) { this.Blorb = options.Blorb; } if (options.Dialog) { this.Dialog = options.Dialog; } this.is_inited = true; // Send an init event, which if is received by GlkApi, will then result in VM.start() being called this.send_event({ type: 'init' }); } error(error) { throw (typeof error === 'string' ? new Error(error) : error); } extevent(value) { this.send_event({ type: 'external', value, }); } getdomcontext() { throw new Error('getdomcontext is not applicable to this GlkOte library'); } getdomid(name) { throw new Error('getdomid is not applicable to this GlkOte library'); } getinterface() { return this.options; } getlibrary(name) { switch (name) { case 'Blorb': return this.Blorb; case 'Dialog': return this.Dialog; default: return null; } } inited() { return this.is_inited; } log(msg) { console.log(msg); } setdomcontext(val) { throw new Error('setdomcontext is not applicable to this GlkOte library'); } update(data) { try { this.autorestoring = false; this.waiting_for_update = false; if (data.type === 'error') { return this.error(data.message); } if (data.type === 'pass') { return; } if (data.type === 'retry') { setTimeout(() => this.send_event({ type: 'refresh' }), 2000); return; } if (data.type !== 'update') { return this.error(`Unknown update type: ${data.type}`); } if (data.gen === this.generation) { this.log(`Ignoring repeated generation number: ${data.gen}`); return; } this.generation = data.gen; if (this.disabled) { this.disable(false); } // Now handle all the state updates if (data.input) { this.cancel_inputs(data.input); } if (data.windows) { this.update_windows(data.windows); } if (data.content) { this.update_content(data.content); } if (data.input) { this.update_inputs(data.input); } if (data.timer !== undefined) { if (this.timer) { clearInterval(this.timer); this.timer = null; } if (data.timer) { this.timer = setInterval(() => this.ontimer(), data.timer); } } if (data.specialinput) { this.handle_specialinput(data.specialinput); } // Disable everything if requested this.disabled = false; if (data.disable || data.specialinput) { this.disable(true); } if (data.autorestore) { this.autorestoring = true; this.autorestore(data.autorestore); } // Page background colour if (typeof data.page_margin_bg !== 'undefined' && this.options.set_body_to_page_bg) { this.set_page_bg(data.page_margin_bg); } if (data.disable) { this.exit(); } } catch (err) { this.error(err); } } warning(msg) { console.warn(msg); } // AsyncGlk specific implementation methods autorestore(data) { } capabilities() { return ['timer']; } exit() { } handle_specialinput(data) { if (data.type === 'fileref_prompt') { const replyfunc = (ref) => this.send_event({ type: 'specialresponse', response: 'fileref_prompt', value: ref, }); try { if (!this.Dialog) { setTimeout(() => replyfunc(null), 0); } else { if (this.Dialog.async) { this.Dialog.prompt(filetype_to_extension(data.filetype), data.filemode !== 'read').then(filename => { replyfunc(filename ? { filename } : null); }); } else { this.Dialog.open(data.filemode !== 'read', data.filetype, data.gameid, replyfunc); } } } catch (ex) { this.log(`Unable to open file dialog: ${ex}`); /* Return a failure. But we don't want to call send_response before glkote_update has finished, so we defer the reply slightly. */ setTimeout(() => replyfunc(null), 0); } } else { this.error(`Request for unknown special input type: ${data.type}`); } } ontimer() { if (!this.disabled && this.timer) { this.send_event({ type: 'timer' }); } } save_allstate() { } send_event(ev) { if (this.disabled && ev.type !== 'specialresponse') { return; } if (this.waiting_for_update) { console.log('Trying to send event when waiting for input', ev); return; } this.waiting_for_update = true; ev.gen = this.generation; switch (ev.type) { case 'arrange': ev.metrics = this.current_metrics; break; case 'init': ev.metrics = this.current_metrics; ev.support = this.capabilities(); break; case 'specialresponse': ev.response = 'fileref_prompt'; break; } this.accept_func(ev); } set_page_bg(colour) { } }