From e970c6122e8bd13f958d6140c4dd1a535ed8f9e8 Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 29 Oct 2022 22:45:56 -0500 Subject: [PATCH 1/4] Better deletion confirmation dialog --- static/koboldai.css | 40 ++++++++++++++ static/koboldai.js | 123 ++++++++++++++++++++++++++++++++++-------- templates/popups.html | 16 ++++++ 3 files changed, 158 insertions(+), 21 deletions(-) diff --git a/static/koboldai.css b/static/koboldai.css index 2ba3f262..260196ea 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -2562,6 +2562,46 @@ body { to { width: 0%; } } + +/* WI Delete Confirm */ +#confirm-delete-dialog { + background-color: var(--popup_background_color); + padding: 10px; + min-width: 40vw; +} + +#confirm-buttons { + display: flex; + width: 100%; + justify-content: center; + gap: 10px; + margin-top: 10px; +} + +#confirm-deny-button { background-color: var(--button_background); } +#confirm-confirm-button { background-color: #974040; } + +.confirm-button { + display: inline-flex; + height: 32px; + + flex-basis: 50%; + + align-items: center; + justify-content: center; + + cursor: pointer; +} + +.confirm-button > .material-icons-outlined { + opacity: 0.7; +} + +.confirm-button > .text { + position: relative; + top: 2px; +} + /*---------------------------------- Global ------------------------------------------------*/ .hidden { display: none; diff --git a/static/koboldai.js b/static/koboldai.js index ff7341c5..95455d4f 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -953,15 +953,17 @@ function redrawPopup() { delete_icon.id = row.path; delete_icon.setAttribute("folder", row.isFolder); delete_icon.onclick = function () { - if (this.getAttribute("folder") == "true") { - if (window.confirm("Do you really want to delete this folder and ALL files under it?")) { - socket.emit("popup_delete", this.id); + const message = this.getAttribute("folder") == "true" ? "Do you really want to delete this folder and ALL files under it?" : "Do you really want to delete this file?"; + const delId = this.id; + + deleteConfirmation( + [{text: message}], + confirmText="Go for it.", + denyText="I've changed my mind!", + confirmCallback=function() { + socket.emit("popup_delete", delId); } - } else { - if (window.confirm("Do you really want to delete this file?")) { - socket.emit("popup_delete", this.id); - } - } + ); }; } icon_area.append(delete_icon); @@ -1746,13 +1748,24 @@ function world_info_entry(data) { delete_icon.setAttribute("uid", data.uid); delete_icon.setAttribute("wi-title", data.title); delete_icon.onclick = function () { - if (confirm("This will delete world info "+this.getAttribute("wi-title"))) { - if (parseInt(this.getAttribute("uid")) < 0) { - this.parentElement.parentElement.remove(); - } else { - socket.emit("delete_world_info", this.getAttribute("uid")); + const wiTitle = this.getAttribute("wi-title"); + const wiUid = parseInt(this.getAttribute("uid")); + const wiElement = this.parentElement.parentElement; + deleteConfirmation([ + {text: "You're about to delete World Info entry "}, + {text: wiTitle, format: "bold"}, + {text: ". Are you alright with this?"}, + ], + confirmText="Go for it.", + denyText="I've changed my mind!", + confirmCallback=function() { + if (wiUid < 0) { + wiElement.remove(); + } else { + socket.emit("delete_world_info", wiUid); + } } - } + ); } tags = world_info_card.querySelector('.world_info_tag_primary_area'); tags.id = "world_info_tags_"+data.uid; @@ -2119,10 +2132,19 @@ function world_info_folder(data) { delete_button.setAttribute("folder", folder_name); delete_button.textContent = "delete"; delete_button.onclick = function () { - if (window.confirm("Do you really want to delete this World Info folder and ALL entries under it?")) { - socket.emit("delete_wi_folder", this.getAttribute("folder")); - } - }; + const folderName = this.getAttribute("folder"); + deleteConfirmation([ + {text: "You're about to delete World Info folder "}, + {text: folderName, format: "bold"}, + {text: " and the "}, + {text: countWIFolderChildren(folderName), format: "bold"}, + {text: " entries inside it. Are you sure?"}, + ], + confirmText="Go for it.", + denyText="I've changed my mind!", + confirmCallback=function() { socket.emit("delete_wi_folder", folderName); } + ); + }; delete_button.classList.add("delete"); title.append(delete_button); @@ -2405,9 +2427,15 @@ function new_story() { //check if the story is saved if (document.getElementById('save_story').getAttribute('story_gamesaved') == "false") { //ask the user if they want to continue - if (window.confirm("You asked for a new story but your current story has not been saved. If you continue you will loose your changes.")) { - socket.emit('new_story', ''); - } + deleteConfirmation([ + {text: "You asked for a new story but your current story has not been saved. If you continue you will loose your changes."}, + ], + confirmText="Go for it.", + denyText="I've changed my mind!", + confirmCallback=function() { + socket.emit('new_story', ''); + } + ); } else { socket.emit('new_story', ''); } @@ -5552,4 +5580,57 @@ function run_infinite_scroll_update(action_type, actions, first_action) { } } +} + +function countWIFolderChildren(folder) { + let count = 0; + for (const wi of Object.values(world_info_data)) { + if (wi.folder === folder) count += 1; + } + return count; +} + +function sFormatted2HTML(sFormatted) { + // "sFormatted" is a rudimentary solution to safe formatting + let outHTML = ""; + + for (const chunk of sFormatted) { + // Expand as needed + let format = { + bold: "%s", + italic: "%s" + }[chunk.format] || "%s"; + + // This actually sucks but apparently the best recognized way to escape + // HTML in JavaScript is just "make an element real quick and slap some + // text in it." + let escaped = new Option(chunk.text).innerHTML; + + outHTML += format.replace("%s", escaped); + } + return outHTML; +} + +function deleteConfirmation(sFormatted, confirmText, denyText, confirmCallback, denyCallback) { + $el("#confirm-text").innerHTML = sFormatted2HTML(sFormatted); + + $el("#confirm-confirm-button > .text").innerText = confirmText; + $el("#confirm-deny-button > .text").innerText = denyText; + + const confirmButton = $el("#confirm-confirm-button") + confirmButton.onclick = function() { + confirmCallback(); + closePopups(); + confirmButton.onclick = undefined; + } + + const denyButton = $el("#confirm-deny-button") + denyButton.onclick = function() { + // No-op if no deny callback + (denyCallback || function(){})(); + closePopups(); + confirmButton.onclick = undefined; + } + + openPopup("confirm-delete-dialog"); } \ No newline at end of file diff --git a/templates/popups.html b/templates/popups.html index 85cafdd7..0598c665 100644 --- a/templates/popups.html +++ b/templates/popups.html @@ -267,6 +267,22 @@ + + +
+ You're about to delete World Info folder evil and malice and the infinite entries inside it. Are you sure? + +
+
+ close + I've changed my mind! +
+
+ delete + Go for it. +
+
+
\ No newline at end of file From 9f0b81742b457dfe072ec0675789c0793894d2fc Mon Sep 17 00:00:00 2001 From: Llama <34464159+pi6am@users.noreply.github.com> Date: Sat, 29 Oct 2022 21:15:47 -0700 Subject: [PATCH 2/4] Fix space tokens being dropped from stream preview. When streaming preview tokens arrive, they are accumulated into a stream_preview span. These tokens were inserted one-by-one into the `innerText` property of this span. However, the behavior of innerText is to discard trailing whitespace, which meant that a token that was entirely composed of spaces would be discarded. See this link for more information on this behavior: https://stackoverflow.com/questions/47768523/empty-spaces-are-ignored-by-the-innertext-property I tried fixing this by switching to use `textContent`, however this caused newlines to be discarded instead. This change fixes the issue by accumulating incoming tokens into a string and then assigning the string to innerText. --- static/application.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/static/application.js b/static/application.js index 81940fd3..863a0c8a 100644 --- a/static/application.js +++ b/static/application.js @@ -79,6 +79,7 @@ var rs_close; var seqselmenu; var seqselcontents; var stream_preview; +var stream_preview_text; var token_prob_container; var storyname = null; @@ -2151,6 +2152,7 @@ function endStream() { if (stream_preview) { stream_preview.remove(); stream_preview = null; + stream_preview_text = null; } } @@ -2390,10 +2392,14 @@ $(document).ready(function(){ if (!stream_preview && streamingEnabled) { stream_preview = document.createElement("span"); game_text.append(stream_preview); + stream_preview_text = ""; } for (const token of msg.data) { - if (streamingEnabled) stream_preview.innerText += token.decoded; + if (streamingEnabled) { + stream_preview_text += token.decoded; + stream_preview.innerText = stream_preview_text; + } if (probabilitiesEnabled) { // Probability display @@ -3800,4 +3806,4 @@ function getSelectedOptions(element) { output.push(item.value); } return output; -} \ No newline at end of file +} From b0bad4999bd05eddc689eebd6cf8778f2be7841a Mon Sep 17 00:00:00 2001 From: somebody Date: Sun, 30 Oct 2022 11:16:16 -0500 Subject: [PATCH 3/4] Remove some debug code for the notifications Whoops --- static/koboldai.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/koboldai.js b/static/koboldai.js index ff7341c5..ea544516 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -5499,8 +5499,6 @@ function reportError(title, text) { showNotification(title, text, "error"); } -showNotification("Be aware!", "Things are happening at an alarming pace!"); - //function to load more actions if nessisary function infinite_scroll() { if (scroll_trigger_element != undefined) { From 58aacf2cd87a940b0122634b8d5a2e2f831d3e5c Mon Sep 17 00:00:00 2001 From: somebody Date: Sun, 30 Oct 2022 11:23:59 -0500 Subject: [PATCH 4/4] Shortcut criteria for story history navigation --- static/koboldai.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/static/koboldai.js b/static/koboldai.js index ea544516..9527349b 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -109,9 +109,9 @@ const context_menu_actions = [ const shortcuts = [ {key: "k", desc: "Finder", func: open_finder}, {key: "/", desc: "Help screen", func: () => openPopup("shortcuts-popup")}, - {key: "z", desc: "Undoes last story action", func: () => socket.emit("back", {})}, - {key: "y", desc: "Redoes last story action", func: () => socket.emit("redo", {})}, - {key: "e", desc: "Retries last story action", func: () => socket.emit("retry", {})}, + {key: "z", desc: "Undoes last story action", func: () => socket.emit("back", {}), criteria: canNavigateStoryHistory}, + {key: "y", desc: "Redoes last story action", func: () => socket.emit("redo", {}), criteria: canNavigateStoryHistory}, + {key: "e", desc: "Retries last story action", func: () => socket.emit("retry", {}), criteria: canNavigateStoryHistory}, {key: "m", desc: "Focuses Memory", func: () => focusEl("#memory")}, {key: "l", desc: "Focuses Author's Note", func: () => focusEl("#authors_notes")}, // CTRL-N is reserved :^( {key: "g", desc: "Focuses game text", func: () => focusEl("#input_text")}, @@ -5461,6 +5461,7 @@ function initalizeTooltips() { for (const shortcut of shortcuts) { if (shortcut.key !== event.key) continue; + if (shortcut.criteria && !shortcut.criteria()) continue; event.preventDefault(); shortcut.func(); } @@ -5499,6 +5500,10 @@ function reportError(title, text) { showNotification(title, text, "error"); } +function canNavigateStoryHistory() { + return !["TEXTAREA", "INPUT"].includes(document.activeElement.tagName); +} + //function to load more actions if nessisary function infinite_scroll() { if (scroll_trigger_element != undefined) {