Merge pull request 'Fix Texas Hold'em dealer page: script timing crash and seat overlap' (#11) from feature/casino-texas-holdem into master
Build and Deploy / build-deploy (push) Successful in 1m8s
Build and Deploy / build-deploy (push) Successful in 1m8s
Reviewed-on: #11
This commit was merged in pull request #11.
This commit is contained in:
@@ -34,6 +34,13 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<aside id="holdemSidebar">
|
<aside id="holdemSidebar">
|
||||||
|
<section id="seatEditorPanel" class="sidebarPanel seatEditorPanel">
|
||||||
|
<h2 id="seatEditorTitle">Select a seat</h2>
|
||||||
|
<div id="seatEditorBody">
|
||||||
|
<p class="fieldHint">Click a seat at the table to edit their name, cards, chips and anything else.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<details class="sidebarPanel" open>
|
<details class="sidebarPanel" open>
|
||||||
<summary>Table Setup</summary>
|
<summary>Table Setup</summary>
|
||||||
<div class="panelBody">
|
<div class="panelBody">
|
||||||
@@ -81,35 +88,42 @@
|
|||||||
<button class="dealerChip" title="Make this seat the dealer button">D</button>
|
<button class="dealerChip" title="Make this seat the dealer button">D</button>
|
||||||
<span class="blindBadge sbBadge">SB</span>
|
<span class="blindBadge sbBadge">SB</span>
|
||||||
<span class="blindBadge bbBadge">BB</span>
|
<span class="blindBadge bbBadge">BB</span>
|
||||||
<div class="seatCard">
|
<div class="seatPuckName"></div>
|
||||||
<div class="seatCardTop">
|
<div class="seatPuckCards"></div>
|
||||||
<input type="text" class="seatNameInput" placeholder="Seat" maxlength="24" />
|
<div class="seatPuckTotal"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="seatCardToggles">
|
</template>
|
||||||
<label class="inHandToggle" title="This seat is playing the current hand">
|
|
||||||
<input type="checkbox" class="inHandCheckbox" />
|
<template id="seatEditorTemplate">
|
||||||
<span>In hand</span>
|
<div class="seatEditorForm">
|
||||||
</label>
|
<label class="fieldRow">
|
||||||
<label class="foldToggle" title="Folded">
|
<span>Name</span>
|
||||||
<input type="checkbox" class="foldCheckbox" />
|
<input type="text" class="seatNameInput" placeholder="Seat" maxlength="24" />
|
||||||
<span>Fold</span>
|
</label>
|
||||||
</label>
|
<div class="seatCardToggles">
|
||||||
</div>
|
<label class="inHandToggle" title="This seat is playing the current hand">
|
||||||
<div class="seatCards">
|
<input type="checkbox" class="inHandCheckbox" />
|
||||||
<input type="text" class="cardInput card1Input" maxlength="2" placeholder="__" />
|
<span>In hand</span>
|
||||||
<input type="text" class="cardInput card2Input" maxlength="2" placeholder="__" />
|
</label>
|
||||||
</div>
|
<label class="foldToggle" title="Folded">
|
||||||
<div class="seatContribution">
|
<input type="checkbox" class="foldCheckbox" />
|
||||||
<div class="chipCounts"></div>
|
<span>Fold</span>
|
||||||
<label class="fieldRow amountOverrideRow">
|
</label>
|
||||||
<span>$ amount</span>
|
|
||||||
<input type="number" class="amountOverrideInput" min="0" step="1" placeholder="auto" />
|
|
||||||
</label>
|
|
||||||
<div class="seatTotal">Total: <span class="seatTotalAmount">$0</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="customFields"></div>
|
|
||||||
<button class="holdemBtn holdemBtnSmall addFieldBtn">+ field</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="seatCards">
|
||||||
|
<input type="text" class="cardInput card1Input" maxlength="2" placeholder="__" />
|
||||||
|
<input type="text" class="cardInput card2Input" maxlength="2" placeholder="__" />
|
||||||
|
</div>
|
||||||
|
<div class="seatContribution">
|
||||||
|
<div class="chipCounts"></div>
|
||||||
|
<label class="fieldRow amountOverrideRow">
|
||||||
|
<span>$ amount</span>
|
||||||
|
<input type="number" class="amountOverrideInput" min="0" step="1" placeholder="auto" />
|
||||||
|
</label>
|
||||||
|
<div class="seatTotal">Total: <span class="seatTotalAmount">$0</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="customFields"></div>
|
||||||
|
<button class="holdemBtn holdemBtnSmall addFieldBtn">+ field</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ body {
|
|||||||
#pokerTableWrap {
|
#pokerTableWrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 62%;
|
padding-bottom: 72%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pokerTable {
|
#pokerTable {
|
||||||
@@ -161,19 +161,21 @@ body {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Seats ---------- */
|
/* ---------- Seats (compact pucks on the table) ---------- */
|
||||||
|
|
||||||
.seat {
|
.seat {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 190px;
|
width: 96px;
|
||||||
|
min-height: 54px;
|
||||||
background: #232323;
|
background: #232323;
|
||||||
border: 1px solid #3a3a3a;
|
border: 2px solid #3a3a3a;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 0.6rem;
|
padding: 0.3rem 0.4rem;
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seat.dragging {
|
.seat.dragging {
|
||||||
@@ -189,30 +191,60 @@ body {
|
|||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seat.seatFolded .seatCard {
|
.seat.seatFolded {
|
||||||
opacity: 0.55;
|
border-style: dashed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seat.seatSelected {
|
||||||
|
border-color: #09ff00;
|
||||||
|
box-shadow: 0 0 0 2px rgba(9, 255, 0, 0.35), 0 4px 10px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.seatChairNumber {
|
.seatChairNumber {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 5px;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seatPuckName {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #eee;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seatPuckCards {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
color: #888;
|
font-family: var(--font-mono, monospace);
|
||||||
text-transform: uppercase;
|
color: #bbb;
|
||||||
letter-spacing: 0.05em;
|
min-height: 0.9em;
|
||||||
margin-bottom: 0.3rem;
|
}
|
||||||
|
|
||||||
|
.seatPuckTotal {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #9be6a8;
|
||||||
|
min-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dealerChip {
|
.dealerChip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
right: -10px;
|
right: -10px;
|
||||||
width: 26px;
|
width: 22px;
|
||||||
height: 26px;
|
height: 22px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #444;
|
background: #444;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
border: 2px solid #666;
|
border: 2px solid #666;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.75rem;
|
font-size: 0.68rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
@@ -226,10 +258,10 @@ body {
|
|||||||
.blindBadge {
|
.blindBadge {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -9px;
|
||||||
font-size: 0.65rem;
|
font-size: 0.58rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
padding: 0.1rem 0.4rem;
|
padding: 0.05rem 0.3rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
@@ -245,15 +277,43 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bbBadge {
|
.bbBadge {
|
||||||
left: 26px;
|
left: 22px;
|
||||||
background: #b7791f;
|
background: #b7791f;
|
||||||
color: #fff7e6;
|
color: #fff7e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seatCardTop {
|
/* ---------- Seat editor panel (sidebar form for the selected seat) ---------- */
|
||||||
|
|
||||||
|
.seatEditorPanel {
|
||||||
|
padding: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seatEditorPanel h2 {
|
||||||
|
margin: 0 0 0.6rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seatEditorForm {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.4rem;
|
flex-direction: column;
|
||||||
margin-bottom: 0.35rem;
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seatCardToggles {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inHandToggle,
|
||||||
|
.foldToggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seatNameInput {
|
.seatNameInput {
|
||||||
@@ -267,22 +327,6 @@ body {
|
|||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seatCardToggles {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.6rem;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inHandToggle,
|
|
||||||
.foldToggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seatCards {
|
.seatCards {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
@@ -598,6 +642,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.seat {
|
.seat {
|
||||||
width: 150px;
|
width: 78px;
|
||||||
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
(function () {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const state = HoldemState.load();
|
const state = HoldemState.load();
|
||||||
|
|
||||||
const hooks = {
|
const hooks = {
|
||||||
@@ -67,4 +67,4 @@
|
|||||||
document.getElementById("potWizardOverlay").addEventListener("click", e => {
|
document.getElementById("potWizardOverlay").addEventListener("click", e => {
|
||||||
if (e.target.id === "potWizardOverlay") PotWizard.close();
|
if (e.target.id === "potWizardOverlay") PotWizard.close();
|
||||||
});
|
});
|
||||||
})();
|
});
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
// All DOM rendering + drag/drop for the table. Talks to the outside world only through
|
// All DOM rendering + drag/drop for the table. Talks to the outside world only through
|
||||||
// the `hooks` object passed to mount(): hooks.save(state) persists, hooks.rerender()
|
// the `hooks` object passed to mount(): hooks.save(state) persists, hooks.rerender()
|
||||||
// re-draws everything (used after structural changes like resizing seats or the chip
|
// re-draws everything (used after structural changes like resizing seats or the chip
|
||||||
// legend), hooks.rerenderPotBadge(state) just updates the pot total display.
|
// legend).
|
||||||
|
//
|
||||||
|
// The table itself only shows compact seat "pucks" — name, cards, total, blind/dealer
|
||||||
|
// badges — because a full data-entry form (chip counts, custom fields, etc.) doesn't fit
|
||||||
|
// next to nine others arranged in a circle at any screen size. Clicking a puck opens the
|
||||||
|
// full form in the seat editor panel instead.
|
||||||
const Render = {
|
const Render = {
|
||||||
state: null,
|
state: null,
|
||||||
hooks: null,
|
hooks: null,
|
||||||
|
selectedSeatIndex: null,
|
||||||
|
|
||||||
mount(state, hooks) {
|
mount(state, hooks) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.hooks = hooks;
|
this.hooks = hooks;
|
||||||
|
this.selectedSeatIndex = null;
|
||||||
this.renderAll();
|
this.renderAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
renderAll() {
|
renderAll() {
|
||||||
|
if (this.selectedSeatIndex !== null && this.selectedSeatIndex >= this.state.seatCount) {
|
||||||
|
this.selectedSeatIndex = null;
|
||||||
|
}
|
||||||
this.renderSeats();
|
this.renderSeats();
|
||||||
|
this.renderSeatEditor();
|
||||||
this.renderChipLegend();
|
this.renderChipLegend();
|
||||||
this.renderNotes();
|
this.renderNotes();
|
||||||
this.updatePotBadge();
|
this.updatePotBadge();
|
||||||
@@ -36,29 +47,22 @@ const Render = {
|
|||||||
document.getElementById("potBadgeAmount").textContent = Money.format(total);
|
document.getElementById("potBadgeAmount").textContent = Money.format(total);
|
||||||
},
|
},
|
||||||
|
|
||||||
commitAndRedrawSeat(seatEl, seatIndex) {
|
// ---- Seats around the table (compact pucks) ----
|
||||||
this.hooks.save(this.state);
|
|
||||||
this.updatePotBadge();
|
|
||||||
this.renderSeatTotal(seatEl, this.state.seats[seatIndex]);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSeatTotal(seatEl, player) {
|
|
||||||
const total = HoldemState.computeContribution(player, this.state.chipLegend, this.state.basePotAmount);
|
|
||||||
seatEl.querySelector(".seatTotalAmount").textContent = Money.format(total);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ---- Seats around the table ----
|
|
||||||
|
|
||||||
seatPosition(index, count) {
|
seatPosition(index, count) {
|
||||||
const angle = -90 + (360 / count) * index;
|
const angle = -90 + (360 / count) * index;
|
||||||
const radians = (angle * Math.PI) / 180;
|
const radians = (angle * Math.PI) / 180;
|
||||||
const rx = 44;
|
const rx = 44;
|
||||||
const ry = 40;
|
const ry = 42;
|
||||||
const left = 50 + rx * Math.cos(radians);
|
const left = 50 + rx * Math.cos(radians);
|
||||||
const top = 50 + ry * Math.sin(radians);
|
const top = 50 + ry * Math.sin(radians);
|
||||||
return { left, top };
|
return { left, top };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
puckLabel(player, seatIndex) {
|
||||||
|
return player.name && player.name.trim() ? player.name.trim() : `Seat ${seatIndex + 1}`;
|
||||||
|
},
|
||||||
|
|
||||||
renderSeats() {
|
renderSeats() {
|
||||||
const layer = document.getElementById("seatsLayer");
|
const layer = document.getElementById("seatsLayer");
|
||||||
layer.innerHTML = "";
|
layer.innerHTML = "";
|
||||||
@@ -72,11 +76,12 @@ const Render = {
|
|||||||
node.style.top = `${pos.top}%`;
|
node.style.top = `${pos.top}%`;
|
||||||
node.dataset.seatIndex = String(index);
|
node.dataset.seatIndex = String(index);
|
||||||
|
|
||||||
node.querySelector(".seatChairNumber").textContent = `Seat ${index + 1}`;
|
node.querySelector(".seatChairNumber").textContent = index + 1;
|
||||||
|
|
||||||
const dealerChip = node.querySelector(".dealerChip");
|
const dealerChip = node.querySelector(".dealerChip");
|
||||||
dealerChip.classList.toggle("active", index === dealerIndex);
|
dealerChip.classList.toggle("active", index === dealerIndex);
|
||||||
dealerChip.addEventListener("click", () => {
|
dealerChip.addEventListener("click", e => {
|
||||||
|
e.stopPropagation();
|
||||||
this.state.dealerSeatIndex = index;
|
this.state.dealerSeatIndex = index;
|
||||||
this.hooks.save(this.state);
|
this.hooks.save(this.state);
|
||||||
this.renderSeats();
|
this.renderSeats();
|
||||||
@@ -85,12 +90,14 @@ const Render = {
|
|||||||
node.querySelector(".sbBadge").classList.toggle("visible", index === sbIndex);
|
node.querySelector(".sbBadge").classList.toggle("visible", index === sbIndex);
|
||||||
node.querySelector(".bbBadge").classList.toggle("visible", index === bbIndex);
|
node.querySelector(".bbBadge").classList.toggle("visible", index === bbIndex);
|
||||||
|
|
||||||
this.wireSeatFields(node, player, index);
|
this.updatePuckContent(node, player, index);
|
||||||
this.renderChipCounts(node, player);
|
node.classList.toggle("seatSelected", index === this.selectedSeatIndex);
|
||||||
this.renderCustomFields(node, player);
|
|
||||||
this.renderSeatTotal(node, player);
|
node.addEventListener("click", () => {
|
||||||
node.classList.toggle("seatInactive", !player.inHand);
|
this.selectedSeatIndex = index;
|
||||||
node.classList.toggle("seatFolded", player.folded);
|
this.renderSeats();
|
||||||
|
this.renderSeatEditor();
|
||||||
|
});
|
||||||
|
|
||||||
this.wireDragAndDrop(node, index);
|
this.wireDragAndDrop(node, index);
|
||||||
|
|
||||||
@@ -98,106 +105,21 @@ const Render = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
wireSeatFields(node, player, index) {
|
updatePuckContent(node, player, index) {
|
||||||
const nameInput = node.querySelector(".seatNameInput");
|
node.querySelector(".seatPuckName").textContent = player.name && player.name.trim() ? player.name.trim() : "";
|
||||||
nameInput.value = player.name;
|
const cards = (player.cards || []).filter(Boolean).join(" ");
|
||||||
nameInput.addEventListener("input", () => {
|
node.querySelector(".seatPuckCards").textContent = cards;
|
||||||
player.name = nameInput.value;
|
const total = HoldemState.computeContribution(player, this.state.chipLegend, this.state.basePotAmount);
|
||||||
this.hooks.save(this.state);
|
node.querySelector(".seatPuckTotal").textContent = total > 0 ? Money.format(total) : "";
|
||||||
});
|
node.classList.toggle("seatInactive", !player.inHand);
|
||||||
|
node.classList.toggle("seatFolded", player.folded);
|
||||||
const inHandCheckbox = node.querySelector(".inHandCheckbox");
|
|
||||||
inHandCheckbox.checked = player.inHand;
|
|
||||||
inHandCheckbox.addEventListener("change", () => {
|
|
||||||
player.inHand = inHandCheckbox.checked;
|
|
||||||
if (!player.inHand) player.folded = false;
|
|
||||||
this.renderSeats();
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
this.updatePotBadge();
|
|
||||||
});
|
|
||||||
|
|
||||||
const foldCheckbox = node.querySelector(".foldCheckbox");
|
|
||||||
foldCheckbox.checked = player.folded;
|
|
||||||
foldCheckbox.disabled = !player.inHand;
|
|
||||||
foldCheckbox.addEventListener("change", () => {
|
|
||||||
player.folded = foldCheckbox.checked;
|
|
||||||
node.classList.toggle("seatFolded", player.folded);
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
});
|
|
||||||
|
|
||||||
const card1 = node.querySelector(".card1Input");
|
|
||||||
const card2 = node.querySelector(".card2Input");
|
|
||||||
card1.value = player.cards[0] || "";
|
|
||||||
card2.value = player.cards[1] || "";
|
|
||||||
[card1, card2].forEach((input, cardIdx) => {
|
|
||||||
input.addEventListener("input", () => {
|
|
||||||
input.value = input.value.toUpperCase();
|
|
||||||
player.cards[cardIdx] = input.value;
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const amountInput = node.querySelector(".amountOverrideInput");
|
|
||||||
amountInput.value = player.amountOverride ?? "";
|
|
||||||
amountInput.addEventListener("input", () => {
|
|
||||||
player.amountOverride = amountInput.value === "" ? null : amountInput.value;
|
|
||||||
this.commitAndRedrawSeat(node, index);
|
|
||||||
});
|
|
||||||
|
|
||||||
const addFieldBtn = node.querySelector(".addFieldBtn");
|
|
||||||
addFieldBtn.addEventListener("click", () => {
|
|
||||||
player.customFields.push({ label: "", value: "" });
|
|
||||||
this.renderCustomFields(node, player);
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderChipCounts(node, player) {
|
// Cheaper than a full renderSeats() — used after edits so the seat editor's inputs
|
||||||
const container = node.querySelector(".chipCounts");
|
// (which live in a separate panel) never lose focus.
|
||||||
container.innerHTML = "";
|
refreshPuck(index) {
|
||||||
const template = document.getElementById("chipCountTemplate");
|
const node = document.querySelector(`.seat[data-seat-index="${index}"]`);
|
||||||
|
if (node) this.updatePuckContent(node, this.state.seats[index], index);
|
||||||
this.state.chipLegend.forEach(chip => {
|
|
||||||
const row = template.content.firstElementChild.cloneNode(true);
|
|
||||||
row.querySelector(".chipSwatch").style.background = chip.color;
|
|
||||||
row.title = `$${chip.value} chips`;
|
|
||||||
const input = row.querySelector(".chipCountInput");
|
|
||||||
input.value = player.chipCounts[chip.id] || 0;
|
|
||||||
input.addEventListener("input", () => {
|
|
||||||
const count = Math.max(0, Math.floor(Number(input.value)) || 0);
|
|
||||||
player.chipCounts[chip.id] = count;
|
|
||||||
this.commitAndRedrawSeat(node, Number(node.dataset.seatIndex));
|
|
||||||
});
|
|
||||||
container.appendChild(row);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderCustomFields(node, player) {
|
|
||||||
const container = node.querySelector(".customFields");
|
|
||||||
container.innerHTML = "";
|
|
||||||
const template = document.getElementById("customFieldTemplate");
|
|
||||||
|
|
||||||
player.customFields.forEach((field, fieldIdx) => {
|
|
||||||
const row = template.content.firstElementChild.cloneNode(true);
|
|
||||||
const labelInput = row.querySelector(".customFieldLabel");
|
|
||||||
const valueInput = row.querySelector(".customFieldValue");
|
|
||||||
labelInput.value = field.label;
|
|
||||||
valueInput.value = field.value;
|
|
||||||
labelInput.addEventListener("input", () => {
|
|
||||||
field.label = labelInput.value;
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
});
|
|
||||||
valueInput.addEventListener("input", () => {
|
|
||||||
field.value = valueInput.value;
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
});
|
|
||||||
row.querySelector(".removeFieldBtn").addEventListener("click", () => {
|
|
||||||
player.customFields.splice(fieldIdx, 1);
|
|
||||||
this.renderCustomFields(node, player);
|
|
||||||
this.hooks.save(this.state);
|
|
||||||
});
|
|
||||||
container.appendChild(row);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
wireDragAndDrop(node, index) {
|
wireDragAndDrop(node, index) {
|
||||||
@@ -222,6 +144,151 @@ const Render = {
|
|||||||
[seats[sourceIndex], seats[targetIndex]] = [seats[targetIndex], seats[sourceIndex]];
|
[seats[sourceIndex], seats[targetIndex]] = [seats[targetIndex], seats[sourceIndex]];
|
||||||
this.hooks.save(this.state);
|
this.hooks.save(this.state);
|
||||||
this.renderSeats();
|
this.renderSeats();
|
||||||
|
if (this.selectedSeatIndex === sourceIndex || this.selectedSeatIndex === targetIndex) {
|
||||||
|
this.renderSeatEditor();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Seat editor panel (the full form for whichever seat is selected) ----
|
||||||
|
|
||||||
|
renderSeatEditor() {
|
||||||
|
const title = document.getElementById("seatEditorTitle");
|
||||||
|
const body = document.getElementById("seatEditorBody");
|
||||||
|
|
||||||
|
if (this.selectedSeatIndex === null) {
|
||||||
|
title.textContent = "Select a seat";
|
||||||
|
body.innerHTML = '<p class="fieldHint">Click a seat at the table to edit their name, cards, chips and anything else.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = this.selectedSeatIndex;
|
||||||
|
const player = this.state.seats[index];
|
||||||
|
title.textContent = this.puckLabel(player, index);
|
||||||
|
|
||||||
|
body.innerHTML = "";
|
||||||
|
const template = document.getElementById("seatEditorTemplate");
|
||||||
|
const form = template.content.firstElementChild.cloneNode(true);
|
||||||
|
|
||||||
|
this.wireSeatFields(form, player, index);
|
||||||
|
this.renderChipCounts(form, player, index);
|
||||||
|
this.renderCustomFields(form, player, index);
|
||||||
|
this.renderSeatTotal(form, player);
|
||||||
|
|
||||||
|
body.appendChild(form);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSeatTotal(form, player) {
|
||||||
|
const total = HoldemState.computeContribution(player, this.state.chipLegend, this.state.basePotAmount);
|
||||||
|
form.querySelector(".seatTotalAmount").textContent = Money.format(total);
|
||||||
|
},
|
||||||
|
|
||||||
|
commitSeatChange(form, player, index) {
|
||||||
|
this.hooks.save(this.state);
|
||||||
|
this.updatePotBadge();
|
||||||
|
this.renderSeatTotal(form, player);
|
||||||
|
this.refreshPuck(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
wireSeatFields(form, player, index) {
|
||||||
|
const nameInput = form.querySelector(".seatNameInput");
|
||||||
|
nameInput.value = player.name;
|
||||||
|
nameInput.addEventListener("input", () => {
|
||||||
|
player.name = nameInput.value;
|
||||||
|
document.getElementById("seatEditorTitle").textContent = this.puckLabel(player, index);
|
||||||
|
this.commitSeatChange(form, player, index);
|
||||||
|
});
|
||||||
|
|
||||||
|
const inHandCheckbox = form.querySelector(".inHandCheckbox");
|
||||||
|
inHandCheckbox.checked = player.inHand;
|
||||||
|
inHandCheckbox.addEventListener("change", () => {
|
||||||
|
player.inHand = inHandCheckbox.checked;
|
||||||
|
if (!player.inHand) player.folded = false;
|
||||||
|
foldCheckboxRef.checked = player.folded;
|
||||||
|
foldCheckboxRef.disabled = !player.inHand;
|
||||||
|
this.commitSeatChange(form, player, index);
|
||||||
|
});
|
||||||
|
|
||||||
|
const foldCheckboxRef = form.querySelector(".foldCheckbox");
|
||||||
|
foldCheckboxRef.checked = player.folded;
|
||||||
|
foldCheckboxRef.disabled = !player.inHand;
|
||||||
|
foldCheckboxRef.addEventListener("change", () => {
|
||||||
|
player.folded = foldCheckboxRef.checked;
|
||||||
|
this.commitSeatChange(form, player, index);
|
||||||
|
});
|
||||||
|
|
||||||
|
const card1 = form.querySelector(".card1Input");
|
||||||
|
const card2 = form.querySelector(".card2Input");
|
||||||
|
card1.value = player.cards[0] || "";
|
||||||
|
card2.value = player.cards[1] || "";
|
||||||
|
[card1, card2].forEach((input, cardIdx) => {
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
input.value = input.value.toUpperCase();
|
||||||
|
player.cards[cardIdx] = input.value;
|
||||||
|
this.commitSeatChange(form, player, index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const amountInput = form.querySelector(".amountOverrideInput");
|
||||||
|
amountInput.value = player.amountOverride ?? "";
|
||||||
|
amountInput.addEventListener("input", () => {
|
||||||
|
player.amountOverride = amountInput.value === "" ? null : amountInput.value;
|
||||||
|
this.commitSeatChange(form, player, index);
|
||||||
|
});
|
||||||
|
|
||||||
|
const addFieldBtn = form.querySelector(".addFieldBtn");
|
||||||
|
addFieldBtn.addEventListener("click", () => {
|
||||||
|
player.customFields.push({ label: "", value: "" });
|
||||||
|
this.renderCustomFields(form, player, index);
|
||||||
|
this.hooks.save(this.state);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderChipCounts(form, player, index) {
|
||||||
|
const container = form.querySelector(".chipCounts");
|
||||||
|
container.innerHTML = "";
|
||||||
|
const template = document.getElementById("chipCountTemplate");
|
||||||
|
|
||||||
|
this.state.chipLegend.forEach(chip => {
|
||||||
|
const row = template.content.firstElementChild.cloneNode(true);
|
||||||
|
row.querySelector(".chipSwatch").style.background = chip.color;
|
||||||
|
row.title = `$${chip.value} chips`;
|
||||||
|
const input = row.querySelector(".chipCountInput");
|
||||||
|
input.value = player.chipCounts[chip.id] || 0;
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
const count = Math.max(0, Math.floor(Number(input.value)) || 0);
|
||||||
|
player.chipCounts[chip.id] = count;
|
||||||
|
this.commitSeatChange(form, player, index);
|
||||||
|
});
|
||||||
|
container.appendChild(row);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCustomFields(form, player, index) {
|
||||||
|
const container = form.querySelector(".customFields");
|
||||||
|
container.innerHTML = "";
|
||||||
|
const template = document.getElementById("customFieldTemplate");
|
||||||
|
|
||||||
|
player.customFields.forEach((field, fieldIdx) => {
|
||||||
|
const row = template.content.firstElementChild.cloneNode(true);
|
||||||
|
const labelInput = row.querySelector(".customFieldLabel");
|
||||||
|
const valueInput = row.querySelector(".customFieldValue");
|
||||||
|
labelInput.value = field.label;
|
||||||
|
valueInput.value = field.value;
|
||||||
|
labelInput.addEventListener("input", () => {
|
||||||
|
field.label = labelInput.value;
|
||||||
|
this.hooks.save(this.state);
|
||||||
|
});
|
||||||
|
valueInput.addEventListener("input", () => {
|
||||||
|
field.value = valueInput.value;
|
||||||
|
this.hooks.save(this.state);
|
||||||
|
});
|
||||||
|
row.querySelector(".removeFieldBtn").addEventListener("click", () => {
|
||||||
|
player.customFields.splice(fieldIdx, 1);
|
||||||
|
this.renderCustomFields(form, player, index);
|
||||||
|
this.hooks.save(this.state);
|
||||||
|
});
|
||||||
|
container.appendChild(row);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -242,12 +309,12 @@ const Render = {
|
|||||||
colorInput.addEventListener("input", () => {
|
colorInput.addEventListener("input", () => {
|
||||||
chip.color = colorInput.value;
|
chip.color = colorInput.value;
|
||||||
this.hooks.save(this.state);
|
this.hooks.save(this.state);
|
||||||
this.renderSeats();
|
|
||||||
});
|
});
|
||||||
valueInput.addEventListener("input", () => {
|
valueInput.addEventListener("input", () => {
|
||||||
chip.value = Number(valueInput.value) || 0;
|
chip.value = Number(valueInput.value) || 0;
|
||||||
this.hooks.save(this.state);
|
this.hooks.save(this.state);
|
||||||
this.renderSeats();
|
this.renderSeats();
|
||||||
|
this.renderSeatEditor();
|
||||||
this.updatePotBadge();
|
this.updatePotBadge();
|
||||||
});
|
});
|
||||||
row.querySelector(".removeChipColorBtn").addEventListener("click", () => {
|
row.querySelector(".removeChipColorBtn").addEventListener("click", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user