added additional layouts and carousel mode
This commit is contained in:
parent
d516de52c0
commit
4a550233c0
4 changed files with 519 additions and 46 deletions
|
|
@ -19,14 +19,34 @@
|
|||
"ShowCommunityHint": "Sichtbarkeit der Charaktergemeinschaft.",
|
||||
"Choices": {
|
||||
"CharTop": "Charakter-Name Oben (Spieler Unten)",
|
||||
"PlayerTop": "Spieler-Name Oben (Charakter Unten)",
|
||||
"NoPlayer": "Spieler-Name ausblenden",
|
||||
"MenuLabel": "Stream-Overlay",
|
||||
"PlayerNameDisplay": "Spielernamen-Anzeige"
|
||||
"PlayerNameDisplay": "Spielernamen-Anzeige",
|
||||
"Standard": "Standard (Breitbild)",
|
||||
"Vertical": "Vertikal (Sammelkarte)",
|
||||
"RefFade": "Ausblenden (Fade)",
|
||||
"RefSlide": "Gleiten (Slide)",
|
||||
"RefZoom": "Zoomen (Zoom)",
|
||||
"RefCycle": "Zyklus (In Rotation)",
|
||||
"RefStatic": "Statisch (Immer sichtbar)",
|
||||
"RefHidden": "Versteckt (Im Karussell)"
|
||||
},
|
||||
"PlayerNameDisplayHint": "Wo der Spielername relativ zum Charakternamen angezeigt werden soll.",
|
||||
"HideFearLabel": "Fear-Label verbergen",
|
||||
"HideFearLabelHint": "Verbirgt die Textbeschriftung 'FEAR' neben den Fear-Tokens.",
|
||||
"ShowResourceLabels": "Ressourcen-Bezeichnungen anzeigen",
|
||||
"ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen."
|
||||
"ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen.",
|
||||
"CardLayout": "Karten-Layout Modus",
|
||||
"CardLayoutHint": "Wähle zwischen der Standard-Breitbild-Karte oder einem vertikalen Sammelkarten-Layout.",
|
||||
"CarouselEnabled": "Karussell-Modus aktivieren",
|
||||
"CarouselEnabledHint": "Wechsle automatisch nacheinander durch Spieler.",
|
||||
"CarouselSpeed": "Karussell-Geschwindigkeit (s)",
|
||||
"CarouselSpeedHint": "Sekunden, die jede Karte angezeigt wird, bevor gewechselt wird.",
|
||||
"CarouselEffect": "Übergangseffekt",
|
||||
"CarouselEffectHint": "Animationseffekt beim Wechseln der Karten.",
|
||||
"CarouselGMMode": "GM Karussell-Verhalten",
|
||||
"CarouselGMModeHint": "Wie der GM im Karussell behandelt werden soll."
|
||||
},
|
||||
"OpenStream": "Stream-Ansicht öffnen",
|
||||
"ModeNumeric": "Numerisch (3/6)",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,15 @@
|
|||
"Choices": {
|
||||
"CharTop": "Character Name Top (Player Bottom)",
|
||||
"PlayerTop": "Player Name Top (Character Bottom)",
|
||||
"NoPlayer": "Hide Player Name"
|
||||
"NoPlayer": "Hide Player Name",
|
||||
"Standard": "Standard (Horizontal Card)",
|
||||
"Vertical": "Vertical (Trading Card)",
|
||||
"RefFade": "Fade",
|
||||
"RefSlide": "Slide",
|
||||
"RefZoom": "Zoom",
|
||||
"RefCycle": "Cycle (In Rotation)",
|
||||
"RefStatic": "Static (Always Visible)",
|
||||
"RefHidden": "Hidden (In Carousel)"
|
||||
},
|
||||
"MenuLabel": "Stream Overlay",
|
||||
"PlayerNameDisplay": "Player Name Display",
|
||||
|
|
@ -32,7 +40,21 @@
|
|||
"HideFearLabel": "Hide Fear Label",
|
||||
"HideFearLabelHint": "Hide the text label 'FEAR' next to the fear tokens.",
|
||||
"ShowResourceLabels": "Show Resource Labels",
|
||||
"ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons."
|
||||
"ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons.",
|
||||
"AutoExpandChat": "Auto-Expand Chat Messages",
|
||||
"AutoExpandChatHint": "Automatically expand all chat messages to show full content (rolls, descriptions) without needing to click.",
|
||||
"HideChatActions": "Hide Chat Action Buttons",
|
||||
"HideChatActionsHint": "Hides buttons like 'Apply Damage', 'Place Template', or other interactive elements from chat cards to keep the view clean.",
|
||||
"CardLayout": "Card Layout Mode",
|
||||
"CardLayoutHint": "Choose between the standard wide card or a vertical trading-card style.",
|
||||
"CarouselEnabled": "Enable Carousel Mode",
|
||||
"CarouselEnabledHint": "Cycle through players one by one automatically.",
|
||||
"CarouselSpeed": "Carousel Speed (s)",
|
||||
"CarouselSpeedHint": "Seconds to show each player card before switching.",
|
||||
"CarouselEffect": "Transition Effect",
|
||||
"CarouselEffectHint": "Animation effect when switching cards.",
|
||||
"CarouselGMMode": "GM Carousel Behavior",
|
||||
"CarouselGMModeHint": "How the GM should constitute in the carousel."
|
||||
},
|
||||
"OpenStream": "Open Stream View",
|
||||
"ModeNumeric": "Numeric (3/6)",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dh-stream-overlay",
|
||||
"title": "Daggerheart Stream Overlay",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "13"
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
],
|
||||
"packFolders": [],
|
||||
"url": "https://github.com/cptn-cosmo/dh-stream-overlay",
|
||||
"manifest": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/latest/download/module.json",
|
||||
"download": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/download/1.0.0/dh-stream-overlay.zip",
|
||||
"manifest": "https://git.geeks.gay/cosmo/dh-stream-overlay/raw/branch/main/module.json",
|
||||
"download": "https://git.geeks.gay/cosmo/dh-stream-overlay/releases/download/2.0.0/dh-stream-overlay.zip",
|
||||
"description": "A stream overlay module for Daggerheart that displays chat and a linked party actor sheet."
|
||||
}
|
||||
|
|
@ -191,7 +191,149 @@ Hooks.once("init", () => {
|
|||
scope: "world", config: false, type: Boolean, default: true
|
||||
});
|
||||
|
||||
game.settings.register("dh-stream-overlay", "autoExpandChat", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.AutoExpandChat",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.AutoExpandChatHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
game.settings.register("dh-stream-overlay", "hideChatActions", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.HideChatActions",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.HideChatActionsHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
|
||||
game.settings.register("dh-stream-overlay", "cardLayout", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.CardLayout",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.CardLayoutHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: String,
|
||||
choices: {
|
||||
"standard": "DH_STREAM_OVERLAY.Settings.Choices.Standard",
|
||||
"vertical": "DH_STREAM_OVERLAY.Settings.Choices.Vertical"
|
||||
},
|
||||
default: "standard",
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
|
||||
game.settings.register("dh-stream-overlay", "carouselEnabled", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.CarouselEnabled",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.CarouselEnabledHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
game.settings.register("dh-stream-overlay", "carouselSpeed", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.CarouselSpeed",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.CarouselSpeedHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Number,
|
||||
default: 5,
|
||||
range: { min: 2, max: 60, step: 1 },
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
game.settings.register("dh-stream-overlay", "carouselEffect", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.CarouselEffect",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.CarouselEffectHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: String,
|
||||
choices: {
|
||||
"fade": "DH_STREAM_OVERLAY.Settings.Choices.RefFade",
|
||||
"slide": "DH_STREAM_OVERLAY.Settings.Choices.RefSlide",
|
||||
"zoom": "DH_STREAM_OVERLAY.Settings.Choices.RefZoom"
|
||||
},
|
||||
default: "fade",
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
game.settings.register("dh-stream-overlay", "carouselGMMode", {
|
||||
name: "DH_STREAM_OVERLAY.Settings.CarouselGMMode",
|
||||
hint: "DH_STREAM_OVERLAY.Settings.CarouselGMModeHint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: String,
|
||||
choices: {
|
||||
"cycle": "DH_STREAM_OVERLAY.Settings.Choices.RefCycle",
|
||||
"static": "DH_STREAM_OVERLAY.Settings.Choices.RefStatic",
|
||||
"hidden": "DH_STREAM_OVERLAY.Settings.Choices.RefHidden"
|
||||
},
|
||||
default: "cycle",
|
||||
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// CAROUSEL LOGIC
|
||||
// =============================================================================
|
||||
let carouselIndex = 0;
|
||||
let carouselTimer = null;
|
||||
|
||||
function startCarousel() {
|
||||
if (carouselTimer) clearInterval(carouselTimer);
|
||||
const speed = game.settings.get("dh-stream-overlay", "carouselSpeed") || 5;
|
||||
carouselTimer = setInterval(() => {
|
||||
cycleCarousel();
|
||||
}, speed * 1000);
|
||||
}
|
||||
|
||||
function stopCarousel() {
|
||||
if (carouselTimer) {
|
||||
clearInterval(carouselTimer);
|
||||
carouselTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function cycleCarousel() {
|
||||
const container = document.querySelector(".dh-party-grid.carousel-mode");
|
||||
if (!container) return;
|
||||
|
||||
const cards = container.querySelectorAll(".dh-party-card");
|
||||
if (cards.length === 0) return;
|
||||
|
||||
// Increment
|
||||
carouselIndex++;
|
||||
if (carouselIndex >= cards.length) carouselIndex = 0;
|
||||
|
||||
updateCarouselClasses(container);
|
||||
}
|
||||
|
||||
function updateCarouselClasses(container) {
|
||||
if (!container) return;
|
||||
const cards = container.querySelectorAll(".dh-party-card");
|
||||
|
||||
// Safety: Ensure index is valid
|
||||
if (carouselIndex >= cards.length) carouselIndex = 0;
|
||||
|
||||
cards.forEach((card, idx) => {
|
||||
// Reset classes
|
||||
card.classList.remove("active-slide", "exit-slide");
|
||||
|
||||
if (idx === carouselIndex) {
|
||||
card.classList.add("active-slide");
|
||||
} else {
|
||||
// Optional: Mark previous slide for exit animation if needed
|
||||
// For simple fade, just lacking 'active-slide' hides it
|
||||
}
|
||||
});
|
||||
}
|
||||
// ...
|
||||
|
||||
// ... inside renderPartyOverlay function ...
|
||||
|
|
@ -353,24 +495,45 @@ function renderPartyOverlay(container) {
|
|||
const gms = game.users.filter(u => u.isGM);
|
||||
|
||||
if (players.length === 0 && gms.length === 0) {
|
||||
container.innerHTML = `<div style="color:white; opacity:0.6; padding:10px;">No Active Players or GM Found</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `<div class="dh-party-grid">`;
|
||||
let layoutMode = "standard";
|
||||
try { layoutMode = game.settings.get("dh-stream-overlay", "cardLayout"); } catch (e) { }
|
||||
|
||||
// 1. Render GM(s)
|
||||
let carouselEnabled = false;
|
||||
let carouselEffect = "fade";
|
||||
let carouselGMMode = "cycle";
|
||||
try {
|
||||
carouselEnabled = game.settings.get("dh-stream-overlay", "carouselEnabled");
|
||||
carouselEffect = game.settings.get("dh-stream-overlay", "carouselEffect");
|
||||
carouselGMMode = game.settings.get("dh-stream-overlay", "carouselGMMode");
|
||||
} catch (e) { }
|
||||
|
||||
const renderPips = (val, max, type) => {
|
||||
let h = `<div class="dh-pips-container ${type}">`;
|
||||
for (let i = 1; i <= max; i++) {
|
||||
h += `<div class="dh-pip ${i <= val ? 'marked' : 'open'}"></div>`;
|
||||
}
|
||||
h += `</div>`;
|
||||
return h;
|
||||
};
|
||||
|
||||
// --- HTML GENERATORS ---
|
||||
|
||||
// GM HTML Generator
|
||||
let gmHtml = "";
|
||||
gms.forEach(gm => {
|
||||
const img = gm.avatar || "icons/svg/mystery-man.svg";
|
||||
const name = gm.name; // GM just uses their user name usually
|
||||
const name = gm.name;
|
||||
|
||||
html += `
|
||||
<div class="dh-party-card" style="min-height: 100px;">
|
||||
<div class="dh-card-img-wrapper" style="width: 100px;">
|
||||
gmHtml += `
|
||||
<div class="dh-party-card">
|
||||
<div class="dh-card-img-wrapper">
|
||||
<img class="dh-card-img" src="${img}" />
|
||||
</div>
|
||||
<div class="dh-card-content">
|
||||
<div class="dh-card-header" style="border-bottom:none; padding-bottom:0;">
|
||||
<div class="dh-card-header no-border">
|
||||
<div class="dh-card-name" style="color: #fca5a5;">Game Master</div>
|
||||
<div class="dh-card-meta" style="font-size:1.1em; color:white; margin-top:5px;">${name}</div>
|
||||
</div>
|
||||
|
|
@ -379,6 +542,9 @@ function renderPartyOverlay(container) {
|
|||
`;
|
||||
});
|
||||
|
||||
// Player HTML Generator
|
||||
let playerHtml = "";
|
||||
|
||||
// 2. Render Players
|
||||
// Check setting availability
|
||||
let nameMode = "char-top";
|
||||
|
|
@ -407,14 +573,7 @@ function renderPartyOverlay(container) {
|
|||
showCommunity = game.settings.get("dh-stream-overlay", "showCommunity");
|
||||
} catch (e) { console.warn("DH OVERLAY: Settings not ready yet, using default"); }
|
||||
|
||||
const renderPips = (val, max, type) => {
|
||||
let h = `<div class="dh-pips-container ${type}">`;
|
||||
for (let i = 1; i <= max; i++) {
|
||||
h += `<div class="dh-pip ${i <= val ? 'marked' : 'open'}"></div>`;
|
||||
}
|
||||
h += `</div>`;
|
||||
return h;
|
||||
};
|
||||
|
||||
|
||||
players.forEach(user => {
|
||||
const actor = user.character;
|
||||
|
|
@ -546,7 +705,7 @@ function renderPartyOverlay(container) {
|
|||
const spotlightHtml = isSpotlightRequesting ?
|
||||
`<div class="dh-spotlight-indicator"><i class="fas fa-hand-sparkles"></i></div>` : "";
|
||||
|
||||
html += `
|
||||
playerHtml += `
|
||||
<div class="dh-party-card ${turnClass}">
|
||||
${spotlightHtml}
|
||||
<div class="dh-card-img-wrapper">
|
||||
|
|
@ -594,8 +753,45 @@ function renderPartyOverlay(container) {
|
|||
|
||||
});
|
||||
|
||||
html += `</div>`;
|
||||
container.innerHTML = html;
|
||||
// --- ASSEMBLY LOGIC ---
|
||||
if (carouselEnabled) {
|
||||
let finalHtml = "";
|
||||
let carouselClass = `carousel-mode effect-${carouselEffect}`;
|
||||
|
||||
// GM Logic
|
||||
if (carouselGMMode === "static") {
|
||||
// Static grid for GM, Carousel for Players
|
||||
// Wrap in a flex container
|
||||
container.innerHTML = `
|
||||
<div class="dh-carousel-wrapper">
|
||||
${gms.length > 0 ? `<div class="dh-party-grid layout-${layoutMode} dh-static-gm">${gmHtml}</div>` : ""}
|
||||
${players.length > 0 ? `<div class="dh-party-grid layout-${layoutMode} ${carouselClass}">${playerHtml}</div>` : ""}
|
||||
</div>`;
|
||||
|
||||
} else if (carouselGMMode === "hidden") {
|
||||
// Only Players in Carousel
|
||||
container.innerHTML = `<div class="dh-party-grid layout-${layoutMode} ${carouselClass}">${playerHtml}</div>`;
|
||||
} else {
|
||||
// "cycle" or default - Mixed
|
||||
container.innerHTML = `<div class="dh-party-grid layout-${layoutMode} ${carouselClass}">${gmHtml}${playerHtml}</div>`;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Standard Mode
|
||||
container.innerHTML = `<div class="dh-party-grid layout-${layoutMode}">${gmHtml}${playerHtml}</div>`;
|
||||
}
|
||||
|
||||
// Post-Render: Apply Carousel State immediately (if applicable)
|
||||
if (carouselEnabled) {
|
||||
// Find the carousel grid (could be the main one or secondary)
|
||||
const grid = container.querySelector(".dh-party-grid.carousel-mode");
|
||||
if (grid) {
|
||||
updateCarouselClasses(grid);
|
||||
if (!carouselTimer) startCarousel();
|
||||
}
|
||||
} else {
|
||||
stopCarousel();
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -612,17 +808,27 @@ Hooks.once("ready", async () => {
|
|||
});
|
||||
|
||||
// Fallback Init Trigger
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (window.location.pathname.includes("/stream")) {
|
||||
setTimeout(async () => {
|
||||
if (!overlayInitialized && document.body.classList.contains("stream")) {
|
||||
console.warn("DH Stream Overlay | Fallback Init Triggered");
|
||||
// Fallback Init Trigger
|
||||
if (window.location.pathname.includes("/stream")) {
|
||||
const attemptInit = async (retries = 0) => {
|
||||
if (overlayInitialized) return;
|
||||
|
||||
// If game is ready, or purely maximizing retries (10 seconds), force init
|
||||
if (game.ready || retries > 20) {
|
||||
if (!overlayInitialized) {
|
||||
console.warn(`DH Stream Overlay | Fallback Init Triggered (Ready: ${game.ready}, Retries: ${retries})`);
|
||||
overlayInitialized = true;
|
||||
await initStreamOverlay();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Check again in 500ms
|
||||
setTimeout(() => attemptInit(retries + 1), 500);
|
||||
}
|
||||
};
|
||||
|
||||
// Start the fallback poll
|
||||
setTimeout(() => attemptInit(), 1000);
|
||||
}
|
||||
|
||||
// Menu Button Hook
|
||||
Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
||||
|
|
@ -633,7 +839,10 @@ Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
|||
const button = document.createElement("button");
|
||||
button.innerHTML = `<i class="fas fa-video"></i> ${game.i18n.localize("DH_STREAM_OVERLAY.OpenStream")}`;
|
||||
button.style.width = "100%";
|
||||
button.onclick = () => window.open("/stream", "streamOverlay", "width=1280,height=720,menubar=no,toolbar=no,status=no");
|
||||
button.onclick = () => {
|
||||
const path = window.location.pathname.replace(/\/game\/?$/, "/stream");
|
||||
window.open(path, "streamOverlay", "width=1280,height=720,menubar=no,toolbar=no,status=no");
|
||||
};
|
||||
section.appendChild(button);
|
||||
container.appendChild(section);
|
||||
});
|
||||
|
|
@ -645,12 +854,20 @@ Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
|||
async function initStreamOverlay() {
|
||||
console.log("DH Stream Overlay | Initializing Layout...");
|
||||
|
||||
// 0a. WAIT FOR SETTINGS
|
||||
// Ensure "init" hook has run so settings are registered.
|
||||
// If not, we wait a moment.
|
||||
if (!game.settings.settings.has("dh-stream-overlay.autoExpandChat")) {
|
||||
console.warn("DH Stream Overlay | Settings not yet registered, waiting for init...");
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
|
||||
// 0. WAIT FOR GAME DATA
|
||||
// The stream view might load faster than Foundry data.
|
||||
let dataRetries = 0;
|
||||
while ((!game.users || !game.users.size) && dataRetries < 20) {
|
||||
console.log(`DH Stream Overlay | Waiting for game.users... (${dataRetries}/20)`);
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
while ((!game.users || !game.users.size) && dataRetries < 10) {
|
||||
console.log(`DH Stream Overlay | Waiting for game.users... (${dataRetries}/10)`);
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
dataRetries++;
|
||||
}
|
||||
|
||||
|
|
@ -697,10 +914,11 @@ async function initStreamOverlay() {
|
|||
// Appending is safer for overlay z-index usually.
|
||||
body.appendChild(wrapper);
|
||||
|
||||
// 3. Move Chat Log
|
||||
// 3. Move Chat Log
|
||||
const retryChat = async () => {
|
||||
let attempts = 0;
|
||||
while (attempts < 50) { // Try for longer (approx 10s)
|
||||
while (attempts < 20) { // Reduced retries to avoid long load times
|
||||
// Try Standard DOM
|
||||
let chatLog = document.getElementById("chat-log");
|
||||
|
||||
|
|
@ -723,9 +941,22 @@ async function initStreamOverlay() {
|
|||
// Force scroll to bottom
|
||||
chatLog.scrollTop = chatLog.scrollHeight;
|
||||
console.log("DH Stream Overlay | Chat Log Moved Successfully");
|
||||
|
||||
// --- FIX: EXISTING MESSAGES ---
|
||||
// Manually strip inline styles from existing messages if auto-expand is OFF
|
||||
const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat");
|
||||
if (!autoExpand) {
|
||||
const $log = $(chatLog);
|
||||
// Remove .expanded class first
|
||||
$log.find(".dice-roll.expanded, .damage-section.expanded, .target-section.expanded").removeClass("expanded");
|
||||
|
||||
// Strip inline styles
|
||||
$log.find(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection").css("display", "");
|
||||
$log.find(".dice-roll .dice-tooltip").css("display", ""); // Specificity check
|
||||
}
|
||||
return;
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
attempts++;
|
||||
}
|
||||
console.warn("DH Stream Overlay | Chat Log NOT FOUND after retries");
|
||||
|
|
@ -749,9 +980,37 @@ async function initStreamOverlay() {
|
|||
});
|
||||
|
||||
// Scroll chat to bottom on new message
|
||||
Hooks.on("renderChatMessageHTML", () => {
|
||||
// Scroll chat to bottom on new message & Handle Auto-Expand Logic
|
||||
Hooks.on("renderChatMessage", (message, html) => {
|
||||
// Scroll
|
||||
const log = document.getElementById("chat-log");
|
||||
if (log) log.scrollTop = log.scrollHeight;
|
||||
|
||||
// Auto-Expand Logic (Override System Defaults if necessary)
|
||||
// If Auto-Expand is DISABLED, we want to ensure tooltips don't have inline 'display: block'
|
||||
// which the system might be adding by default.
|
||||
try {
|
||||
const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat");
|
||||
if (!autoExpand) {
|
||||
// WRAPPED IN TIMEOUT to beat system race conditions
|
||||
setTimeout(() => {
|
||||
// 1. Remove .expanded class to force collapse state, allowing manual toggle primarily
|
||||
html.find(".dice-roll.expanded, .damage-section.expanded, .target-section.expanded").removeClass("expanded");
|
||||
|
||||
// 2. Remove inline display styles that force expansion (failsafe)
|
||||
html.find(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection").css("display", "");
|
||||
|
||||
// Also legacy support for non-jquery if appropriate (but standard hook passes jQuery)
|
||||
if (html instanceof HTMLElement) {
|
||||
html.querySelectorAll(".expanded").forEach(el => el.classList.remove("expanded"));
|
||||
const nodes = html.querySelectorAll(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection");
|
||||
nodes.forEach(n => n.style.display = "");
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("DH Stream Overlay | Error in renderChatMessage hook", e);
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Combat Tracker Handling
|
||||
|
|
@ -817,6 +1076,8 @@ async function initStreamOverlay() {
|
|||
|
||||
function injectStyles() {
|
||||
const useGreen = game.settings.get("dh-stream-overlay", "useGreenScreen");
|
||||
const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat");
|
||||
const hideActions = game.settings.get("dh-stream-overlay", "hideChatActions");
|
||||
const bgColor = useGreen ? "#00ff00" : "rgba(0, 0, 0, 0)";
|
||||
|
||||
const style = document.createElement("style");
|
||||
|
|
@ -938,12 +1199,22 @@ function injectStyles() {
|
|||
height: 100vh;
|
||||
background: transparent !important; /* Fully transparent for Chroma Key */
|
||||
/* border-right: 2px solid rgba(255, 255, 255, 0.1) !important; Optional separator */
|
||||
pointer-events: auto;
|
||||
pointer-events: auto !important;
|
||||
z-index: 10000 !important; /* Force on top of everything */
|
||||
position: relative;
|
||||
display: flex; flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* GLOBAL INTERACTIVITY FORCE - ensuring chat is always clickable */
|
||||
#stream-chat-container #chat-log,
|
||||
#stream-chat-container .chat-message,
|
||||
#stream-chat-container .message-content,
|
||||
#stream-chat-container .dice-roll,
|
||||
#stream-chat-container button {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
#stream-right-col {
|
||||
grid-column: 2;
|
||||
height: 100vh;
|
||||
|
|
@ -1077,6 +1348,51 @@ function injectStyles() {
|
|||
/* Hide standard timestamps to save space if needed, or style them */
|
||||
.message-timestamp { color: #888; font-size: 0.8em; }
|
||||
|
||||
/* Auto-Expand Chat Logic */
|
||||
${autoExpand ? `
|
||||
.chat-message .message-content,
|
||||
.chat-message .dice-roll,
|
||||
.chat-message .dice-result,
|
||||
.chat-message .dice-tooltip,
|
||||
.chat-message .dice-formula {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
height: auto !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.dice-tooltip { display: block !important; }
|
||||
` : `
|
||||
/* Force collapse default state (allow JS toggling) via HIGH SPECIFICITY */
|
||||
#stream-chat-container .chat-message .dice-tooltip,
|
||||
#stream-chat-container .chat-message .dice-formula,
|
||||
#stream-chat-container .chat-message .daggerheart-target-list,
|
||||
#stream-chat-container .chat-message .evaluation-selection,
|
||||
#stream-chat-container .chat-message .dice-roll .dice-tooltip,
|
||||
#stream-chat-container .chat-message .dice-result .dice-tooltip {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Allow manual interaction if the .expanded class is present */
|
||||
#stream-chat-container .chat-message .dice-roll.expanded .dice-tooltip,
|
||||
#stream-chat-container .chat-message .dice-roll.expanded .dice-formula,
|
||||
#stream-chat-container .chat-message .target-section.expanded .daggerheart-target-list,
|
||||
#stream-chat-container .chat-message .damage-section.expanded .dice-formula,
|
||||
#stream-chat-container .chat-message .dice-roll.expanded .dice-result .dice-tooltip {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* FORCE INTERACTIVITY removed from here to be global */
|
||||
`}
|
||||
|
||||
${hideActions ? `
|
||||
.chat-message button,
|
||||
.chat-message .chat-card-buttons,
|
||||
.chat-message .card-buttons {
|
||||
display: none !important;
|
||||
}
|
||||
` : ''}
|
||||
|
||||
#stream-combat-container {
|
||||
position: absolute;
|
||||
top: 20px; right: 20px;
|
||||
|
|
@ -1091,14 +1407,16 @@ function injectStyles() {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* Party Overlay Cards - GRID RESTORED */
|
||||
.dh-party-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
||||
align-items: start; /* Prevents cards from stretching vertically to match tallest */
|
||||
gap: 15px;
|
||||
gap: 40px;
|
||||
width: 100%;
|
||||
padding-right: 10px;
|
||||
padding: 20px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.dh-party-card {
|
||||
|
|
@ -1260,7 +1578,120 @@ function injectStyles() {
|
|||
color: #333;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
style.innerHTML += `
|
||||
/* Vertical Layout */
|
||||
.dh-party-grid.layout-vertical {
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.dh-party-grid.layout-vertical .dh-party-card {
|
||||
flex-direction: column;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.dh-party-grid.layout-vertical .dh-card-img-wrapper {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dh-party-grid.layout-vertical .dh-card-img {
|
||||
object-position: top center;
|
||||
}
|
||||
|
||||
.dh-party-grid.layout-vertical .dh-card-content {
|
||||
padding: 15px;
|
||||
gap: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
/* Carousel Mode */
|
||||
style.innerHTML += `
|
||||
/* Carousel Mode */
|
||||
.dh-party-grid.carousel-mode {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr !important;
|
||||
grid-template-rows: 1fr;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dh-party-grid.carousel-mode .dh-party-card {
|
||||
grid-area: 1 / 1 / -1 / -1; /* Stack on top of each other */
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
/* TRANSITION FIX: Delay visibility hide so it fades out first */
|
||||
transition: opacity 0.8s ease-in-out, transform 0.8s ease-in-out, visibility 0s linear 0.8s;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
max-width: 600px; /* Constrain width in carousel */
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* Active Slide */
|
||||
.dh-party-grid.carousel-mode .dh-party-card.active-slide {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
/* Show immediately */
|
||||
transition: opacity 0.8s ease-in-out, transform 0.8s ease-in-out, visibility 0s linear;
|
||||
pointer-events: auto;
|
||||
z-index: 10;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Effects */
|
||||
|
||||
/* Fade (Default behavior above) */
|
||||
|
||||
/* Slide */
|
||||
.dh-party-grid.carousel-mode.effect-slide .dh-party-card {
|
||||
transform: translateX(50px);
|
||||
}
|
||||
.dh-party-grid.carousel-mode.effect-slide .dh-party-card.active-slide {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Zoom */
|
||||
.dh-party-grid.carousel-mode.effect-zoom .dh-party-card {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
.dh-party-grid.carousel-mode.effect-zoom .dh-party-card.active-slide {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
style.innerHTML += `
|
||||
/* Width Constraint for Vertical Carousel */
|
||||
.dh-party-grid.carousel-mode.layout-vertical .dh-party-card {
|
||||
max-width: 350px !important;
|
||||
}
|
||||
|
||||
/* Static GM Wrapper */
|
||||
.dh-carousel-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dh-static-gm {
|
||||
flex: 0 0 auto;
|
||||
/* Matches vertical card layout styling */
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.dh-static-gm .dh-party-card {
|
||||
height: 100%;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue