import { PythagorasIncomingMessageType, type PythagorasClient, type PythagorasIncomingMessage } from "./pythagoras_client"; import { ScrollingTextBox } from "./scrolling_textbox"; import { dict, IDLE_LOGOS, QUESTION_LINK, QUESTION_QR } from "./settings"; import { AsyncRunner, el, sleep, wait_for_dom_refresh } from "./tools"; interface PresentationScreen { prepare(): Promise; start(): Promise; end(): Promise; serve(trigger: PythagorasIncomingMessage): Promise; dom: HTMLDivElement; } export class PresentationManager { private screen: PresentationScreen; private dom_root: HTMLDivElement; public constructor() { this.dom_root = el.div([]); this.screen = new BlankScreen(); } private async update_dom_screen(): Promise { await this.screen.prepare(); this.dom_root.innerHTML = ""; this.dom_root.appendChild(this.screen.dom); await wait_for_dom_refresh(); } public async serve(ws_client: PythagorasClient): Promise { while (true) { const received = await ws_client.recv(); if (received.type == PythagorasIncomingMessageType.SetScreen) { await this.screen.end(); this.set_screen(received.screen); await this.update_dom_screen(); await this.screen.start(); continue; } await this.screen.serve(received); } } private set_screen(screen: "idle" | "video" | "main"): void { switch (screen) { case "idle": this.screen = new IdleScreen(); break; case "video": this.screen = new VideoScreen(); break; case "main": this.screen = new MainScreen(); break; default: throw new Error("unknown screen id"); } } public get dom(): HTMLDivElement { return this.dom_root; } } class MainScreen implements PresentationScreen { private dom_root: HTMLDivElement; private subs_english: AsyncRunner; private subs_czech: AsyncRunner; private dom_question: HTMLDivElement; private dom_question_link: HTMLDivElement; private question_insert: HTMLParagraphElement; public constructor() { const subs_en = new ScrollingTextBox(); const subs_cz = new ScrollingTextBox(); this.subs_english = new AsyncRunner(subs_en); this.subs_czech = new AsyncRunner(subs_cz); this.question_insert = el.p("Otázka"); this.dom_question_link = el.div([el.h1(QUESTION_LINK)], ["link"]); this.dom_question = el.div([this.dom_question_link, this.question_insert], ["question"]); this.dom_root = el.div([ this.dom_question, el.div([ el.div([subs_en.dom], ["lang"]), el.div([subs_cz.dom], ["lang"]), ], ["subtitles"]), el.div([ el.p("Jedná se o automaticky generovaný přepis. Omluvte, prosíme, případné chyby.", ["disclaimer"]), ]), ], ["main"]); } public async prepare(): Promise { //this.dom_question_link.append(await el.img(QUESTION_QR, ["qr"])); } public async start(): Promise { await wait_for_dom_refresh(); this.dom_root.style.opacity = "1"; await sleep(500); } public async end(): Promise { this.dom_root.style.opacity = "0"; await sleep(500); } private async show_question(text: string): Promise { this.question_insert.innerText = text; this.question_insert.style.opacity = "1"; await sleep(500); } private async hide_question(): Promise { this.question_insert.style.opacity = "0"; await sleep(500); } public async serve(trigger: PythagorasIncomingMessage): Promise { switch (trigger.type) { case PythagorasIncomingMessageType.SubEnUpdateCur: this.subs_english.run((target: ScrollingTextBox) => { return target.update_current(trigger.text) }); break; case PythagorasIncomingMessageType.SubEnSubmit: this.subs_english.run((target: ScrollingTextBox) => { return target.finish_current(trigger.text); }); break; case PythagorasIncomingMessageType.SubCzSubmit: this.subs_czech.run((target: ScrollingTextBox) => { return target.add_line(trigger.text); }); break; case PythagorasIncomingMessageType.SelectedMessage: if (trigger.message === null) { this.hide_question(); } else { this.show_question(trigger.message); } break; } } public get dom(): HTMLDivElement { return this.dom_root; } } class VideoScreen implements PresentationScreen { private dom_root: HTMLDivElement; private dom_video: HTMLVideoElement; private src: string | null; private subtitles: string | null; public constructor() { this.src = null; this.subtitles = null; this.dom_video = document.createElement("video"); this.dom_root = el.div([this.dom_video], ["video"]); } public async prepare(): Promise { this.dom_root.innerHTML = ""; if (this.src !== null) { this.dom_video = await el.video( this.src, this.subtitles === null ? "" : this.subtitles ); this.dom_video.volume = 0; this.dom_root.appendChild(this.dom_video); } } public async start(): Promise { this.dom_video.style.opacity = "1"; for (let i = 0; i <= 100; ++i) { this.dom_video.volume = i / 100; await sleep(10); } } public async end(): Promise { this.dom_video.style.opacity = "0"; for (let i = 100; i >= 0; --i) { this.dom_video.volume = i / 100; await sleep(10); } } public async serve(trigger: PythagorasIncomingMessage): Promise { switch (trigger.type) { case PythagorasIncomingMessageType.PlayVideo: await this.play_video( trigger.filename, trigger.subtitles, trigger.seconds_from_start ); break; case PythagorasIncomingMessageType.SeekVideo: this.seek_video(trigger.timestamp); break; } } private async play_video( video_url: string, subtitles_url: string | null, seconds_from_start: number ): Promise { this.dom_video = await el.video(video_url, subtitles_url); this.dom_video.currentTime = seconds_from_start; this.dom_root.innerHTML = ""; this.dom_root.appendChild(this.dom_video); await wait_for_dom_refresh(); await this.start(); } private seek_video(seconds_from_start: number): void { this.dom_video.currentTime = seconds_from_start; } public get dom(): HTMLDivElement { return this.dom_root; } } class IdleScreen implements PresentationScreen { private dom_root: HTMLDivElement; private dom_title: HTMLHeadingElement; private dom_subtitle: HTMLHeadingElement; private dom_logos: HTMLDivElement[]; public constructor() { this.dom_title = el.h1(dict.IDLE_TITLE) this.dom_subtitle = el.h2(dict.IDLE_STARTING); this.dom_logos = []; this.dom_root = el.div([this.dom_title, this.dom_subtitle], ["idle"]); } public async prepare(): Promise { for (const logo of IDLE_LOGOS) { this.dom_logos.push(el.div([await el.img(logo)], ["logo"])); } const logos = el.div([...this.dom_logos], ["logos"]); this.dom_root.appendChild(logos); } public async start(): Promise { await wait_for_dom_refresh(); this.dom_title.style.transform = "translateY(0)"; this.dom_title.style.opacity = "1"; await sleep(250); this.dom_subtitle.style.opacity = "1"; await sleep(250); for (const logo of this.dom_logos) { logo.style.transform = "translateY(0)"; logo.style.opacity = "1"; await sleep(250); } await sleep(250); } public async end(): Promise { for (const logo of this.dom_logos) { logo.style.transform = "translateY(100%)"; logo.style.opacity = "0"; await sleep(250); } this.dom_subtitle.style.opacity = "0"; await sleep(250); this.dom_title.style.transform = "translateY(-100%)"; this.dom_title.style.opacity = "0"; await sleep(500); } public async serve(trigger: PythagorasIncomingMessage): Promise {} public get dom(): HTMLDivElement { return this.dom_root; } } class BlankScreen implements PresentationScreen { private dom_root: HTMLDivElement; public constructor() { this.dom_root = el.div([]); } public async prepare() {} public async start() {} public async end() {} public async serve(_: PythagorasIncomingMessage) {} public get dom(): HTMLDivElement { return this.dom_root; } }