Files
bitvid/js/ui/components/EmbedPlayerModal.js
T
google-labs-jules[bot] 93b3434145 docs(ai): improve onboarding steps (agent)
- Audit onboarding process (`npm ci`, build, lint).
- Fix hex color lint failures by updating `scripts/check-hex.js` ignore list.
- Fix inline style lint failures by updating `scripts/check-inline-styles.mjs` allowlist.
- Fix raw tailwind colors in `js/ui/components/EmbedPlayerModal.js` by using semantic tokens.
- Add `ai/onboarding-report.md` detailing the audit.
2026-01-25 08:25:42 +00:00

215 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export class EmbedPlayerModal {
constructor({ document: doc, logger } = {}) {
this.document = doc || (typeof document !== "undefined" ? document : null);
this.logger = logger || console;
this.root = null;
this.video = null;
this.status = null;
this.eventTarget = new EventTarget();
// Overlay elements
this.watchButton = null;
this.viewCountEl = null;
this.likeCountEl = null;
this.dislikeCountEl = null;
this.toneClasses = {
default: "text-white/90",
error: "text-status-danger",
success: "text-status-success",
};
}
load() {
if (this.document) {
this.root = this.document.getElementById("embedRoot");
this.video = this.document.getElementById("embedVideo");
this.status = this.document.getElementById("embedStatus");
this.watchButton = this.document.getElementById("embedWatchButton");
this.viewCountEl = this.document.getElementById("embedViewCount");
this.likeCountEl = this.document.getElementById("embedLikeCount");
this.dislikeCountEl = this.document.getElementById("embedDislikeCount");
}
return Promise.resolve(this.root);
}
getRoot() {
return this.root;
}
getVideoElement() {
return this.video;
}
setVideoElement(videoElement) {
if (videoElement) {
this.video = videoElement;
}
return this.video;
}
resetStats() {
// no-op for embed view (torrent stats)
}
setStatus(message, tone = "default") {
if (!this.status) {
return;
}
const normalizedMessage = typeof message === "string" ? message.trim() : "";
this.status.textContent = normalizedMessage;
// Reset classes
this.status.className = "pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-md bg-black/60 px-3 py-1 text-sm backdrop-blur-sm";
const toneClass = this.toneClasses[tone] || this.toneClasses.default;
this.status.classList.add(toneClass);
if (!normalizedMessage) {
this.status.hidden = true;
} else {
this.status.hidden = false;
}
}
updateStatus(message) {
this.setStatus(message, "default");
}
applyLoadingPoster() {
this.setStatus("Loading…", "default");
}
forceRemovePoster() {
this.setStatus("", "default");
return true;
}
clearPosterCleanup() {}
setTorrentStatsVisibility(isVisible) {
if (!this.root) {
return;
}
this.root.dataset.torrentStats = isVisible ? "true" : "false";
}
open() {
return this.root;
}
close() {}
updateMetadata() {}
setShareEnabled() {}
addEventListener(type, listener, options) {
this.eventTarget.addEventListener(type, listener, options);
}
removeEventListener(type, listener, options) {
this.eventTarget.removeEventListener(type, listener, options);
}
dispatch(type, detail) {
return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
}
// --- New Methods for Overlay ---
setShareUrl(url) {
if (this.watchButton && url) {
this.watchButton.href = url;
}
}
getViewCountElement() {
return this.viewCountEl;
}
updateViewCountLabel(text) {
if (!this.viewCountEl) {
return;
}
const label = this.viewCountEl.querySelector("[data-view-count-text]");
if (label) {
// App passes "123 views" or "– views". We strip "views" to keep it compact if desired,
// but the prompt asked for "view count" so maybe "1.2k views" is fine.
// Given the compact design (icon + text), just number is often cleaner, but let's stick to what app sends or simple parse.
// Actually app sends formatted string.
// Let's try to extract just the number/text if possible, or just render what is sent.
// "1.2k views" -> "1.2k"
const parts = (text || "").split(' ');
const compactText = parts[0] || "–";
label.textContent = compactText;
}
}
setViewCountPointer(pointerKey) {
if (this.viewCountEl && pointerKey) {
this.viewCountEl.dataset.pointerKey = pointerKey;
}
}
formatCount(num) {
const val = Number.isFinite(num) ? num : 0;
if (val === 0) return "0";
// Simple compact formatter
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
try {
return new Intl.NumberFormat('en-US', {
notation: "compact",
maximumFractionDigits: 1
}).format(val);
} catch (e) {
// fallback
}
}
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
if (val >= 1000) return (val / 1000).toFixed(1) + 'k';
return String(val);
}
updateReactionSummary({ total, counts, userReaction } = {}) {
const likeCount = counts && typeof counts['+'] === 'number' ? counts['+'] : 0;
const dislikeCount = counts && typeof counts['-'] === 'number' ? counts['-'] : 0;
if (this.likeCountEl) {
const label = this.likeCountEl.querySelector("[data-count]");
if (label) label.textContent = this.formatCount(likeCount);
// Highlight if user liked? (Read-only for now but good visual feedback if we had auth state)
if (userReaction === '+') {
this.likeCountEl.classList.add("text-primary");
this.likeCountEl.classList.remove("text-white/80");
} else {
this.likeCountEl.classList.remove("text-primary");
this.likeCountEl.classList.add("text-white/80");
}
}
if (this.dislikeCountEl) {
const label = this.dislikeCountEl.querySelector("[data-count]");
if (label) label.textContent = this.formatCount(dislikeCount);
if (userReaction === '-') {
this.dislikeCountEl.classList.add("text-status-danger");
this.dislikeCountEl.classList.remove("text-white/80");
} else {
this.dislikeCountEl.classList.remove("text-status-danger");
this.dislikeCountEl.classList.add("text-white/80");
}
}
}
setUserReaction(reaction) {
// We don't have the full counts here to update UI fully,
// but updateReactionSummary will be called by controller with full data.
// So we can ignore this or just toggle classes optimistically.
}
}