Files
devplacepy/devplacepy/static/js/PollManager.js
T
retoor cae139ead9 Add personal notes, DeepSearch history, backup offload, and gateway auth throttling
Also streamline the top navigation: drop the Tools dropdown, make Quizzes
and Battles icon-only entries, and remove the Workspace, Containers and
Editor entry points from the project detail page.
2026-09-12 20:32:31 +02:00

181 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.
// retoor <retoor@molodetz.nl>
import { OptimisticAction } from "./OptimisticAction.js";
export class PollManager extends OptimisticAction {
constructor() {
super();
document.addEventListener("click", (event) => this.onClick(event));
document.addEventListener("submit", (event) => this.onSubmit(event), true);
}
onClick(event) {
const option = event.target.closest(".poll-option");
if (option && !option.disabled) {
event.preventDefault();
this.vote(option);
return;
}
const toggle = event.target.closest("[data-poll-toggle]");
if (toggle) {
event.preventDefault();
this.toggleBuilder(toggle);
return;
}
const addOption = event.target.closest("[data-poll-add-option]");
if (addOption) {
event.preventDefault();
this.addOption(addOption);
return;
}
const removeOption = event.target.closest("[data-poll-remove-option]");
if (removeOption) {
event.preventDefault();
this.removeOption(removeOption);
}
}
async vote(option) {
const poll = option.closest(".poll");
if (!poll) {
return;
}
const pollUid = poll.dataset.pollUid;
const optionUid = option.dataset.optionUid;
await this.submit(`/polls/${pollUid}/vote`, { option_uid: optionUid }, null, (result) => this.render(poll, result), option);
}
render(poll, result) {
const byUid = {};
(result.options || []).forEach((option) => {
byUid[option.uid] = option;
});
poll.querySelectorAll(".poll-option").forEach((button) => {
const data = byUid[button.dataset.optionUid];
if (!data) {
return;
}
const chosen = result.my_choice === data.uid;
button.classList.toggle("chosen", chosen);
button.setAttribute("aria-pressed", chosen ? "true" : "false");
const bar = button.querySelector(".poll-option-bar");
if (bar) {
bar.style.setProperty("--poll-pct", `${data.pct}%`);
}
const pct = button.querySelector(".poll-option-pct");
if (pct) {
pct.textContent = `${data.pct}%`;
}
const check = button.querySelector(".poll-option-check");
if (check) {
check.hidden = !chosen;
}
});
const total = poll.querySelector(".poll-total");
if (total) {
total.textContent = `${result.total} vote${result.total === 1 ? "" : "s"}`;
}
}
toggleBuilder(toggle) {
const form = toggle.closest("form");
const builder = form ? form.querySelector("[data-poll-builder]") : null;
if (!builder) {
return;
}
const activating = builder.hidden;
builder.hidden = !activating;
this.setPollEnabled(builder, activating);
const label = toggle.querySelector("[data-poll-toggle-label]");
if (label) {
label.textContent = activating ? "Remove poll" : "Add poll";
}
}
setPollEnabled(builder, enabled) {
builder.querySelectorAll("input[name='poll_question'], input[name='poll_options']").forEach((input) => {
input.disabled = !enabled;
if (!enabled) {
input.value = "";
}
});
const error = builder.querySelector("[data-poll-error]");
if (error) {
error.hidden = true;
error.textContent = "";
}
}
addOption(button) {
const form = button.closest("form");
const container = form ? form.querySelector("[data-poll-options]") : null;
if (!container) {
return;
}
const rows = container.querySelectorAll(".poll-builder-row");
if (rows.length >= 6) {
return;
}
const row = document.createElement("div");
row.className = "poll-builder-row";
const input = document.createElement("input");
input.type = "text";
input.name = "poll_options";
input.maxLength = 100;
input.placeholder = `Option ${rows.length + 1}`;
const remove = document.createElement("button");
remove.type = "button";
remove.className = "poll-builder-remove";
remove.dataset.pollRemoveOption = "";
remove.setAttribute("aria-label", "Remove option");
remove.textContent = "×";
row.appendChild(input);
row.appendChild(remove);
container.appendChild(row);
}
removeOption(button) {
const container = button.closest("[data-poll-options]");
if (!container) {
return;
}
if (container.querySelectorAll(".poll-builder-row").length <= 2) {
return;
}
const row = button.closest(".poll-builder-row");
if (row) {
row.remove();
}
}
onSubmit(event) {
const form = event.target;
if (!form || typeof form.querySelector !== "function") {
return;
}
const builder = form.querySelector("[data-poll-builder]");
if (!builder || builder.hidden) {
return;
}
const question = builder.querySelector("input[name='poll_question']");
const options = [...builder.querySelectorAll("input[name='poll_options']")]
.map((input) => input.value.trim())
.filter(Boolean);
let problem = "";
if (!question || !question.value.trim()) {
problem = "Add a poll question, or remove the poll.";
} else if (options.length < 2) {
problem = "Add at least two poll options, or remove the poll.";
}
if (problem) {
event.preventDefault();
event.stopImmediatePropagation();
const error = builder.querySelector("[data-poll-error]");
if (error) {
error.textContent = problem;
error.hidden = false;
}
}
}
}