treehouse/static/js/emoji.js

125 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

2023-08-28 22:11:18 +02:00
// Emoji zoom-in functionality.
2024-03-03 21:23:37 +01:00
import { addSpell } from "treehouse/spells.js";
2024-02-20 21:50:24 +01:00
class EmojiTooltip extends HTMLElement {
2024-03-03 21:23:37 +01:00
constructor(emoji, element, { onClosed }) {
2024-02-20 21:50:24 +01:00
super();
this.emoji = emoji;
2024-03-03 21:23:37 +01:00
this.emojiElement = element;
2024-02-20 21:50:24 +01:00
this.onClosed = onClosed;
}
connectedCallback() {
this.role = "tooltip";
this.image = new Image();
2024-03-03 21:23:37 +01:00
this.image.src = this.emojiElement.src;
2024-02-20 21:50:24 +01:00
this.description = document.createElement("p");
this.description.textContent = `${this.emoji.emojiName}`;
2024-03-03 21:23:37 +01:00
let emojiBoundingBox = this.emojiElement.getBoundingClientRect();
2024-02-20 21:50:24 +01:00
this.style.left = `${emojiBoundingBox.left + emojiBoundingBox.width / 2}px`;
this.style.top = `calc(${emojiBoundingBox.top}px + 1.5em)`;
this.fullyOpaque = false;
this.addEventListener("transitionend", event => {
if (event.propertyName == "opacity") {
this.fullyOpaque = !this.fullyOpaque;
if (!this.fullyOpaque) {
this.onClosed();
}
}
});
// Timeout is zero because we just want to execute this later, to be definitely sure
// the transition plays out.
setTimeout(() => this.classList.add("transitioned-in"), 0);
this.appendChild(this.image);
this.appendChild(this.description);
}
close() {
this.classList.remove("transitioned-in");
}
}
customElements.define("th-emoji-tooltip", EmojiTooltip);
let emojiTooltips = null;
class EmojiTooltips extends HTMLElement {
2023-08-28 22:11:18 +02:00
constructor() {
super();
2024-02-20 21:50:24 +01:00
this.tooltips = new Set();
this.abortController = new AbortController();
}
connectedCallback() {
emojiTooltips = this;
2024-02-20 22:36:47 +01:00
addEventListener(
"wheel",
event => emojiTooltips.closeTooltips(event),
{ signal: this.abortController.signal },
);
2024-02-20 21:50:24 +01:00
}
disconnectedCallback() {
this.abortController.abort();
}
2024-03-03 21:23:37 +01:00
openTooltip(emoji, element) {
let tooltip = new EmojiTooltip(emoji, element, {
2024-02-20 21:50:24 +01:00
onClosed: () => {
this.removeChild(tooltip);
this.tooltips.delete(tooltip);
},
});
this.appendChild(tooltip);
this.tooltips.add(tooltip);
return tooltip;
}
2023-08-28 22:11:18 +02:00
2024-02-20 21:50:24 +01:00
closeTooltip(tooltip) {
tooltip.close();
}
2023-08-28 22:11:18 +02:00
2024-02-20 21:50:24 +01:00
closeTooltips() {
for (let tooltip of this.tooltips) {
tooltip.close();
}
}
}
2023-08-28 22:11:18 +02:00
2024-02-20 21:50:24 +01:00
customElements.define("th-emoji-tooltips", EmojiTooltips);
2023-08-28 22:11:18 +02:00
2024-03-03 21:23:37 +01:00
class Emoji {
constructor(element) {
this.emojiName = element.title;
2023-08-28 22:11:18 +02:00
2024-02-20 21:50:24 +01:00
// title makes the browser add a tooltip. We replace browser tooltips with our own,
// so remove the title.
2024-03-03 21:23:37 +01:00
element.title = "";
2024-02-20 21:50:24 +01:00
2024-03-03 21:23:37 +01:00
element.addEventListener("mouseenter", () => this.openTooltip(element));
element.addEventListener("mouseleave", () => this.closeTooltip());
element.addEventListener("scroll", () => this.closeTooltip());
2024-02-20 21:50:24 +01:00
}
2024-03-03 21:23:37 +01:00
openTooltip(element) {
this.tooltip = emojiTooltips.openTooltip(this, element);
2024-02-20 21:50:24 +01:00
}
closeTooltip() {
emojiTooltips.closeTooltip(this.tooltip);
this.tooltip = null;
2023-08-28 22:11:18 +02:00
}
}
2024-03-03 21:23:37 +01:00
addSpell("emoji", Emoji);