Compare commits

...

2 Commits

Author SHA1 Message Date
e4e555cac3 Update 2026-03-07 01:12:36 +02:00
2b63370873 Updated password fields for autocomplete ignoring 2026-03-07 01:07:43 +02:00
4 changed files with 167 additions and 20 deletions

View File

@@ -86,7 +86,20 @@
<div class="field-group"> <div class="field-group">
<label for="room-password">Room password (optional)</label> <label for="room-password">Room password (optional)</label>
<input type="password" id="room-password" name="password" maxlength="64" placeholder="Optional password"> <input
type="password"
id="room-password"
name="password"
maxlength="64"
placeholder="Optional password"
autocomplete="new-password"
data-bwignore="true"
data-1p-ignore="true"
data-lpignore="true"
autocapitalize="off"
autocorrect="off"
spellcheck="false"
>
</div> </div>
</div> </div>

View File

@@ -362,6 +362,17 @@ body.is-dragging-window .ui-tool-title-bar {
min-width: 0; min-width: 0;
} }
.side-panel-window {
display: flex;
flex-direction: column;
min-width: 0;
}
.side-panel-window > .window-content {
flex: 1;
min-height: 0;
}
.participants-window { .participants-window {
min-height: 24rem; min-height: 24rem;
} }
@@ -421,12 +432,13 @@ body.is-dragging-window .ui-tool-title-bar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.55rem; gap: 0.55rem;
height: 100%; flex: 1;
min-height: 0;
} }
.participants-scroll { .participants-scroll {
flex: 1; flex: 1;
min-height: 13rem; min-height: 0;
overflow-y: auto; overflow-y: auto;
} }

View File

