Files
pythagoras/client/presentationmgr.ts
2025-05-05 11:26:59 +02:00

286 lines
7.4 KiB
TypeScript

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 { el, sleep } from "./tools";
interface PresentationScreen {
prepare(): Promise<void>;
start(): Promise<void>;
end(): Promise<void>;
serve(trigger: PythagorasIncomingMessage): Promise<void>;
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<void> {
await this.screen.prepare();
this.dom_root.innerHTML = "";
this.dom_root.appendChild(this.screen.dom);
await sleep(10);
}
public async serve(ws_client: PythagorasClient): Promise<void> {
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: ScrollingTextBox;
private subs_czech: ScrollingTextBox;
private dom_question: HTMLDivElement;
private dom_question_link: HTMLDivElement;
private question_insert: HTMLParagraphElement;
public constructor() {
this.subs_english = new ScrollingTextBox();
this.subs_czech = new ScrollingTextBox();
this.question_insert = el.p("");
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([this.subs_english.dom], ["lang"]),
el.div([this.subs_czech.dom], ["lang"]),
], ["subtitles"]),
], ["main"]);
}
public async prepare(): Promise<void> {
this.dom_question_link.append(await el.img(QUESTION_QR, ["qr"]));
}
public async start(): Promise<void> {
await sleep(10);
this.dom_root.style.opacity = "1";
await sleep(500);
}
public async end(): Promise<void> {
this.dom_root.style.opacity = "0";
await sleep(500);
}
private async show_question(text: string): Promise<void> {
this.question_insert.innerText = text;
this.question_insert.style.opacity = "1";
await sleep(500);
}
private async hide_question(): Promise<void> {
this.question_insert.style.opacity = "0";
await sleep(500);
}
public async serve(trigger: PythagorasIncomingMessage): Promise<void> {
switch (trigger.type) {
case PythagorasIncomingMessageType.SubEnUpdateCur:
await this.subs_english.update_current(trigger.text);
break;
case PythagorasIncomingMessageType.SubEnSubmit:
await this.subs_english.finish_line(trigger.text);
break;
case PythagorasIncomingMessageType.SubCzSubmit:
await this.subs_czech.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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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 sleep(10);
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: HTMLImageElement[];
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<void> {
for (const logo of IDLE_LOGOS) {
this.dom_logos.push(await el.img(logo, ["logo"]));
}
const logos = el.div([...this.dom_logos], ["logos"]);
this.dom_root.appendChild(logos);
}
public async start(): Promise<void> {
await sleep(10);
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<void> {
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<void> {}
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;
}
}