treehouse/static/js/components/chat.js
2024-03-24 22:27:06 +01:00

189 lines
5.1 KiB
JavaScript

import { addSpell, spell } from "treehouse/spells.js";
import { Branch } from "treehouse/tree.js";
const characters = {
coco: {
name: "Coco",
},
}
const persistenceKey = "treehouse.chats";
let persistentState = JSON.parse(localStorage.getItem(persistenceKey)) || {};
persistentState.log ??= {};
persistentState.facts ??= {};
savePersistentState();
function savePersistentState() {
localStorage.setItem(persistenceKey, JSON.stringify(persistentState));
}
class Chat extends HTMLElement {
constructor(branch) {
super();
this.branch = branch;
}
connectedCallback() {
this.id = spell(this.branch, Branch).namedID;
this.model = JSON.parse(spell(this.branch, Branch).branchContent.textContent);
this.state = new ChatState(this, this.model);
this.state.onInteract = () => {
persistentState.log[this.id] = this.state.log;
savePersistentState();
};
this.state.exec("init");
let log = persistentState.log[this.id];
if (log != null) {
this.state.replay(log);
}
}
}
customElements.define("th-chat", Chat);
class Said extends HTMLElement {
constructor({ content, character, expression }) {
super();
this.content = content;
this.character = character;
this.expression = expression ?? "neutral";
}
connectedCallback() {
this.picture = new Image(64, 64);
this.picture.src = `${TREEHOUSE_SITE}/static/character/${this.character}/${this.expression}.svg`;
this.picture.classList.add("picture");
this.appendChild(this.picture);
this.textContainer = document.createElement("span");
this.textContainer.innerHTML = this.content;
this.textContainer.classList.add("text-container");
this.appendChild(this.textContainer);
this.dispatchEvent(new Event(".textFullyVisible"));
}
}
customElements.define("th-chat-said", Said);
class Asked extends HTMLElement {
constructor({ content, alreadyAsked }) {
super();
this.content = content;
this.alreadyAsked = alreadyAsked;
}
connectedCallback() {
this.button = document.createElement("button");
this.button.innerHTML = this.content;
this.button.addEventListener("click", _ => {
this.dispatchEvent(new Event(".click"));
});
if (this.alreadyAsked) {
this.button.classList.add("asked");
}
this.appendChild(this.button);
}
interactionFinished() {
this.button.disabled = true;
}
}
customElements.define("th-chat-asked", Asked);
class ChatState {
constructor(container, model) {
this.container = container;
this.model = model;
this.log = [];
this.results = {};
this.wereAsked = new Set();
this.onInteract = _ => {};
}
replay(log) {
for (let entry of log) {
this.interact(entry);
}
}
exec(name) {
let node = this.model.nodes[name];
let results = this.results[name];
this.results[name] = this[node.kind](name, node, results);
}
say(_, node) {
let said = new Said({ content: node.content, character: node.character, expression: node.expression });
said.addEventListener(".textFullyVisible", _ => this.exec(node.then));
this.container.appendChild(said);
}
ask(name, node) {
let questions = [];
for (let i_ = 0; i_ < node.questions.length; ++i_) {
let i = i_;
let key = `${name}[${i}]`;
let question = node.questions[i];
let asked = new Asked({ content: question.content, alreadyAsked: this.wereAsked.has(key) });
asked.addEventListener(".click", _ => {
this.interact({
kind: "ask.choose",
name,
option: i,
key,
});
});
this.container.appendChild(asked);
questions[i] = asked;
}
return questions;
}
set(_, node) {
persistentState.facts[node.fact] = true;
this.exec(node.then);
}
end() {}
interact(interaction) {
let node = this.model.nodes[interaction.name];
this.log.push(interaction);
this.onInteract();
switch (interaction.kind) {
case "ask.choose": {
if (this.wereAsked.has(interaction.key)) {
this.log.pop();
}
this.wereAsked.add(interaction.key);
let questions = this.results[interaction.name];
let question = node.questions[interaction.option];
let asked = questions[interaction.option];
asked.interactionFinished();
this.exec(question.then);
for (let q of questions) {
if (q != asked) {
q.parentNode.removeChild(q);
}
}
}
break;
}
}
}
addSpell("chat", class {
constructor(branch) {
branch.replaceWith(new Chat(branch));
}
});