diff --git a/evennia/web/webclient/static/webclient/css/goldenlayout.css b/evennia/web/webclient/static/webclient/css/goldenlayout.css new file mode 100644 index 0000000000..bbb099e712 --- /dev/null +++ b/evennia/web/webclient/static/webclient/css/goldenlayout.css @@ -0,0 +1,117 @@ +/* + * + */ + +#clientwrapper #main { + height: 100%; +} + +#clientwrapper #main #main-sub { + height: 100%; +} + +#toolbar { + display: none; +} + +#optionsbutton { + font-size: .9rem; + width: .9rem; + vertical-align: top; +} + +.content { + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} + +body .lm_popout { + display: none; +} + +label { + display: block; +} + +.lm_title { + text-align: center; +} + +#typelist { + position: relative; +} + +.typelistsub { + position: absolute; + top: 5px; + left: 5px; + width: max-content; + background-color: #333333; + line-height: .5em; + padding: .3em; + font-size: .9em; + z-index: 1; +} + +#renamebox { + position: relative; +} + +#renameboxin { + position: absolute; + z-index: 1; +} + +#updatelist { + position: relative; +} + +.updatelistsub { + position: absolute; + top: 5px; + left: 5px; + width: max-content; + background-color: #333333; + line-height: .5em; + padding: .3em; + font-size: .9em; + z-index: 1; +} + +#inputcontrol { + height: 100%; +} + +.inputwrap { + position: relative; + height: 100%; +} + +.inputsend { + position: absolute; + right: 0; + z-index: 1; + height: inherit; + width: 30px; +} + +.inputfield { + position: absolute; + top: 0; + height: inherit; + width: calc(100% - 30px); + background-color: black; + color: white; +} + +.inputfield:focus { + background-color: black; + color: white; +} + +.glbutton { + font-size: 0.6em; + line-height: 1.3em; + padding: .5em; +} diff --git a/evennia/web/webclient/static/webclient/js/plugins/default_in.js b/evennia/web/webclient/static/webclient/js/plugins/default_in.js index 02fd401706..bdd2fc0c69 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -1,6 +1,6 @@ /* * - * Evennia Webclient default 'send-text-on-enter-key' IO plugin + * Evennia Webclient default "send-text-on-enter-key" IO plugin * */ let defaultin_plugin = (function () { @@ -8,16 +8,48 @@ let defaultin_plugin = (function () { // // handle the default key triggering onSend() var onKeydown = function (event) { - $("#inputfield").focus(); - if ( (event.which === 13) && (!event.shiftKey) ) { // Enter Key without shift - var inputfield = $("#inputfield"); - var outtext = inputfield.val(); - var lines = outtext.trim().replace(/[\r]+/,"\n").replace(/[\n]+/, "\n").split("\n"); - for (var i = 0; i < lines.length; i++) { - plugin_handler.onSend( lines[i].trim() ); - } - inputfield.val(''); - event.preventDefault(); + // find where the key comes from + var inputfield = $(".inputfield:focus"); + + if( inputfield.length < 1 ) { // non-goldenlayout backwards compatibility + inputfield = $("#inputfield:focus"); + } + + // check for important keys + switch (event.which) { + case 9: // ignore tab key -- allows normal focus control + case 16: // ignore shift + case 17: // ignore alt + case 18: // ignore control + case 20: // ignore caps lock + case 144: // ignore num lock + break; + + case 13: // Enter key + var outtext = inputfield.val(); // Grab the text from which-ever inputfield is focused + if ( outtext && !event.shiftKey ) { // Enter Key without shift --> send Mesg + var lines = outtext.trim().replace(/[\r]+/,"\n").replace(/[\n]+/, "\n").split("\n"); + for (var i = 0; i < lines.length; i++) { + plugin_handler.onSend( lines[i].trim() ); + } + inputfield.val(""); // Clear this inputfield + event.preventDefault(); + } + inputfield.blur(); + break; + + // Anything else, focus() a textarea if needed, and allow the default event + default: + // is an inputfield actually focused? + if( inputfield.length < 1 ) { + // Nope, focus the last .inputfield found in the DOM (or #inputfield) + // :last only matters if multi-input plugins are in use + inputfield = $(".inputfield:last"); + inputfield.focus(); + if( inputfield.length < 1 ) { // non-goldenlayout backwards compatibility + $("#inputfield").focus(); + } + } } return true; @@ -26,15 +58,14 @@ let defaultin_plugin = (function () { // // Mandatory plugin init function var init = function () { - // Handle pressing the send button + // Handle pressing the send button, this only applies to non-goldenlayout setups $("#inputsend") - .bind("click", function (event) { + .bind("click", function (evnt) { + // simulate a carriage return var e = $.Event( "keydown" ); e.which = 13; - $('#inputfield').trigger(e); + $("#inputfield").focus().trigger(e); }); - - console.log('DefaultIn initialized'); } return { @@ -42,4 +73,4 @@ let defaultin_plugin = (function () { onKeydown: onKeydown, } })(); -plugin_handler.add('defaultin', defaultin_plugin); +window.plugin_handler.add("defaultin", defaultin_plugin); diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js new file mode 100644 index 0000000000..caa18289d9 --- /dev/null +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -0,0 +1,519 @@ +/* + * + * Golden Layout plugin + * + */ +let goldenlayout = (function () { + + var myLayout; + var knownTypes = ["all", "untagged"]; + var untagged = []; + + var newTabConfig = { + title: "Untitled", + type: "component", + componentName: "evennia", + componentState: { + types: "all", + updateMethod: "newlines", + }, + }; + + var newInputConfig = { + title: "input", + type: "component", + componentName: "input", + id: "inputComponent", + }; + + // helper function: filter vals out of array + function filter (vals, array) { + if( Array.isArray( vals ) && Array.isArray( array ) ) { + let tmp = array.slice(); + vals.forEach( function (val) { + while( tmp.indexOf(val) > -1 ) { + tmp.splice( tmp.indexOf(val), 1 ); + } + }); + return tmp; + } + // pass along whatever we got, since our arguments aren't right. + return array; + } + + + // + // Calculate all knownTypes minus the "all" type, + // then filter out all types that have been mapped to a pane. + var calculateUntaggedTypes = function () { + // set initial untagged list + untagged = filter( ["all", "untagged"], knownTypes); + // for each .content pane + $(".content").each( function () { + let types = $(this).attr("types"); + if ( typeof types !== "undefined" ) { + untagged = filter( types.split(" "), untagged ); + } + }); + } + + + // + // + var closeRenameDropdown = function () { + let content = $("#renamebox").parent().parent().parent().parent()[0]; + let title = $("#renameboxin").val(); + + let components = myLayout.root.getItemsByType("component"); + + components.forEach( function (component) { + let element = component.tab.header.parent.element[0]; + if( (element === content) && (component.tab.isActive) ) { + component.setTitle( title ); + } + }); + + myLayout.emit("stateChanged"); + $("#renamebox").remove(); + } + + + // + // + var closeTypelistDropdown = function () { + let content = $("#typelist").parent().find(".content"); + let checkboxes = $("#typelist :input"); + + let types = []; + checkboxes.each( function (idx) { + if( $(checkboxes[idx]).prop("checked") ) { + types.push( $(checkboxes[idx]).val() ); + } + }); + + content.attr("types", types.join(" ")); + myLayout.emit("stateChanged"); + + calculateUntaggedTypes(); + $("#typelist").remove(); + } + + + // + // + var closeUpdatelistDropdown = function () { + let content = $("#updatelist").parent().find(".content"); + let value = $("input[name=upmethod]:checked").val(); + + content.attr("updateMethod", value ); + myLayout.emit("stateChanged"); + $("#updatelist").remove(); + } + + + // + // Handle the renameDropdown + var renameDropdown = function (evnt) { + let element = $(evnt.data.contentItem.element); + let content = element.find(".content"); + let title = evnt.data.contentItem.config.title; + let renamebox = document.getElementById("renamebox"); + + // check that no other dropdown is open + if( document.getElementById("typelist") ) { + closeTypelistDropdown(); + } + + if( document.getElementById("updatelist") ) { + closeUpdatelistDropdown(); + } + + if( !renamebox ) { + renamebox = $("
"); + renamebox.append(""); + renamebox.insertBefore( content ); + } else { + closeRenameDropdown(); + } + } + + + // + // + var onSelectTypesClicked = function (evnt) { + let element = $(evnt.data.contentItem.element); + let content = element.find(".content"); + let selectedTypes = content.attr("types"); + let menu = $("
"); + let div = $("
"); + + if( selectedTypes ) { + selectedTypes = selectedTypes.split(" "); + } + knownTypes.forEach( function (itype) { + let choice; + if( selectedTypes && selectedTypes.includes(itype) ) { + choice = $(""); + } else { + choice = $(""); + } + choice.appendTo(div); + }); + div.appendTo(menu); + + element.prepend(menu); + } + + + // + // Handle the typeDropdown + var typeDropdown = function (evnt) { + let typelist = document.getElementById("typelist"); + + // check that no other dropdown is open + if( document.getElementById("renamebox") ) { + closeRenameDropdown(); + } + + if( document.getElementById("updatelist") ) { + closeUpdatelistDropdown(); + } + + if( !typelist ) { + onSelectTypesClicked(evnt); + } else { + closeTypelistDropdown(); + } + } + + + // + // + var onUpdateMethodClicked = function (evnt) { + let element = $(evnt.data.contentItem.element); + let content = element.find(".content"); + let updateMethod = content.attr("updateMethod"); + let nlchecked = (updateMethod === "newlines") ? "checked='checked'" : ""; + let apchecked = (updateMethod === "append") ? "checked='checked'" : ""; + let rpchecked = (updateMethod === "replace") ? "checked='checked'" : ""; + + let menu = $("
"); + let div = $("
"); + + let newlines = $(""); + let append = $(""); + let replace = $(""); + + newlines.appendTo(div); + append.appendTo(div); + replace.appendTo(div); + + div.appendTo(menu); + + element.prepend(menu); + } + + + // + // Handle the updateDropdown + var updateDropdown = function (evnt) { + let updatelist = document.getElementById("updatelist"); + + // check that no other dropdown is open + if( document.getElementById("renamebox") ) { + closeRenameDropdown(); + } + + if( document.getElementById("typelist") ) { + closeTypelistDropdown(); + } + + if( !updatelist ) { + onUpdateMethodClicked(evnt); + } else { + closeUpdatelistDropdown(); + } + } + + + // + // + var onActiveTabChange = function (tab) { + let renamebox = document.getElementById("renamebox"); + let typelist = document.getElementById("typelist"); + let updatelist = document.getElementById("updatelist"); + + if( renamebox ) { + closeRenameDropdown(); + } + + if( typelist ) { + closeTypelistDropdown(); + } + + if( updatelist ) { + closeUpdatelistDropdown(); + } + } + + + // + // Save the GoldenLayout state to localstorage whenever it changes. + var onStateChanged = function () { + let components = myLayout.root.getItemsByType("component"); + components.forEach( function (component) { + if( component.hasId("inputComponent") ) { return; } // ignore input components + + let textDiv = component.container.getElement().children(".content"); + let types = textDiv.attr("types"); + let updateMethod = textDiv.attr("updateMethod"); + component.container.extendState({ "types": types, "updateMethod": updateMethod }); + }); + + var state = JSON.stringify( myLayout.toConfig() ); + localStorage.setItem( "evenniaGoldenLayoutSavedState", state ); + } + + + // + // + var onTabCreate = function (tab) { + //HTML for the typeDropdown + let renameDropdownControl = $("\u2B57"); + let typeDropdownControl = $(""); + let updateDropdownControl = $(""); + let splitControl = $("+"); + + // track dropdowns when the associated control is clicked + renameDropdownControl.click( tab, renameDropdown ); + + typeDropdownControl.click( tab, typeDropdown ); + + updateDropdownControl.click( tab, updateDropdown ); + + // track adding a new tab + splitControl.click( tab, function (evnt) { + evnt.data.header.parent.addChild( newTabConfig ); + }); + + // Add the typeDropdown to the header + tab.element.prepend( renameDropdownControl ); + tab.element.append( typeDropdownControl ); + tab.element.append( updateDropdownControl ); + tab.element.append( splitControl ); + + if( tab.contentItem.config.componentName === "Main" ) { + tab.element.prepend( $("#optionsbutton").clone(true).addClass("lm_title") ); + } + + tab.header.parent.on( "activeContentItemChanged", onActiveTabChange ); + } + + + // + // + var onInputCreate = function (tab) { + //HTML for the typeDropdown + let splitControl = $("+"); + + // track adding a new tab + splitControl.click( tab, function (evnt) { + evnt.data.header.parent.addChild( newInputConfig ); + }); + + // Add the typeDropdown to the header + tab.element.append( splitControl ); + + tab.header.parent.on( "activeContentItemChanged", onActiveTabChange ); + } + + // + // + var scrollAll = function () { + let components = myLayout.root.getItemsByType("component"); + components.forEach( function (component) { + if( component.hasId("inputComponent") ) { return; } // ignore input components + + let textDiv = component.container.getElement().children(".content"); + let scrollHeight = textDiv.prop("scrollHeight"); + let clientHeight = textDiv.prop("clientHeight"); + textDiv.scrollTop( scrollHeight - clientHeight ); + }); + myLayout.updateSize(); + } + + + // + // + var routeMsg = function (textDiv, txt, updateMethod) { + if ( updateMethod === "replace" ) { + textDiv.html(txt); + } else if ( updateMethod === "append" ) { + textDiv.append(txt); + } else { // line feed + textDiv.append("
" + txt + "
"); + } + let scrollHeight = textDiv.prop("scrollHeight"); + let clientHeight = textDiv.prop("clientHeight"); + textDiv.scrollTop( scrollHeight - clientHeight ); + } + + + // + // + var initComponent = function (div, container, state, defaultTypes, updateMethod) { + // set this container"s content div types attribute + if( state ) { + div.attr("types", state.types); + div.attr("updateMethod", state.updateMethod); + } else { + div.attr("types", defaultTypes); + div.attr("updateMethod", updateMethod); + } + div.appendTo( container.getElement() ); + container.on("tab", onTabCreate); + } + + + // + // Public + // + + + // + // + var onKeydown = function(evnt) { + var renamebox = document.getElementById("renamebox"); + if( renamebox ) { + return true; + } + return false; + } + + + // + // + var onText = function (args, kwargs) { + // If the message is not itself tagged, we"ll assume it + // should go into any panes with "all" and "untagged" set + var msgtype = "untagged"; + + if ( kwargs && "type" in kwargs ) { + msgtype = kwargs["type"]; + if ( ! knownTypes.includes(msgtype) ) { + // this is a new output type that can be mapped to panes + knownTypes.push(msgtype); + untagged.push(msgtype); + } + } + + let messageDelivered = false; + let components = myLayout.root.getItemsByType("component"); + + components.forEach( function (component) { + if( component.hasId("inputComponent") ) { return; } // ignore the input component + + let textDiv = component.container.getElement().children(".content"); + let attrTypes = textDiv.attr("types"); + let paneTypes = attrTypes ? attrTypes.split(" ") : []; + let updateMethod = textDiv.attr("updateMethod"); + let txt = args[0]; + + // is this message type listed in this pane"s types (or is this pane catching "all") + if( paneTypes.includes(msgtype) || paneTypes.includes("all") ) { + routeMsg( textDiv, txt, updateMethod ); + messageDelivered = true; + } + + // is this pane catching "upmapped" messages? + // And is this message type listed in the untagged types array? + if( paneTypes.includes("untagged") && untagged.includes(msgtype) ) { + routeMsg( textDiv, txt, updateMethod ); + messageDelivered = true; + } + }); + + if ( messageDelivered ) { + return true; + } + // unhandled message + return false; + } + + + // + // + var postInit = function () { + // finish the setup and actually start GoldenLayout + myLayout.init(); + + // Set the Event handler for when the client window changes size + $(window).bind("resize", scrollAll); + + // Set Save State callback + myLayout.on( "stateChanged", onStateChanged ); + } + + + // + // required Init me + var init = function (options) { + // Set up our GoldenLayout instance built off of the default main-sub div + var savedState = localStorage.getItem( "evenniaGoldenLayoutSavedState" ); + var mainsub = document.getElementById("main-sub"); + + if( savedState !== null ) { + // Overwrite the global-variable configuration with the version from localstorage + window.goldenlayout_config = JSON.parse( savedState ); + } + + myLayout = new GoldenLayout( window.goldenlayout_config, mainsub ); + + $("#inputcontrol").remove(); // remove the cluttered, HTML-defined input divs + + // register our component and replace the default messagewindow with the Main component + myLayout.registerComponent( "Main", function (container, componentState) { + let main = $("#messagewindow").addClass("content"); + initComponent(main, container, componentState, "untagged", "newlines" ); + }); + + // register our new input component + myLayout.registerComponent( "input", function (container, componentState) { + var inputfield = $(""); + var button = $(""); + + $("
") + .append( button ) + .append( inputfield ) + .appendTo( container.getElement() ); + + button.bind("click", function (evnt) { + // focus our textarea + $( $(evnt.target).siblings(".inputfield")[0] ).focus(); + // fake a carriage return event + var e = $.Event("keydown"); + e.which = 13; + $( $(evnt.target).siblings(".inputfield")[0] ).trigger(e); + }); + + container.on("tab", onInputCreate); + }); + + myLayout.registerComponent( "evennia", function (container, componentState) { + let div = $("
"); + initComponent(div, container, componentState, "all", "newlines"); + container.on("destroy", calculateUntaggedTypes); + }); + } + + + return { + init: init, + postInit: postInit, + onKeydown: onKeydown, + onText: onText, + getGL: function () { return myLayout; }, + addKnownType: function (newtype) { knownTypes.push(newtype); }, + } +}()); +window.plugin_handler.add("goldenlayout", goldenlayout); diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout_default_config.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout_default_config.js new file mode 100644 index 0000000000..cd0b0ca5ec --- /dev/null +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout_default_config.js @@ -0,0 +1,55 @@ +/* + * Define the default GoldenLayout-based config + * + * The layout defined here will need to be customized based on which plugins + * you are using and what layout you want players to see by default. + * + * This needs to be loaded in the HTML before the goldenlayout.js plugin + * + * The contents of the global variable will be overwritten by what is in the + * browser's localstorage after visiting this site. + * + * For full documentation on all of the keywords see: + * http://golden-layout.com/docs/Config.html + * + */ +var goldenlayout_config = { // Global Variable used in goldenlayout.js init() + content: [{ + type: "column", + content: [{ + type: "row", + content: [{ + type: "column", + content: [{ + type: "component", + componentName: "Main", + isClosable: false, // remove the 'x' control to close this + tooltip: "Main - drag to desird position.", + componentState: { + types: "untagged", + updateMethod: "newlines", + }, + }] + }], +// }, { // Uncomment the following to add a default hotbuttons component +// type: "component", +// componentName: "hotbuttons", +// id: "inputComponent", // mark 'ignore this component during output message processing' +// height: 6, +// isClosable: false, + }, { + type: "component", + componentName: "input", + id: "inputComponent", // mark for ignore + height: 12, // percentage + tooltip: "Input - The last input in the layout is always the default.", + }, { + type: "component", + componentName: "input", + id: "inputComponent", // mark for ignore + height: 12, // percentage + isClosable: false, // remove the 'x' control to close this + tooltip: "Input - The last input in the layout is always the default.", + }] + }] +}; diff --git a/evennia/web/webclient/static/webclient/js/plugins/history.js b/evennia/web/webclient/static/webclient/js/plugins/history.js index 60b1a2b163..d7a33c1175 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/history.js +++ b/evennia/web/webclient/static/webclient/js/plugins/history.js @@ -3,29 +3,29 @@ * Evennia Webclient Command History plugin * */ -let history_plugin = (function () { +let history = (function () { // Manage history for input line - var history_max = 21; + var historyMax = 21; var history = new Array(); - var history_pos = 0; + var historyPos = 0; - history[0] = ''; // the very latest input is empty for new entry. + history[0] = ""; // the very latest input is empty for new entry. // // move back in the history var back = function () { // step backwards in history stack - history_pos = Math.min(++history_pos, history.length - 1); - return history[history.length - 1 - history_pos]; + historyPos = Math.min(++historyPos, history.length - 1); + return history[history.length - 1 - historyPos]; } // // move forward in the history var fwd = function () { // step forwards in history stack - history_pos = Math.max(--history_pos, 0); - return history[history.length - 1 - history_pos]; + historyPos = Math.max(--historyPos, 0); + return history[history.length - 1 - historyPos]; } // @@ -33,23 +33,13 @@ let history_plugin = (function () { var add = function (input) { // add a new entry to history, don't repeat latest if (input && input != history[history.length-2]) { - if (history.length >= history_max) { + if (history.length >= historyMax) { history.shift(); // kill oldest entry } history[history.length-1] = input; - history[history.length] = ''; + history[history.length] = ""; } - // reset the position to the last history entry - history_pos = 0; - } - - // - // Add input to the scratch line - var scratch = function (input) { - // Put the input into the last history entry (which is normally empty) - // without making the array larger as with add. - // Allows for in-progress editing to be saved. - history[history.length-1] = input; + historyPos = 0; } // Public @@ -58,21 +48,26 @@ let history_plugin = (function () { // Handle up arrow and down arrow events. var onKeydown = function(event) { var code = event.which; - var history_entry = null; - var inputfield = $("#inputfield"); + var historyEntry = null; - if (code === 38) { // Arrow up - history_entry = back(); + // Only process up/down arrow if cursor is at the end of the line. + if (code === 38 && event.shiftKey) { // Arrow up + historyEntry = back(); } - else if (code === 40) { // Arrow down - history_entry = fwd(); + else if (code === 40 && event.shiftKey) { // Arrow down + historyEntry = fwd(); } - if (history_entry !== null) { - // Performing a history navigation - // replace the text in the input and move the cursor to the end of the new value - inputfield.val(''); - inputfield.blur().focus().val(history_entry); + // are we processing an up or down history event? + if (historyEntry !== null) { + // Doing a history navigation; replace the text in the input and + // move the cursor to the end of the new value + var inputfield = $(".inputfield:focus"); + if( inputfield.length < 1 ) { // pre-goldenlayout backwards compatibility + inputfield = $("#inputfield"); + } + inputfield.val(""); + inputfield.blur().focus().val(historyEntry); event.preventDefault(); return true; } @@ -84,19 +79,13 @@ let history_plugin = (function () { // Listen for onSend lines to add to history var onSend = function (line) { add(line); - return null; // we are not returning an altered input line + return null; } - // - // Init function - var init = function () { - console.log('History Plugin Initialized.'); - } - return { - init: init, + init: function () {}, onKeydown: onKeydown, onSend: onSend, } -})() -plugin_handler.add('history', history_plugin); +}()); +window.plugin_handler.add("history", history); diff --git a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js index a20a620f52..023db7eb5e 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js +++ b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js @@ -1,9 +1,10 @@ /* * - * Assignable 'hot-buttons' Plugin + * Assignable "hot-buttons" Plugin * - * This adds a bar of 9 buttons that can be shift-click assigned whatever is in the textinput buffer, so you can simply - * click the button again and have it execute those commands, instead of having to type it all out again and again. + * This adds a bar of 9 buttons that can be shift-click assigned, + * whatever text is in the bottom textinput buffer will be copied. + * Once assigned, clicking the button again and have it execute those commands. * * It stores these commands as server side options. * @@ -12,51 +13,26 @@ * Stop Evennia * * Copy this file to mygame/web/static_overrides/webclient/js/plugins/hotbuttons.js - * Copy evennia/web/webclient/templates/webclient/base.html to mygame/web/template_overrides/webclient/base.html + * + * Copy evennia/web/webclient/templates/webclient/base.html to + * mygame/web/template_overrides/webclient/base.html * * Edit mygame/web/template_overrides/webclient/base.html to add: - * - * after the other plugin tags. + * + * before the goldenlayout.js plugin tags or after the splithandler.js plugin tags * - * Run: evennia collectstatic (say 'yes' to the overwrite prompt) + * If you are using goldenlayout.js, uncomment the hotbuttons component in goldenlayout_default_config.js + * + * Run: evennia collectstatic (say "yes" to the overwrite prompt) * Start Evennia + * + * REQUIRES: goldenlayout.js OR splithandler.js */ -plugin_handler.add('hotbuttons', (function () { +let hotbuttons = (function () { + var dependenciesMet = false; - var num_buttons = 9; - var command_cache = new Array(num_buttons); - - // - // Add Buttons - var addButtonsUI = function () { - var buttons = $( [ - '
', - '
', - '
', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '
', - '
', - '
', - ].join("\n") ); - - // Add buttons in front of the existing #inputform - $('#input').prev().replaceWith(buttons); - - Split(['#main','#buttons','#input'], { - sizes: [85,5,10], - direction: 'vertical', - gutterSize: 4, - minSize: [150,20,50], - }); - } + var numButtons = 9; + var commandCache = new Array; // // collect command text @@ -64,7 +40,7 @@ plugin_handler.add('hotbuttons', (function () { // make sure text has something in it if( text && text.length ) { // cache the command text - command_cache[n] = text; + commandCache[n] = text; // is there a space in the command, indicating "command argument" syntax? if( text.indexOf(" ") > 0 ) { @@ -83,13 +59,13 @@ plugin_handler.add('hotbuttons', (function () { // change button text to "unassigned" $("#assign_button"+n).text( "unassigned" ); // clear current command - command_cache[n] = "unassigned"; + commandCache[n] = "unassigned"; } // // actually send the command associated with the button that is clicked var sendImmediate = function(n) { - var text = command_cache[n]; + var text = commandCache[n]; if( text.length ) { Evennia.msg("text", [text], {}); } @@ -99,55 +75,129 @@ plugin_handler.add('hotbuttons', (function () { // send, assign, or clear the button var hotButtonClicked = function(e) { var button = $("#assign_button"+e.data); - console.log("button " + e.data + " clicked"); if( button.text() == "unassigned" ) { // Assign the button and send the full button state to the server using a Webclient_Options event - assignButton( e.data, $('#inputfield').val() ); - Evennia.msg("webclient_options", [], { "HotButtons": command_cache }); + var input = $(".inputfield:last"); + if( input.length < 1 ) { + input = $("#inputfield"); + } + assignButton( e.data, input.val() ); + Evennia.msg("webclient_options", [], { "HotButtons": commandCache }); } else { if( e.shiftKey ) { // Clear the button and send the full button state to the server using a Webclient_Options event clearButton(e.data); - Evennia.msg("webclient_options", [], { "HotButtons": command_cache }); + Evennia.msg("webclient_options", [], { "HotButtons": commandCache }); } else { sendImmediate(e.data); } } } + + // + // Add Buttons UI for SplitHandler + var addButtonsUI = function () { + var buttons = $( [ + "
", + "
", + "
", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "
", + "
", + "
", + ].join("\n") ); + + // Add buttons in front of the existing #inputform + $("#input").prev().replaceWith(buttons); + + Split(["#main","#buttons","#input"], { + sizes: [85,5,10], + direction: "vertical", + gutterSize: 4, + minSize: [150,20,50], + }); + + for( var n=0; n"); + + var len = commandCache.length; + for( var x=len; x < len + numButtons; x++ ) { + commandCache.push("unassigned"); + + // initialize button command cache and onClick handler + var button = $("