@@ -203,7 +203,9 @@ function parseNumericVote(value) {
function calculateSummary(state) { function calculateSummary(state) {
const rows = new Map(); const rows = new Map();
const numericVotes = []; const scoreVotes = [];
const cardIndexByValue = new Map(state.cards.map((cardValue, index) => [cardValue, index]));
let hasNumericVote = false;
state.participants.forEach((participant) => { state.participants.forEach((participant) => {
if (!participant.connected) { if (!participant.connected) {
@@ -221,26 +223,42 @@ function calculateSummary(state) {
const numeric = parseNumericVote(participant.voteValue); const numeric = parseNumericVote(participant.voteValue);
if (numeric !== null) { if (numeric !== null) {
numericVotes.push(numeric); hasNumericVote = true;
scoreVotes.push(numeric);
return;
}
if (cardIndexByValue.has(participant.voteValue)) {
scoreVotes.push(cardIndexByValue.get(participant.voteValue));
} }
}); });
let average = null; let average = null;
if (numericVotes.length > 0) { if (scoreVotes.length > 0) {
average = numericVotes.reduce((acc, value) => acc + value, 0) / numericVotes.length; average = scoreVotes.reduce((acc, value) => acc + value, 0) / scoreVotes.length;
} }
let averageCard = null;
let recommended = null;
if (!hasNumericVote && average !== null && state.cards.length > 0) {
const roundedIndex = Math.min(
state.cards.length - 1,
Math.max(0, Math.round(average)),
);
averageCard = state.cards[roundedIndex];
recommended = state.cards[roundedIndex];
} else {
const deckNumeric = state.cards const deckNumeric = state.cards
.map(parseNumericVote) .map(parseNumericVote)
.filter((value) => value !== null) .filter((value) => value !== null)
.sort((a, b) => a - b); .sort((a, b) => a - b);
let recommended = null;
if (average !== null && deckNumeric.length > 0) { if (average !== null && deckNumeric.length > 0) {
recommended = deckNumeric.find((value) => value >= average) ?? deckNumeric[deckNumeric.length - 1]; recommended = deckNumeric.find((value) => value >= average) ?? deckNumeric[deckNumeric.length - 1];
} }
}
return { rows, average, recommended }; return { rows, average, recommended, averageCard };
} }
function renderSummary(state) { function renderSummary(state) {
@@ -251,7 +269,7 @@ function renderSummary(state) {
return; return;
} }
const { rows, average, recommended } = calculateSummary(state); const { rows, average, recommended, averageCard } = calculateSummary(state);
summaryBody.innerHTML = ''; summaryBody.innerHTML = '';
if (rows.size === 0) { if (rows.size === 0) {
@@ -274,7 +292,11 @@ function renderSummary(state) {
}); });
} }
if (averageCard !== null) {
summaryAverage.textContent = `Average: ${averageCard}`;
} else {
summaryAverage.textContent = average === null ? 'Average: -' : `Average: ${average.toFixed(2)}`; summaryAverage.textContent = average === null ? 'Average: -' : `Average: ${average.toFixed(2)}`;
}
summaryRecommended.textContent = recommended === null ? 'Recommended: -' : `Recommended: ${recommended}`; summaryRecommended.textContent = recommended === null ? 'Recommended: -' : `Recommended: ${recommended}`;
} }
@@ -400,6 +422,34 @@ function updateShareLink() {
shareLinkInput.value = raw ? `${window.location.origin}${raw}` : ''; shareLinkInput.value = raw ? `${window.location.origin}${raw}` : '';
} }
function fallbackCopyFromShareInput() {
shareLinkInput.focus();
shareLinkInput.select();
shareLinkInput.setSelectionRange(0, shareLinkInput.value.length);
document.execCommand('copy');
}
async function selectAndCopyShareLink() {
if (!shareLinkInput.value) {
return;
}
shareLinkInput.focus();
shareLinkInput.select();
shareLinkInput.setSelectionRange(0, shareLinkInput.value.length);
if (!navigator.clipboard || !window.isSecureContext) {
fallbackCopyFromShareInput();
return;
}
try {
await navigator.clipboard.writeText(shareLinkInput.value);
} catch (_err) {
fallbackCopyFromShareInput();
}
}
function connectSSE() { function connectSSE() {
if (eventSource) { if (eventSource) {
eventSource.close(); eventSource.close();
@@ -502,6 +552,9 @@ async function changeName() {
revealBtn.addEventListener('click', () => adminAction('reveal')); revealBtn.addEventListener('click', () => adminAction('reveal'));
resetBtn.addEventListener('click', () => adminAction('reset')); resetBtn.addEventListener('click', () => adminAction('reset'));
shareAdminToggle.addEventListener('change', updateShareLink); shareAdminToggle.addEventListener('change', updateShareLink);
shareLinkInput.addEventListener('click', () => {
void selectAndCopyShareLink();
});
changeNameBtn.addEventListener('click', () => { changeNameBtn.addEventListener('click', () => {
void changeName(); void changeName();
}); });

View File

@@ -40,6 +40,10 @@
return Math.min(max, Math.max(min, value)); return Math.min(max, Math.max(min, value));
} }
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function parseJSON(value, fallback) { function parseJSON(value, fallback) {
if (!value) { if (!value) {
return fallback; return fallback;
@@ -153,7 +157,58 @@
saveWindowLayouts(); saveWindowLayouts();
} }
function ensureWindowLayout(def) { function measureLayoutFromContent(def) {
const windowEl = def.el;
const fallback = def.defaultLayout;
const wasHidden = windowEl.classList.contains('hidden');
const previousStyles = {
visibility: windowEl.style.visibility,
left: windowEl.style.left,
top: windowEl.style.top,
width: windowEl.style.width,
height: windowEl.style.height,
right: windowEl.style.right,
bottom: windowEl.style.bottom,
transform: windowEl.style.transform,
};
if (wasHidden) {
windowEl.classList.remove('hidden');
}
windowEl.style.visibility = 'hidden';
windowEl.style.left = '-10000px';
windowEl.style.top = '-10000px';
windowEl.style.width = 'max-content';
windowEl.style.height = 'auto';
windowEl.style.right = 'auto';
windowEl.style.bottom = 'auto';
windowEl.style.transform = 'none';
const rect = windowEl.getBoundingClientRect();
windowEl.style.visibility = previousStyles.visibility;
windowEl.style.left = previousStyles.left;
windowEl.style.top = previousStyles.top;
windowEl.style.width = previousStyles.width;
windowEl.style.height = previousStyles.height;
windowEl.style.right = previousStyles.right;
windowEl.style.bottom = previousStyles.bottom;
windowEl.style.transform = previousStyles.transform;
if (wasHidden) {
windowEl.classList.add('hidden');
}
return normalizeLayout({
left: fallback.left,
top: fallback.top,
width: Number.isFinite(rect.width) && rect.width > 0 ? Math.ceil(rect.width) : fallback.width,
height: Number.isFinite(rect.height) && rect.height > 0 ? Math.ceil(rect.height) : fallback.height,
}, fallback);
}
function ensureWindowLayout(def, options = {}) {
if (isMobileViewport()) { if (isMobileViewport()) {
def.el.style.right = 'auto'; def.el.style.right = 'auto';
def.el.style.bottom = 'auto'; def.el.style.bottom = 'auto';
@@ -161,7 +216,12 @@
return; return;
} }
const normalized = normalizeLayout(windowLayouts[def.id], def.defaultLayout); const hasSavedLayout = hasOwn(options, 'hasSavedLayout')
? options.hasSavedLayout
: hasOwn(windowLayouts, def.id);
const normalized = hasSavedLayout
? normalizeLayout(windowLayouts[def.id], def.defaultLayout)
: measureLayoutFromContent(def);
const clamped = clampLayoutToViewport(normalized); const clamped = clampLayoutToViewport(normalized);
applyLayout(def.el, clamped); applyLayout(def.el, clamped);
windowLayouts[def.id] = clamped; windowLayouts[def.id] = clamped;
@@ -284,11 +344,20 @@
function initWindowLayouts() { function initWindowLayouts() {
windowLayouts = loadWindowLayouts(); windowLayouts = loadWindowLayouts();
let shouldPersistSeededLayouts = false;
windowDefs.forEach((def) => { windowDefs.forEach((def) => {
ensureWindowLayout(def); const hasSavedLayout = hasOwn(windowLayouts, def.id);
ensureWindowLayout(def, { hasSavedLayout });
if (!hasSavedLayout) {
shouldPersistSeededLayouts = true;
}
}); });
if (shouldPersistSeededLayouts) {
saveWindowLayouts();
}
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
if (isMobileViewport()) { if (isMobileViewport()) {
return; return;