Files
pythagoras/client/scrolling_textbox.ts

142 lines
4.4 KiB
TypeScript

import { DELAY_WORDS } from "./settings";
import { el, sleep } from "./tools";
export class ScrollingTextBox {
private dom_root: HTMLDivElement;
private prev_line_words: HTMLDivElement[];
private cur_line_words: HTMLDivElement[];
private prev_line: HTMLParagraphElement;
private current_line: HTMLParagraphElement;
public constructor() {
this.dom_root = el.div([], ["scrolling-textbox"]);
this.cur_line_words = [];
this.prev_line_words = [];
this.current_line = el.div(this.cur_line_words, ["paragraph", "active"]);
this.prev_line = el.div(this.prev_line_words, ["paragraph", "previous"]);
this.dom_root.appendChild(this.prev_line);
this.dom_root.appendChild(this.current_line);
}
public async update_current(text: string): Promise<void> {
this.update_words(this.current_line, this.cur_line_words, text);
await sleep(10);
this.show_new_words(this.cur_line_words);
}
public async finish_line(text: string): Promise<void> {
this.update_words(this.current_line, this.cur_line_words, text, true);
const current_height = this.current_line.getBoundingClientRect().height;
this.prev_line.style.transform = `translateY(calc(-100% - ${current_height}px))`;
this.current_line.style.transform = "translateY(-100%)";
this.current_line.style.color = "grey";
await sleep(500);
this.dom_root.removeChild(this.prev_line);
this.prev_line = this.current_line;
this.prev_line_words = this.cur_line_words;
this.prev_line.className = "paragraph previous";
this.current_line = el.div([], ["paragraph", "active"]);
this.cur_line_words = [];
this.dom_root.appendChild(this.current_line);
await sleep(10);
}
public async add_line(text: string): Promise<void> {
const current_height = this.current_line.getBoundingClientRect().height;
const next_words = this.make_words(text);
const next_line = el.div(next_words, ["paragraph", "previous"]);
next_line.style.transform = `translateY(${current_height}px)`;
next_line.style.opacity = "0";
this.dom_root.appendChild(next_line);
await sleep(50);
next_line.style.transform = "translateY(0px)";
next_line.style.opacity = "1";
this.current_line.style.transform = "translateY(-100%)";
this.current_line.style.color = "grey";
this.prev_line.style.transform = `translateY(calc(-100% - ${current_height}px))`;
await sleep(500);
this.dom_root.removeChild(this.prev_line);
this.prev_line = this.current_line;
this.prev_line_words = this.cur_line_words;
this.prev_line.className = "paragraph previous";
this.current_line = next_line;
this.current_line.className = "paragraph active";
this.cur_line_words = next_words;
}
private async show_new_words(words: HTMLParagraphElement[]): Promise<void> {
for (const word of words) {
if (word.style.opacity != "0") { continue; }
word.style.opacity = "1";
word.style.transform = "scale(100%)";
await sleep(50);
}
}
private update_words(
parent: HTMLDivElement,
words: HTMLParagraphElement[],
new_text: string,
override: boolean = false,
): void {
const new_words = new_text.split(" ");
if (!override) {
for (let i = 0; i < DELAY_WORDS; ++i) { new_words.pop(); }
}
if (words.length > new_words.length) {
this.trim_words(parent, words, new_words.length);
for (let i = 0; i < new_words.length; ++i) {
if (words[i].innerText == new_words[i]) { continue; }
words[i].innerText = new_words[i];
}
return;
}
const to_push: HTMLParagraphElement[] = [];
for (let i = 0; i < new_words.length; ++i) {
if (i < words.length) {
if (new_words[i] == words[i].innerText) { continue; }
words[i].innerText = new_words[i];
continue;
}
const word = el.p(new_words[i], ["word"]);
if (!override) {
word.style.opacity = "0";
word.style.transform = "scale(0)";
}
words.push(word);
to_push.push(word);
}
parent.append(...to_push);
}
private trim_words(
parent: HTMLDivElement,
words: HTMLParagraphElement[],
target_length: number
): void {
if (target_length >= words.length || words.length == 0) { return; }
for (let i = words.length - 1; i >= target_length; --i) {
const word = words.pop();
if (word === undefined) { continue; }
parent.removeChild(word);
}
}
private make_words(text: string): HTMLDivElement[] {
const words = text.split(" ");
let res = [];
for (const word of words) {
res.push(el.p(word, ["word"]));
}
return res;
}
public get dom(): HTMLDivElement {
return this.dom_root;
}
}