diff --git a/CHANGELOG.md b/CHANGELOG.md index 88ef3da..2e5bef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ Changelog ------------ - +[v2.3.0] - 2021-08-16 +--------------------- +[GitHub Release Page](https://github.com/ToranSharma/Xporcle-Extension/releases/tag/v2.3.0) +### Added +- Quiz Queue + - Button for hosts to add the quiz they are on to the queue. + - Draggable (for hosts) list of quizzes in the queue. + - Remove button for hosts to remove quizzes from the queue. + - Go to quiz button for hosts to visit the page of quizzes in the queue. + - Controls for hosts to toggle and customise interval for auto changing to + next quiz. + - Option to set default interval for auto chaning. + - Countdown under leaderboard displaying time left until change to next + quiz. + - Button to cancel the countdown for hosts. +- Option to set default duration for next quiz polls. + [v2.2.0] - 2021-08-16 --------------------- [GitHub Release Page](https://github.com/ToranSharma/Xporcle-Extension/releases/tag/v2.2.0) @@ -15,7 +32,6 @@ Changelog ### Changed - Xporcle interface now scrollable if longer than available space. - [v2.1.0] - 2021-08-14 --------------------- [GitHub Release Page](https://github.com/ToranSharma/Xporcle-Extension/releases/tag/v2.1.0) @@ -149,6 +165,7 @@ Changelog - Options page popup placeholder. [Unreleased]: https://github.com/ToranSharma/Xporcle-Extension/compare/master...develop +[v2.3.0]: https://github.com/ToranSharma/Xporcle-Extension/compare/v2.2.0...v2.3.0 [v2.2.0]: https://github.com/ToranSharma/Xporcle-Extension/compare/v2.1.0...v2.2.0 [v2.1.0]: https://github.com/ToranSharma/Xporcle-Extension/compare/v2.0.0...v2.1.0 [v2.0.0]: https://github.com/ToranSharma/Xporcle-Extension/compare/v1.2.1...v2.0.0 diff --git a/background.js b/background.js index 1fdf80f..3e52288 100644 --- a/background.js +++ b/background.js @@ -41,6 +41,8 @@ let pollData = {}; let voteData = {}; let voted = false; let saveName = null; +let queue = []; +let queueInterval; chrome.runtime.onConnect.addListener( (port) => @@ -70,7 +72,9 @@ chrome.runtime.onConnect.addListener( poll_data: pollData, vote_data: voteData, saveName: saveName, - voted: voted + voted: voted, + queue: queue, + queue_interval: queueInterval } ); ws.send(JSON.stringify({type: "url_update", url: message["url"]})) @@ -212,6 +216,8 @@ function forwardMessage(event) if (message["success"]) { hosts = message["hosts"]; + queue = message["queue"]; + queueInterval = message["queue_interval"]; } else { @@ -241,7 +247,9 @@ function forwardMessage(event) { host = true; urls = message["urls"]; - poll_data = message["poll_data"] ?? {} + poll_data = message["poll_data"] ?? {}; + queue = message["queue"]; + queueInterval = message["queue_interval"]; } else if ( messageType === "users_update" @@ -319,6 +327,11 @@ function forwardMessage(event) { voteData = message["vote_data"]; } + else if (messageType === "queue_update") + { + queue = message["queue"]; + queueInterval = message["queue_interval"]; + } if (messagePort !== null) { @@ -340,4 +353,6 @@ function reset() voteData = {}; saveName = null; voted = false; + queue = []; + queueInterval = null; } diff --git a/manifest.json b/manifest.json index 2ab9c3c..e530ad7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name" : "Xporcle", "description" : "Adds real-time multiplayer abilities to Sporcle.com", - "version" : "2.2.0", + "version" : "2.3.0", "manifest_version" : 2, "icons" : { diff --git a/options.html b/options.html index 7ca2524..4a6ea48 100644 --- a/options.html +++ b/options.html @@ -6,7 +6,7 @@ -

Xporcle v2.2.0

+

Xporcle v2.3.0

Enable or Disable Features Here

diff --git a/options.js b/options.js index c1809cc..814a207 100644 --- a/options.js +++ b/options.js @@ -101,6 +101,7 @@ function getOptions(firstLoad=false) document.getElementById(option).checked = options[option]; break; case "string": + case "number": document.getElementById(option).value = options[option]; break; } @@ -120,6 +121,10 @@ function saveOptions() { options[element.id] = element.checked; } + else if (element.type === "number") + { + options[element.id] = Number(element.value); + } else { options[element.id] = element.value; diff --git a/stylesheets/interface.css b/stylesheets/interface.css index 46c9d3b..6279d3d 100644 --- a/stylesheets/interface.css +++ b/stylesheets/interface.css @@ -15,20 +15,6 @@ max-width: 400px; overflow: auto; } - #interfaceContainer form - { - display: flex; - flex-direction: column; - } - #interfaceContainer form input, - #interfaceContainer form button, - #interfaceContainer form select - { - width: min(100%, 10em); - box-sizing: border-box; - margin: 0 auto; - - } .interfaceSection { @@ -52,6 +38,31 @@ margin: 0; } +#interfaceBox .toggle +{ + top: 0.5rem; +} +#interfaceBox form +{ + display: flex; + flex-direction: column; +} + #interfaceBox form input, + #interfaceBox form button, + #interfaceBox form select + { + width: min(100%, 10em); + box-sizing: border-box; + margin: 0 auto; + + } +#interfaceBox #changeQuizCountdown +{ + display: flex; + flex-direction: row; + justify-content: space-evenly; + flex-wrap: wrap; +} #leaderboard, #liveScores @@ -328,7 +339,7 @@ margin: 0; border: 0; position: absolute; - right: 2rem; + right: 1rem; top: 1rem; color: grey; cursor: pointer; @@ -338,6 +349,10 @@ transform: rotate(0deg); transition: transform 0.5s; } +.closeButton ~ .toggle +{ + right: 2rem; +} .toggle::after { content: "\02228"; @@ -349,8 +364,8 @@ } .toggle:checked ~ * { - display: none; - visibility: hidden; + display: none !important; + visibility: hidden !important; } .closeButton::after, @@ -366,3 +381,82 @@ text-align: center; } +.goTo +{ + appearance: none; + border: none; + padding: 0; + position: absolute; + top: 0; + right: 2rem; + width: 1rem; + height: 1rem; + background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij48cGF0aCBkPSJNOSAyTDkgMyAxMi4zIDMgNiA5LjMgNi43IDEwIDEzIDMuNyAxMyA3IDE0IDcgMTQgMlpNNCA0QzIuOSA0IDIgNC45IDIgNkwyIDEyQzIgMTMuMSAyLjkgMTQgNCAxNEwxMCAxNEMxMS4xIDE0IDEyIDEzLjEgMTIgMTJMMTIgNyAxMSA4IDExIDEyQzExIDEyLjYgMTAuNiAxMyAxMCAxM0w0IDEzQzMuNCAxMyAzIDEyLjYgMyAxMkwzIDZDMyA1LjQgMy40IDUgNCA1TDggNSA5IDRaIi8+PC9zdmc+) no-repeat; + background-size: contain; + cursor: pointer; +} + +#quizQueueBox ol +{ + display: grid; + grid-auto-rows: 1fr; + margin: 0; +} + #quizQueueBox li[draggable="true"] + { + cursor: move; + position: relative; + padding-right: 3rem; + } + #quizQueueBox li.moving + { + color: transparent; + } + #quizQueueBox li.moving::marker + { + color: black; + } + #quizQueueBox li .closeButton, + #quizQueueBox li .goTo + { + display: none; + visibility: hidden; + top: 0; + } + #quizQueueBox li:hover .closeButton, + #quizQueueBox li:hover .goTo + { + display: unset; + visibility: visible; + } + #quizQueueBox li:active .closeButton, + #quizQueueBox li.moving .closeButton, + #quizQueueBox li.moving:hover .closeButton, + #quizQueueBox li.moving ~ li .closeButton, + #quizQueueBox li:active .goTo, + #quizQueueBox li.moving .goTo, + #quizQueueBox li.moving:hover .goTo, + #quizQueueBox li.moving ~ li .goTo + { + display: none; + visibility: hidden; + } + #quizQueueBox li .closeButton:hover, + #quizQueueBox li .goTo:hover + { + display: unset; + visibility: visible; + } +#quizQueueBox form +{ + display: block; +} + #quizQueueBox form input + { + display: inline; + vertical-align: baseline; + } + #quizQueueBox form input[type="number"] + { + width: 3.5em; + } diff --git a/stylesheets/options.css b/stylesheets/options.css index 49d0cb2..832fe84 100644 --- a/stylesheets/options.css +++ b/stylesheets/options.css @@ -78,17 +78,21 @@ ul { color: grey; } -#optionTree .labelFirst +#optionTree li.labelFirst +{ + grid-template-columns: max-content 4em; +} +#optionTree ul li.labelFirst { grid-template-columns: 3em max-content 3em; } - #optionTree .labelFirst > label - { - padding-left: 0; - padding-right: 0.5em; - z-index: 3; - background-color: white; - } +#optionTree .labelFirst > label +{ + padding-left: 0; + padding-right: 0.5em; + z-index: 3; + background-color: white; +} #optionTree .labelFirst.containsTextInput { @@ -119,6 +123,17 @@ ul line-height: 1.5em; } +#optionTree .containsTime::after +{ + content:"s"; + height: 1.5em; + line-height: 1.5em; +} +#optionTree > li.labelFirst.containsTime +{ + grid-template-columns: max-content 4em auto; +} + button { width: 6em; diff --git a/xporcle.js b/xporcle.js index ce8f820..865bc98 100644 --- a/xporcle.js +++ b/xporcle.js @@ -143,7 +143,9 @@ async function init() const pollData = Object.keys(statusResponse["poll_data"]).length !== 0 ? statusResponse["poll_data"] : undefined; const voteData = Object.keys(statusResponse["vote_data"]).length !== 0 ? statusResponse["vote_data"] : undefined; - onRoomConnect(statusResponse["scores"], pollData, voteData); + const queue = Object.keys(statusResponse["queue"]).length !== 0 ? statusResponse["queue"] : undefined; + const queueInterval = statusResponse["queue_interval"]; + onRoomConnect(statusResponse["scores"], pollData, voteData, queue, queueInterval); } else { @@ -203,7 +205,9 @@ function retrieveOptions() { useDefaultUsername: false, defaultUsername: "", - blurRoomCode: false + blurRoomCode: false, + defaultPollDuration: 30, + defaultQuizQueueInterval: 60 }; if (Object.entries(data).length === 0) @@ -260,11 +264,11 @@ function applyOptions() // Blur Room Code if (options.blurRoomCode) { - document.body.classList.add(".blurRoomCode"); + document.body.classList.add("blurRoomCode"); } else { - document.body.classList.remove(".blurRoomCode"); + document.body.classList.remove("blurRoomCode"); } } @@ -353,9 +357,7 @@ function resetInterface(errorElement, lastUsername, lastCode) setQuizStartProvention(false); document.querySelectorAll("#startCountdown").forEach(elm => elm.remove()); - document.querySelector("#pollBox")?.remove(); - document.querySelector("#voteInfoBox")?.remove(); - document.querySelector("#ballotPopout")?.remove(); + document.querySelectorAll(".interfaceSection:not(#interfaceBox)").forEach(section => section.remove()); init().then( () => @@ -442,7 +444,16 @@ function processMessage(message) updateLeaderboardUrls(); suggestions = []; - document.querySelectorAll(`#changeQuizButton, #suggestionsHeader, #suggestionsList, #saveButton, #createPollButton`).forEach(element => element.remove()); + document.querySelectorAll( + ` + #changeQuizButton, + #suggestionsHeader, #suggestionsList, + #saveButton, + #createPollButton, #pollBox, + #addQuizToQueueButton, #quizQueueBox form, #quizQueueBox ol button + ` + ).forEach(element => element.remove()); + document.querySelectorAll(`#quizQueueBox ol li`).forEach(li => li.removeAttribute("draggable")); // Add non host features if (onQuizPage) @@ -466,6 +477,16 @@ function processMessage(message) updateLeaderboardUrls(); updateContextMenuHandling(); addSaveRoomButton(); + if (onQuizPage) + { + interfaceBox.append(addQuizToQueueButton()); + } + if (message["queue"] !== null) + { + document.querySelector("#quizQueueBox")?.remove(); + addQuizQueueBox(message["queue"], message["queue_interval"]); + } + if (message["poll_data"] !== null) { addCreatePollBox(message["poll_data"]); @@ -528,8 +549,51 @@ function processMessage(message) case "vote_update": updateVoteInfoBox(message["vote_data"]); break; + case "queue_update": + updateQuizQueue(message["queue"], message["queue_interval"]); + break; + case "start_change_quiz_countdown": + { + const endTime = (new Date()).getTime() + message["countdown_length"]*1000; + const timeLeft = () => (Math.max(0, endTime - (new Date()).getTime())/1000).toFixed(1); + const countdown = document.createElement("div"); + countdown.id = "changeQuizCountdown"; + countdown.append(document.createElement("span")); + countdown.firstChild.textContent = `Changing to next quiz in ${timeLeft()}s`; + setInterval( + () => + { + if (countdown.firstChild.textContent !== "Change quiz countdown cancelled") + { + countdown.firstChild.textContent = `Changing to next quiz in ${timeLeft()}s`; + } + } + , 100 + ); + if (host) + { + const cancelButton = document.createElement("button"); + cancelButton.textContent = "Cancel Countdown"; + cancelButton.addEventListener("click", + (event) => + { + port.postMessage({type: "change_queue_interval", queue_interval: null}); + } + ); + countdown.append(cancelButton); + } + document.querySelector("#interfaceBox")?.insertBefore(countdown, document.querySelector("#leaderboard").nextElementSibling); + break; + } + case "cancel_change_quiz_countdown": + { + const countdownMessage = document.querySelector("#changeQuizCountdown span"); + countdownMessage.textContent = "Change quiz countdown cancelled"; + document.querySelector("#changeQuizCountdown button")?.remove(); + setTimeout(() => countdownMessage.parentNode.remove(), 2000); + break; + } } - } function updateHostsInLeaderboard() @@ -639,7 +703,7 @@ async function joinRoom(event) username: username, code: roomCode, }; - + let queue = undefined; try { port.postMessage({type: "startConnection", initialMessage: message}); @@ -656,6 +720,7 @@ async function joinRoom(event) { hosts = message["hosts"]; port.postMessage({type: "url_update", url: window.location.pathname}) + queue = message["queue"] ?? undefined; // Set up message handing port.onMessage.addListener(processMessage); @@ -689,7 +754,7 @@ async function joinRoom(event) return; } - onRoomConnect(); + onRoomConnect(undefined, undefined, undefined, queue); } async function loadRoom(event, form) @@ -774,7 +839,7 @@ async function loadRoom(event, form) onRoomConnect(); } -function onRoomConnect(existingScores, existingPollData, currentVoteData) +function onRoomConnect(existingScores, existingPollData, currentVoteData, queue, queueInterval) { // Set up message handing if not done already. if (!port.onMessage.hasListener(processMessage)) @@ -795,6 +860,8 @@ function onRoomConnect(existingScores, existingPollData, currentVoteData) roomCodeHeader.lastChild.textContent = roomCode; interfaceBox.insertBefore(roomCodeHeader, interfaceBox.firstElementChild); + interfaceBox.insertBefore(collapseToggle(),roomCodeHeader.nextElementSibling); + // If the user is a host and is on a quiz, // add a button to send the quiz to the rest of the room if (host && onQuizPage) @@ -833,6 +900,8 @@ function onRoomConnect(existingScores, existingPollData, currentVoteData) if (host) { addSaveRoomButton(); + + // Poll if (existingPollData) { addCreatePollBox(existingPollData); @@ -841,6 +910,12 @@ function onRoomConnect(existingScores, existingPollData, currentVoteData) { addCreatePollButton(); } + + // Add Quiz to Queue + if (onQuizPage) + { + interfaceBox.append(addQuizToQueueButton()); + } } // Add vote info for current poll @@ -853,6 +928,13 @@ function onRoomConnect(existingScores, existingPollData, currentVoteData) } } + // Quiz Queue + if (queue) + { + addQuizQueueBox(queue, queueInterval); + } + + // If on a quiz page, observe for the start of the quiz if (onQuizPage) { @@ -926,14 +1008,22 @@ function addChangeQuizButton() ); // The button goes just after the room code header - interfaceBox.insertBefore(changeQuizButton, interfaceBox.querySelector(`#roomCodeHeader`).nextElementSibling); + interfaceBox.insertBefore(changeQuizButton, interfaceBox.querySelector(`.toggle`).nextElementSibling); } -function addSuggestionQuizButton() +function currentQuizInfo() { + const url = window.location.href; const shortTitle = document.querySelector(`title`).textContent; const longTitle = document.querySelector("#gameMeta>h2").textContent; + return {url: url, short_title: shortTitle, long_title: longTitle}; +} + +function addSuggestionQuizButton() +{ + const quizInfo = currentQuizInfo(); + const suggestQuizButton = document.createElement("button"); suggestQuizButton.id = "suggestQuizButton"; suggestQuizButton.textContent = "Suggest Quiz to Hosts"; @@ -943,9 +1033,9 @@ function addSuggestionQuizButton() port.postMessage( { type: "suggest_quiz", - url: window.location.href, - short_title: shortTitle, - long_title: longTitle + url: quizInfo.url, + short_title: quizInfo.short_title, + long_title: quizInfo.long_title } ); } @@ -962,7 +1052,7 @@ function addCreatePollButton() createPollButton.addEventListener("click", (event) => { - const pollData = {duration: 30, entries: []}; + const pollData = {duration: options.defaultPollDuration, entries: []}; addCreatePollBox(pollData); port.postMessage({type: "poll_create", poll_data: pollData}); createPollButton.remove(); @@ -989,12 +1079,7 @@ function addCreatePollBox(pollData) if (onQuizPage) { // Get info about quiz from page - const currentQuiz = - { - url: window.location.href, - short_title: document.querySelector(`title`).textContent, - long_title: document.querySelector("#gameMeta>h2").textContent - }; + const quizInfo = currentQuizInfo(); const addCurrentQuizToPollButton = document.createElement("button"); addCurrentQuizToPollButton.textContent = "Add Quiz to Poll"; @@ -1007,7 +1092,7 @@ function addCreatePollBox(pollData) pollData.entries.push(currentQuiz); const newEntryListItem = document.createElement("li"); - newEntryListItem.textContent = currentQuiz.short_title; + newEntryListItem.textContent = quizInfo.short_title; removeEntryButton = document.createElement("div"); removeEntryButton.textContent = "×"; @@ -1015,7 +1100,7 @@ function addCreatePollBox(pollData) (event) => { newEntryListItem.remove(); - pollData.entries = pollData.entries.filter(existingEntry => existingEntry.url !== currentQuiz.url); + pollData.entries = pollData.entries.filter(existingEntry => existingEntry.url !== quizInfo.url); port.postMessage({type:"poll_data_update", poll_data: pollData}); } ); @@ -1062,7 +1147,7 @@ function addCreatePollBox(pollData) pollDurationInput.addEventListener("change", (event) => { - pollData.duration = Math.max(10, Math.min(pollDurationInput.value, 60)); + pollData.duration = Math.max(10, Math.min(Number(pollDurationInput.value), 60)); // Send updated data port.postMessage({type:"poll_data_update", poll_data: pollData}); } @@ -2345,3 +2430,214 @@ function collapseToggle() return toggle; } + +function addQuizToQueueButton() +{ + const button = document.createElement("button"); + button.textContent = "Add Quiz to Queue"; + button.addEventListener("click", + (event) => + { + const quizInfo = currentQuizInfo(); + const existingQueuedQuizes = Array.from(document.querySelectorAll("#quizQueueBox ol li")); + + if (!existingQueuedQuizes.find(queuedQuiz => queuedQuiz.short_title === quizInfo.short_title)) + { + port.postMessage({type: "add_to_queue", quiz: quizInfo}); + } + } + ); + return button; +} + +function addQuizQueueBox(queue = [], queueInterval) +{ + const quizQueueBox = document.createElement("div"); + quizQueueBox.classList.add("interfaceSection"); + quizQueueBox.id = "quizQueueBox"; + + quizQueueBox.append(closeButton(quizQueueBox)); + + const header = document.createElement("h2"); + header.textContent = "Quiz Queue"; + quizQueueBox.append(header); + + quizQueueBox.append(collapseToggle()); + + const queueList = document.createElement("ol"); + quizQueueBox.append(queueList); + + if (host) + { + const queueAutoChangeForm = document.createElement("form"); + + const autoChangeToggleInput = document.createElement("input"); + autoChangeToggleInput.type = "checkbox"; + autoChangeToggleInput.checked = !!queueInterval; + const intervalLabel = document.createElement("label"); + intervalLabel.textContent = "auto change quiz after "; + const intervalInput = document.createElement("input"); + intervalInput.type = "number"; + intervalInput.min = 10; + intervalInput.max = 60*5; + intervalInput.value = queueInterval ?? options.defaultQuizQueueInterval; + intervalInput.disabled = !autoChangeToggleInput.checked; + [autoChangeToggleInput, intervalInput].forEach( + (input) => + { + input.addEventListener("change", + (event) => + { + intervalInput.disabled = !autoChangeToggleInput.checked; + intervalInput.value = Math.max(10, Math.min(Number(intervalInput.value), 60*5)); + const newQueueInterval = autoChangeToggleInput.checked ? Number(intervalInput.value) : null; + port.postMessage({type: "change_queue_interval", queue_interval: newQueueInterval}); + } + ); + } + ); + queueAutoChangeForm.append( + autoChangeToggleInput, intervalLabel, intervalInput, document.createTextNode("s") + ); + quizQueueBox.append(queueAutoChangeForm); + } + + sectionContainer.append(quizQueueBox); + + updateQuizQueue(queue, queueInterval); +} + +function updateQuizQueue(queue, queueInterval) +{ + let quizQueueBox = document.querySelector("#quizQueueBox"); + if (queue.length === 0) + { + return quizQueueBox?.remove(); + } + if (quizQueueBox === null) + { + return addQuizQueueBox(queue, queueInterval); + } + + + const queueList = quizQueueBox.querySelector("ol"); + Array.from(queueList.querySelectorAll("li")) + .filter(li => !queue.some(queuedQuiz => queuedQuiz.short_title === li.textContent)) + .forEach(li => li.remove()); + + queue.forEach( + (queuedQuiz) => + { + let li = Array.from(queueList.querySelectorAll("li")).find(li => li.textContent === queuedQuiz.short_title); + if (li === undefined) + { + li = document.createElement("li"); + li.textContent = queuedQuiz.short_title; + if (host) + { + // Add go to button + const goToButton = document.createElement("button"); + goToButton.classList.add("goTo"); + goToButton.addEventListener("click", (event) => {window.location = queuedQuiz.url}); + li.append(goToButton); + // Add Remove Button + li.append( + closeButton(li, + (event) => + { + port.postMessage({type: "remove_from_queue", quiz: queuedQuiz}); + } + ) + ); + // Make list draggable to reorder queue. + li.setAttribute("draggable", "true"); + li.addEventListener("dragstart", + (event) => + { + const quizInfoString = JSON.stringify(queuedQuiz); + event.dataTransfer.setData("text/quizinfo", quizInfoString); + const dragImage = document.createElement("div"); + dragImage.id = "tempDragImage"; + dragImage.style = + ` + position: absolute; + top: -2000vh; + left: -2000vw; + width: calc(${event.target.clientWidth}px - ${window.getComputedStyle(event.target).paddingRight}); + `; + dragImage.textContent = queuedQuiz.short_title; + document.body.append(dragImage); + + event.dataTransfer.setDragImage(dragImage, 30, dragImage.clientHeight/2); + event.dataTransfer.effectAllowed = "move"; + event.target.classList.add("moving"); + } + ); + li.addEventListener("dragend", + (event) => + { + event.target.classList.remove("moving"); + event.target.style = ""; + document.querySelector("#tempDragImage")?.remove(); + } + ); + li.addEventListener("dragenter", + (event) => + { + if (event.dataTransfer.types.includes("text/quizinfo")) + { + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + const beingDragged = queueList.querySelector(".moving"); + + const indexOffset = + Array.from(queueList.childNodes).indexOf(event.target) + - Array.from(queueList.childNodes).indexOf(beingDragged); + if (indexOffset > 0) + { + // Element being dragged is above the target. + // So we want to move the dragged element to just below/after it. + queueList.insertBefore(beingDragged, event.target.nextElementSibling); + } + else + { + // Element being dragged is below the target + // So we want to move the dragged element to just above/before it. + queueList.insertBefore(beingDragged, event.target); + } + } + } + ); + li.addEventListener("dragover", + (event) => + { + if (event.dataTransfer.types.includes("text/quizinfo")) + { + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + } + } + ); + li.addEventListener("drop", + (event) => + { + const newIndex = Array.from(queueList.children).indexOf(document.querySelector(".moving")); + port.postMessage({type: "reorder_queue", quiz: queuedQuiz, index: newIndex}); + } + ); + } + } + queueList.append(li); + } + ); + + if (host) + { + // Update Queue Interval + const autoChangeToggleInput = quizQueueBox.querySelector(`form input[type="checkbox"]`); + const intervalInput = quizQueueBox.querySelector(`form input[type="number"]`); + autoChangeToggleInput.checked = !!queueInterval; + intervalInput.disabled = !queueInterval; + intervalInput.value = queueInterval ?? intervalInput.value; + } +}