diff --git a/ui/index.html b/ui/index.html index 4f62b5b4..fe848321 100644 --- a/ui/index.html +++ b/ui/index.html @@ -788,6 +788,7 @@ + diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 3195969c..306a8d25 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -3,7 +3,6 @@ const MAX_INIT_IMAGE_DIMENSION = 768 const MIN_GPUS_TO_SHOW_SELECTION = 2 const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64") -const htmlTaskMap = new WeakMap() const spinnerPacmanHtml = '
" - if (this.exception) { - msg += `Error: ${this.exception.message}" - logError(msg, event, outputMsg) - } - break - } - } - if ("update" in event) { - const stepUpdate = event.update - if (!("step" in stepUpdate)) { - return - } - // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) - const overallStepCount = - task.instances.reduce( - (sum, instance) => - sum + - (instance.isPending - ? Math.max(0, instance.step || stepUpdate.step) / - (instance.total_steps || stepUpdate.total_steps) - : 1), - 0 // Initial value - ) * stepUpdate.total_steps // Scale to current number of steps. - const totalSteps = task.instances.reduce( - (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), - stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) - ) - const percent = Math.min(100, 100 * (overallStepCount / totalSteps)).toFixed(0) - - const timeTaken = stepUpdate.step_time // sec - const stepsRemaining = Math.max(0, totalSteps - overallStepCount) - const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) - outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` - outputMsg.style.display = "block" - progressBarInner.style.width = `${percent}%` - - if (stepUpdate.output) { - showImages(reqBody, stepUpdate, outputContainer, true) - } - } - } -} - -function abortTask(task) { - if (!task.isProcessing) { - return false - } - task.isProcessing = false - task.progressBar.classList.remove("active") - task["taskStatusLabel"].style.display = "none" - task["stopTask"].innerHTML = ' Remove' - if (!task.instances?.some((r) => r.isPending)) { - return - } - task.instances.forEach((instance) => { - try { - instance.abort() - } catch (e) { - console.error(e) - } - }) -} - -function onTaskErrorHandler(task, reqBody, instance, reason) { - if (!task.isProcessing) { - return - } - console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) - abortTask(task) - const outputMsg = task["outputMsg"] - logError( - "Stable Diffusion had an error. Please check the logs in the command-line window.
` - } - try { - // 'Response': body stream already read - msg += "Read: " + (await event.response.text()) - } catch (e) { - msg += "Unexpected end of stream. " - } - const bufferString = event.reader.bufferedString - if (bufferString) { - msg += "Buffered data: " + bufferString - } - msg += "
" + - reason.stack + - "", - task, - outputMsg - ) - setStatus("request", "error", "error") -} - -function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { - if (typeof stepUpdate === "object") { - if (stepUpdate.status === "succeeded") { - showImages(reqBody, stepUpdate, outputContainer, false) - } else { - task.isProcessing = false - const outputMsg = task["outputMsg"] - let msg = "" - if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { - msg = stepUpdate.detail - if (msg.toLowerCase().includes("out of memory")) { - msg += `
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}` - } - logError(msg, stepUpdate, outputMsg) - } - } - if (task.isProcessing && task.batchesDone < task.batchCount) { - task["taskStatusLabel"].innerText = "Pending" - task["taskStatusLabel"].classList.add("waitingTaskLabel") - task["taskStatusLabel"].classList.remove("activeTaskLabel") - return - } - if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { - return - } - - task.isProcessing = false - task["stopTask"].innerHTML = ' Remove' - task["taskStatusLabel"].style.display = "none" - - let time = millisecondsToStr(Date.now() - task.startTime) - - if (task.batchesDone == task.batchCount) { - if (!task.outputMsg.innerText.toLowerCase().includes("error")) { - task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` - } - task.progressBar.style.height = "0px" - task.progressBar.style.border = "0px solid var(--background-color3)" - task.progressBar.classList.remove("active") - setStatus("request", "done", "success") - } else { - task.outputMsg.innerText += `. Task ended after ${time}` - } - - if (randomSeedField.checked) { - seedField.value = task.seed - } - - if (SD.activeTasks.size > 0) { - return - } - const uncompletedTasks = getUncompletedTaskEntries() - if (uncompletedTasks && uncompletedTasks.length > 0) { - return - } - - if (pauseClient) { - resumeBtn.click() - } - renderButtons.style.display = "none" - renameMakeImageButton() - - if (isSoundEnabled()) { - playSound() - } -} - -async function onTaskStart(task) { - if (!task.isProcessing || task.batchesDone >= task.batchCount) { - return - } - - if (typeof task.startTime !== "number") { - task.startTime = Date.now() - } - if (!("instances" in task)) { - task["instances"] = [] - } - - task["stopTask"].innerHTML = ' Stop' - task["taskStatusLabel"].innerText = "Starting" - task["taskStatusLabel"].classList.add("waitingTaskLabel") - - let newTaskReqBody = task.reqBody - if (task.batchCount > 1) { - // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. - newTaskReqBody = Object.assign({}, task.reqBody) - if (task.batchesDone == task.batchCount - 1) { - // Last batch of the task - // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images - // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. - newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) - } - } - - const startSeed = task.seed || newTaskReqBody.seed - const genSeeds = Boolean( - typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) - ) - if (genSeeds) { - newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs - } - - // Update the seed *before* starting the processing so it's retained if user stops the task - if (randomSeedField.checked) { - seedField.value = task.seed - } - - const outputContainer = document.createElement("div") - outputContainer.className = "img-batch" - task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) - - const eventInfo = { reqBody: newTaskReqBody } - const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => { - if (typeof hook !== "function") { - console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook) - return Promise.reject(new Error("hook is not a function.")) - } - try { - return Promise.resolve(hook.call(task, eventInfo)) - } catch (err) { - console.error(err) - return Promise.reject(err) - } - }) - await Promise.allSettled(callbacksPromises) - let instance = eventInfo.instance - if (!instance) { - const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format) - if (factory) { - instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) - } - if (!instance) { - console.error( - `${factory ? "Factory " + String(factory) : "No factory defined"} for output format ${eventInfo.reqBody - ?.output_format || newTaskReqBody.output_format}. Instance is ${instance || - "undefined"}. Using default renderer.` - ) - instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) - } - } - - task["instances"].push(instance) - task.batchesDone++ - - instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( - (renderResult) => { - onTaskCompleted(task, newTaskReqBody, instance, outputContainer, renderResult) - }, - (reason) => { - onTaskErrorHandler(task, newTaskReqBody, instance, reason) - } - ) - - setStatus("request", "fetching..") - renderButtons.style.display = "flex" - renameMakeImageButton() - updateInitialText() -} - /* Hover effect for the init image in the task list */ function createInitImageHover(taskEntry, task) { taskEntry.querySelectorAll(".task-initimg").forEach((thumb) => { @@ -1924,20 +1517,6 @@ function createFileName(prompt, seed, steps, guidance, outputFormat) { return fileName } -async function stopAllTasks() { - getUncompletedTaskEntries().forEach((taskEntry) => { - const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") - if (taskStatusLabel) { - taskStatusLabel.style.display = "none" - } - const task = htmlTaskMap.get(taskEntry) - if (!task) { - return - } - abortTask(task) - }) -} - function updateInitialText() { if (document.querySelector(".imageTaskContainer") === null) { if (undoBuffer.length > 0) { @@ -2579,22 +2158,6 @@ function isTabActive(tab) { return tab.classList.contains("active") } -let pauseClient = false - -function resumeClient() { - if (pauseClient) { - document.body.classList.remove("wait-pause") - document.body.classList.add("pause") - } - return new Promise((resolve) => { - let playbuttonclick = function() { - resumeBtn.removeEventListener("click", playbuttonclick) - resolve("resolved") - } - resumeBtn.addEventListener("click", playbuttonclick) - }) -} - function splashScreen(force = false) { const splashVersion = splashScreenPopup.dataset["version"] const lastSplash = localStorage.getItem("lastSplashScreenVersion") || 0 @@ -2612,21 +2175,6 @@ document.getElementById("logo_img").addEventListener("click", (e) => { promptField.addEventListener("input", debounce(renameMakeImageButton, 1000)) -pauseBtn.addEventListener("click", function() { - pauseClient = true - pauseBtn.style.display = "none" - resumeBtn.style.display = "inline" - document.body.classList.add("wait-pause") -}) - -resumeBtn.addEventListener("click", function() { - pauseClient = false - resumeBtn.style.display = "none" - pauseBtn.style.display = "inline" - document.body.classList.remove("pause") - document.body.classList.remove("wait-pause") -}) - function onPing(event) { tunnelUpdate(event) packagesUpdate(event) @@ -3128,3 +2676,83 @@ let recentResolutionsValues = [] heightField.value = temp }) })() + +TASK_CALLBACKS["before_task_start"].push(function(task) { + // Update the seed *before* starting the processing so it's retained if user stops the task + if (randomSeedField.checked) { + seedField.value = task.seed + } +}) + +TASK_CALLBACKS["after_task_start"].push(function(task) { + // setStatus("request", "fetching..") // no-op implementation + renderButtons.style.display = "flex" + renameMakeImageButton() + updateInitialText() +}) + +TASK_CALLBACKS["on_task_step"].push(function(task, reqBody, stepUpdate, outputContainer) { + showImages(reqBody, stepUpdate, outputContainer, true) +}) + +TASK_CALLBACKS["on_render_task_success"].push(function(task, reqBody, stepUpdate, outputContainer) { + showImages(reqBody, stepUpdate, outputContainer, false) +}) + +TASK_CALLBACKS["on_render_task_fail"].push(function(task, reqBody, stepUpdate, outputContainer) { + const outputMsg = task["outputMsg"] + let msg = "" + if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { + msg = stepUpdate.detail + if (msg.toLowerCase().includes("out of memory")) { + msg += `
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}` + } + logError(msg, stepUpdate, outputMsg) +}) + +TASK_CALLBACKS["on_all_tasks_complete"].push(function() { + renderButtons.style.display = "none" + renameMakeImageButton() + + if (isSoundEnabled()) { + playSound() + } +}) diff --git a/ui/media/js/task-manager.js b/ui/media/js/task-manager.js new file mode 100644 index 00000000..1f100cb2 --- /dev/null +++ b/ui/media/js/task-manager.js @@ -0,0 +1,376 @@ +const htmlTaskMap = new WeakMap() + +const pauseBtn = document.querySelector("#pause") +const resumeBtn = document.querySelector("#resume") +const processOrder = document.querySelector("#process_order_toggle") + +let TASK_CALLBACKS = { + before_task_start: [], + after_task_start: [], + on_task_step: [], + on_render_task_success: [], + on_render_task_fail: [], + on_all_tasks_complete: [], +} + +let pauseClient = false + +async function onIdle() { + const serverCapacity = SD.serverCapacity + if (pauseClient === true) { + await resumeClient() + } + + for (const taskEntry of getUncompletedTaskEntries()) { + if (SD.activeTasks.size >= serverCapacity) { + break + } + const task = htmlTaskMap.get(taskEntry) + if (!task) { + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") + taskStatusLabel.style.display = "none" + continue + } + await onTaskStart(task) + } +} + +function getUncompletedTaskEntries() { + const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel")) + .filter((taskLabel) => taskLabel.style.display !== "none") + .map(function(taskLabel) { + let imageTaskContainer = taskLabel.parentNode + while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) { + imageTaskContainer = imageTaskContainer.parentNode + } + return imageTaskContainer + }) + if (!processOrder.checked) { + taskEntries.reverse() + } + return taskEntries +} + +async function onTaskStart(task) { + if (!task.isProcessing || task.batchesDone >= task.batchCount) { + return + } + + if (typeof task.startTime !== "number") { + task.startTime = Date.now() + } + if (!("instances" in task)) { + task["instances"] = [] + } + + task["stopTask"].innerHTML = ' Stop' + task["taskStatusLabel"].innerText = "Starting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + + let newTaskReqBody = task.reqBody + if (task.batchCount > 1) { + // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. + newTaskReqBody = Object.assign({}, task.reqBody) + if (task.batchesDone == task.batchCount - 1) { + // Last batch of the task + // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images + // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. + newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) + } + } + + const startSeed = task.seed || newTaskReqBody.seed + const genSeeds = Boolean( + typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) + ) + if (genSeeds) { + newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs + } + + const outputContainer = document.createElement("div") + outputContainer.className = "img-batch" + task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) + + const eventInfo = { reqBody: newTaskReqBody } + const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => { + if (typeof hook !== "function") { + console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook) + return Promise.reject(new Error("hook is not a function.")) + } + try { + return Promise.resolve(hook.call(task, eventInfo)) + } catch (err) { + console.error(err) + return Promise.reject(err) + } + }) + await Promise.allSettled(callbacksPromises) + let instance = eventInfo.instance + if (!instance) { + const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format) + if (factory) { + instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) + } + if (!instance) { + console.error( + `${factory ? "Factory " + String(factory) : "No factory defined"} for output format ${eventInfo.reqBody + ?.output_format || newTaskReqBody.output_format}. Instance is ${instance || + "undefined"}. Using default renderer.` + ) + instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) + } + } + + task["instances"].push(instance) + task.batchesDone++ + + TASK_CALLBACKS["before_task_start"].forEach((callback) => callback(task)) + + instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( + (renderResult) => { + onRenderTaskCompleted(task, newTaskReqBody, instance, outputContainer, renderResult) + }, + (reason) => { + onTaskErrorHandler(task, newTaskReqBody, instance, reason) + } + ) + + TASK_CALLBACKS["after_task_start"].forEach((callback) => callback(task)) +} + +function getTaskUpdater(task, reqBody, outputContainer) { + const outputMsg = task["outputMsg"] + const progressBar = task["progressBar"] + const progressBarInner = progressBar.querySelector("div") + + const batchCount = task.batchCount + let lastStatus = undefined + return async function(event) { + if (this.status !== lastStatus) { + lastStatus = this.status + switch (this.status) { + case SD.TaskStatus.pending: + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + break + case SD.TaskStatus.waiting: + task["taskStatusLabel"].innerText = "Waiting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") + break + case SD.TaskStatus.processing: + case SD.TaskStatus.completed: + task["taskStatusLabel"].innerText = "Processing" + task["taskStatusLabel"].classList.add("activeTaskLabel") + task["taskStatusLabel"].classList.remove("waitingTaskLabel") + break + case SD.TaskStatus.stopped: + break + case SD.TaskStatus.failed: + if (!SD.isServerAvailable()) { + logError( + "Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", + event, + outputMsg + ) + } else if (typeof event?.response === "object") { + let msg = "Stable Diffusion had an error reading the response:
" + if (this.exception) { + msg += `Error: ${this.exception.message}" + logError(msg, event, outputMsg) + } + break + } + } + if ("update" in event) { + const stepUpdate = event.update + if (!("step" in stepUpdate)) { + return + } + // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) + const instancesWithProgressUpdates = task.instances.filter((instance) => instance.step !== undefined) + const overallStepCount = + instancesWithProgressUpdates.reduce( + (sum, instance) => + sum + + (instance.isPending + ? Math.max(0, instance.step || stepUpdate.step) / + (instance.total_steps || stepUpdate.total_steps) + : 1), + 0 // Initial value + ) * stepUpdate.total_steps // Scale to current number of steps. + const totalSteps = instancesWithProgressUpdates.reduce( + (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), + stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) + ) + const percent = Math.min(100, 100 * (overallStepCount / totalSteps)).toFixed(0) + + const timeTaken = stepUpdate.step_time // sec + const stepsRemaining = Math.max(0, totalSteps - overallStepCount) + const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) + outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` + outputMsg.style.display = "block" + progressBarInner.style.width = `${percent}%` + + if (stepUpdate.output) { + TASK_CALLBACKS["on_task_step"].forEach((callback) => + callback(task, reqBody, stepUpdate, outputContainer) + ) + } + } + } +} + +function onRenderTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { + if (typeof stepUpdate === "object") { + if (stepUpdate.status === "succeeded") { + TASK_CALLBACKS["on_render_task_success"].forEach((callback) => + callback(task, reqBody, stepUpdate, outputContainer) + ) + } else { + task.isProcessing = false + TASK_CALLBACKS["on_render_task_fail"].forEach((callback) => + callback(task, reqBody, stepUpdate, outputContainer) + ) + } + } + if (task.isProcessing && task.batchesDone < task.batchCount) { + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") + return + } + if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { + return + } + + task.isProcessing = false + task["stopTask"].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" + + let time = millisecondsToStr(Date.now() - task.startTime) + + if (task.batchesDone == task.batchCount) { + if (!task.outputMsg.innerText.toLowerCase().includes("error")) { + task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` + } + task.progressBar.style.height = "0px" + task.progressBar.style.border = "0px solid var(--background-color3)" + task.progressBar.classList.remove("active") + // setStatus("request", "done", "success") + } else { + task.outputMsg.innerText += `. Task ended after ${time}` + } + + // if (randomSeedField.checked) { // we already update this before the task starts + // seedField.value = task.seed + // } + + if (SD.activeTasks.size > 0) { + return + } + const uncompletedTasks = getUncompletedTaskEntries() + if (uncompletedTasks && uncompletedTasks.length > 0) { + return + } + + if (pauseClient) { + resumeBtn.click() + } + + TASK_CALLBACKS["on_all_tasks_complete"].forEach((callback) => callback()) +} + +function resumeClient() { + if (pauseClient) { + document.body.classList.remove("wait-pause") + document.body.classList.add("pause") + } + return new Promise((resolve) => { + let playbuttonclick = function() { + resumeBtn.removeEventListener("click", playbuttonclick) + resolve("resolved") + } + resumeBtn.addEventListener("click", playbuttonclick) + }) +} + +function abortTask(task) { + if (!task.isProcessing) { + return false + } + task.isProcessing = false + task.progressBar.classList.remove("active") + task["taskStatusLabel"].style.display = "none" + task["stopTask"].innerHTML = ' Remove' + if (!task.instances?.some((r) => r.isPending)) { + return + } + task.instances.forEach((instance) => { + try { + instance.abort() + } catch (e) { + console.error(e) + } + }) +} + +async function stopAllTasks() { + getUncompletedTaskEntries().forEach((taskEntry) => { + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") + if (taskStatusLabel) { + taskStatusLabel.style.display = "none" + } + const task = htmlTaskMap.get(taskEntry) + if (!task) { + return + } + abortTask(task) + }) +} + +function onTaskErrorHandler(task, reqBody, instance, reason) { + if (!task.isProcessing) { + return + } + console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) + abortTask(task) + const outputMsg = task["outputMsg"] + logError( + "Stable Diffusion had an error. Please check the logs in the command-line window.
` + } + try { + // 'Response': body stream already read + msg += "Read: " + (await event.response.text()) + } catch (e) { + msg += "Unexpected end of stream. " + } + const bufferString = event.reader.bufferedString + if (bufferString) { + msg += "Buffered data: " + bufferString + } + msg += "
" + + reason.stack + + "", + task, + outputMsg + ) + // setStatus("request", "error", "error") +} + +pauseBtn.addEventListener("click", function() { + pauseClient = true + pauseBtn.style.display = "none" + resumeBtn.style.display = "inline" + document.body.classList.add("wait-pause") +}) + +resumeBtn.addEventListener("click", function() { + pauseClient = false + resumeBtn.style.display = "none" + pauseBtn.style.display = "inline" + document.body.classList.remove("pause") + document.body.classList.remove("wait-pause") +}) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 8fef9450..e3014b91 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -1198,4 +1198,37 @@ function makeDialogDraggable(element) { })() ) } +function logMsg(msg, level, outputMsg) { + if (outputMsg.hasChildNodes()) { + outputMsg.appendChild(document.createElement("br")) + } + if (level === "error") { + outputMsg.innerHTML += 'Error: ' + msg + "" + } else if (level === "warn") { + outputMsg.innerHTML += 'Warning: ' + msg + "" + } else { + outputMsg.innerText += msg + } + console.log(level, msg) +} +function logError(msg, res, outputMsg) { + logMsg(msg, "error", outputMsg) + + console.log("request error", res) + console.trace() + // setStatus("request", "error", "error") +} + +function playSound() { + const audio = new Audio("/media/ding.mp3") + audio.volume = 0.2 + var promise = audio.play() + if (promise !== undefined) { + promise + .then((_) => {}) + .catch((error) => { + console.warn("browser blocked autoplay") + }) + } +}