From b5887c2d95eab91a05b8748ba1526ab501e85f0e Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Thu, 31 Jan 2019 22:01:17 -0500 Subject: [PATCH 01/16] Add a dual_input plugin --- .../static/webclient/js/plugins/default_in.js | 7 +- .../static/webclient/js/plugins/dual_input.js | 72 ++++++++++ .../static/webclient/js/plugins/history.js | 135 +++++++++++------- .../webclient/templates/webclient/base.html | 4 +- 4 files changed, 164 insertions(+), 54 deletions(-) create mode 100644 evennia/web/webclient/static/webclient/js/plugins/dual_input.js 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..d6779c8cfb 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -8,9 +8,10 @@ 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 inputfield = $("#inputfield"); + + // Enter Key without shift + if ( inputfield.is(":focus") && (event.which === 13) && (!event.shiftKey) ) { var outtext = inputfield.val(); var lines = outtext.trim().replace(/[\r]+/,"\n").replace(/[\n]+/, "\n").split("\n"); for (var i = 0; i < lines.length; i++) { diff --git a/evennia/web/webclient/static/webclient/js/plugins/dual_input.js b/evennia/web/webclient/static/webclient/js/plugins/dual_input.js new file mode 100644 index 0000000000..5ecce79e82 --- /dev/null +++ b/evennia/web/webclient/static/webclient/js/plugins/dual_input.js @@ -0,0 +1,72 @@ +/* + * + * Dual Input Pane Plugin (Requires splithandler plugin) + * + * This adds a second input window for games that really benefit from having two separate, + * high-complexity commands being created at the same time. + * + * Note: Incompatible with hotbuttons plugin because both Split() the same location + * Split.js doesn't seem to support adding multiple splits at the same level. + */ +plugin_handler.add('dual_input', (function () { + // + // onKeydown check if the second inputfield is focused. + // If so, send the input on '' key. + var onKeydown = function () { + let inputfield = $("#inputfield2"); + if ( inputfield.is(":focus") ) { + if( (event.which === 13) && (!event.shiftKey) ) { + var outtext = inputfield.val(); + var lines = outtext.trim().replace(/[\r\n]+/,"\n").split("\n"); + + for (var i = 0; i < lines.length; i++) { + plugin_handler.onSend( lines[i].trim() ); + } + + inputfield.val(''); + event.preventDefault(); + return true; + } + } + return false; + } + + // + // Initialize me + var init = function() { + // Add buttons to the UI + var input2 = $( [ + '
', + ' ', + '
', + ].join("\n") ); + + // Add second inputform between the existing #main and #inputform, + // replacing the previous gutter div added by the splithandler plugin + $('#input').prev().replaceWith(input2); + + Split(['#main','#input2','#input'], { + sizes: [80,10,10], + direction: 'vertical', + gutterSize: 4, + minSize: [150,50,50], + }); + + $('#inputfield2').css({ + "display": "inline", + "height": "100%", + "width": "100%", + "background-color": "black", + "color": "white", + "padding": "0 .45rem", + "font-size": "1.1rem", + "font-family": "'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace" + }); + console.log("Dual Input Plugin Initialized."); + } + + return { + init: init, + onKeydown: onKeydown, + } +})()); diff --git a/evennia/web/webclient/static/webclient/js/plugins/history.js b/evennia/web/webclient/static/webclient/js/plugins/history.js index 60b1a2b163..7860547440 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/history.js +++ b/evennia/web/webclient/static/webclient/js/plugins/history.js @@ -6,50 +6,72 @@ let history_plugin = (function () { // Manage history for input line - var history_max = 21; - var history = new Array(); - var history_pos = 0; - - history[0] = ''; // the very latest input is empty for new entry. + var history_max = 20; + var history = {}; + var history_pos = {}; // - // 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]; + // Add a new textarea to track history for. + var track_history_for_id = function(id) { + if( ! history.hasOwnProperty( id ) ) { + history[id] = new Array; + history_pos[id] = -1; + } else { + console.log('IGNORED -- already tracking history for that DOM element!'); + } } // - // 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]; + // Return whichever inputfield (if any) is focused, out of the set we are tracking + var get_focused_input = function () { + let inputfield = $( document.activeElement ); + + // is the focused element one of the ones we are tracking history for? + if( history.hasOwnProperty( inputfield.attr('id') ) ) { + return inputfield; + } + return null; + } + + // + // move back from the history (to newer elements) + var back = function (id) { + // step back in history queue, to the most recently stored entry. + if( history_pos[id] >= 0 ) { + history_pos[id]--; + + // if we've stepped "before" the first element of our queue, return new, empty string + if( history_pos[id] == -1 ) { + return ''; + } + } + + return history[id][ history_pos[id] ]; + } + + // + // move forward into the history (to older elements) + var fwd = function (id) { + // step forward in history queue, restricted by bounds checking + if( history_pos[id] < Math.min( history[id].length - 1, history_max - 1 ) ) { + history_pos[id]++; + } + return history[id][ history_pos[id] ]; } // // add a new history line - var add = function (input) { + var add = function (id, input) { // add a new entry to history, don't repeat latest - if (input && input != history[history.length-2]) { - if (history.length >= history_max) { - history.shift(); // kill oldest entry + if (input && input != history[id][0]) { + // make sure to trim the history queue length to 'history_max' + if (history[id].length + 1 >= history_max) { + history[id].pop(); // remove oldest entry from queue } - history[history.length-1] = input; - history[history.length] = ''; + history[id].unshift(input); // add newest entry to beginning of queue } - // 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; + // reset the position to the beginning of the queue + history_pos[id] = -1; } // Public @@ -57,39 +79,52 @@ 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 keycode = event.which; - if (code === 38) { // Arrow up - history_entry = back(); - } - else if (code === 40) { // Arrow down - history_entry = fwd(); - } + // Is one of the two input fields focused? + let inputfield = get_focused_input(); + if( inputfield != null ) { + let id = inputfield.attr('id') + let history_entry = null; // check the keycode for up/down arrows + if (keycode === 40) { // Arrow down + history_entry = back(id); + } + else if (keycode === 38) { // Arrow up + history_entry = fwd(id); + } - 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); - event.preventDefault(); - return true; + 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.blur().focus().val(history_entry); + event.preventDefault(); + return true; + } } - return false; } // // Listen for onSend lines to add to history var onSend = function (line) { - add(line); + let inputfield = get_focused_input(); + if( inputfield != null ) { + add(inputfield.attr('id'), line); + } return null; // we are not returning an altered input line } // // Init function var init = function () { + track_history_for_id('inputfield'); // The default inputfield + + // check to see if the dual_input plugin is enabled. + if( !(typeof plugins['dual_input'] === "undefined") ) { + console.log('configuring history tracking for dual_input plugin'); + track_history_for_id('inputfield2'); + } + console.log('History Plugin Initialized.'); } diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 0cc4302af4..143f7df926 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -72,8 +72,10 @@ JQuery available. - + + + From 5905be91791eff6cb2cf05d0dc40b7c090c6afe1 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Mon, 18 Feb 2019 23:27:23 -0500 Subject: [PATCH 02/16] add dual_inputs.js --- evennia/web/webclient/templates/webclient/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 143f7df926..331eacea66 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -73,7 +73,7 @@ JQuery available. - + From 8b08d410381e198c899eb4f7b7f62c67bdb4b781 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Mon, 18 Feb 2019 23:48:55 -0500 Subject: [PATCH 03/16] Add golden-layout as an alternative to splithandler --- .../static/webclient/css/goldenlayout.css | 106 +++++ .../static/webclient/js/plugins/default_in.js | 1 - .../webclient/js/plugins/goldenlayout.js | 403 ++++++++++++++++++ .../webclient/templates/webclient/base.html | 10 +- 4 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 evennia/web/webclient/static/webclient/css/goldenlayout.css create mode 100644 evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js 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..54b5663e8a --- /dev/null +++ b/evennia/web/webclient/static/webclient/css/goldenlayout.css @@ -0,0 +1,106 @@ +/* + * + */ + +#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; +} + +#inputfield { + position: absolute; + top: 0; +} + +.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..28bfc9f315 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -8,7 +8,6 @@ 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(); 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..f0aa47b67c --- /dev/null +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -0,0 +1,403 @@ +/* + * + * Golden Layout plugin + * + */ +plugin_handler.add('goldenlayout', (function () { + + var myLayout; + var input_component = null; + var known_types = ['all', 'untagged']; + var untagged = []; + + var config = { + content: [{ + type: 'column', + content: [{ + type: 'component', + componentName: 'Main', + isClosable: false, + componentState: { + types: 'untagged', + update_method: 'newlines', + } + }, { + type: 'component', + componentName: 'input', + id: 'inputComponent', + height: 15, + isClosable: false, + }] + }] + }; + + + var newDragConfig = { + title: 'Untitled', + type: 'component', + componentName: 'evennia', + componentState: { + types: 'all', + update_method: 'newlines', + }, + }; + + + // helper function: filter vals out of array + function filter (vals, array) { + let tmp = array.slice(); + for (i=0; i -1 ) { + tmp.splice( tmp.indexOf(val), 1 ); + } + } + return tmp; + } + + + // + // Calculate all known_types minus the 'all' type, + // then filter out all types that have been mapped to a pane. + var calculate_untagged_types = function () { + // set initial untagged list + untagged = filter( ['all', 'untagged'], known_types); + // for each .content pane + $('.content').each( function () { + let types = $(this).attr('types'); + if ( typeof types !== "undefined" ) { + untagged = filter( types.split(' '), untagged ); + } + }); + } + + + // 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 text_div = component.container.getElement().children('.content'); + let types = text_div.attr('types'); + let update_method = text_div.attr('update_method'); + component.container.extendState({ 'types': types, 'update_method': update_method }); + }); + + var state = JSON.stringify( myLayout.toConfig() ); + localStorage.setItem( 'evenniaGoldenLayoutSavedState', state ); + } + + + // + // + // Handle the renamePopup + var renamePopup = 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'); + if( !renamebox ) { + renamebox = $('
'); + renamebox.append(''); + renamebox.insertBefore( content ); + } else { + let title = $('#renameboxin').val(); + evnt.data.setTitle( title ); + evnt.data.contentItem.setTitle( title ); + myLayout.emit('stateChanged'); + $('#renamebox').remove(); + } + } + + + // + var onSelectTypesClicked = function (evnt) { + let element = $(evnt.data.contentItem.element); + let content = element.find('.content'); + let selected_types = content.attr('types'); + let menu = $('
'); + let div = $('
'); + + if( selected_types ) { + selected_types = selected_types.split(' '); + } + for (i=0; i'+type+''); + } else { + choice = $(''); + } + choice.appendTo(div); + } + div.appendTo(menu); + + element.prepend(menu); + } + + + // + // + var commitCheckboxes = function (evnt) { + let element = $(evnt.data.contentItem.element); + let content = element.find('.content'); + let checkboxes = $('#typelist :input'); + let types = []; + for (i=0; i'); + 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 updatePopup + var updatePopup = function (evnt) { + let updatelist = document.getElementById('updatelist'); + if( !updatelist ) { + onUpdateMethodClicked(evnt); + } else { + let element = $(evnt.data.contentItem.element); + let content = element.find('.content'); + content.attr('update_method', $('input[name=update_method]:checked').val() ); + myLayout.emit('stateChanged'); + $('#updatelist').remove(); + } + } + + + // + // + var onTabCreate = function (tab) { + //HTML for the typeDropdown + let tabRenameControl = $('\u2B57'); + let typePopupControl = $(''); + let updatePopupControl = $(''); + let splitControl = $('+'); + + // track popups when the associated control is clicked + tabRenameControl.click( tab, renamePopup ); + + typePopupControl.click( tab, typePopup ); + + updatePopupControl.click( tab, updatePopup ); + + splitControl.click( tab, function (evnt) { + evnt.data.header.parent.addChild( newDragConfig ); + }); + + // Add the typeDropdown to the header + tab.element.prepend( tabRenameControl ); + tab.element.append( typePopupControl ); + tab.element.append( updatePopupControl ); + tab.element.append( splitControl ); + + if( tab.contentItem.config.componentName == "Main" ) { + tab.element.prepend( $('#optionsbutton').clone(true).addClass('lm_title') ); + } + } + + + // + // + var scrollAll = function () { + let components = myLayout.root.getItemsByType('component'); + components.forEach( function (component) { + if( component.hasId('inputComponent') ) { return; } // ignore input components + + let text_div = component.container.getElement().children('.content'); + let scrollHeight = text_div.prop('scrollHeight'); + let clientHeight = text_div.prop('clientHeight'); + text_div.scrollTop( scrollHeight - clientHeight ); + }); + myLayout.updateSize(); + } + + + // + // + var route_msg = function (text_div, txt, update_method) { + if ( update_method == 'replace' ) { + text_div.html(txt) + } else if ( update_method == 'append' ) { + text_div.append(txt); + } else { // line feed + text_div.append('
' + txt + '
'); + } + let scrollHeight = text_div.prop('scrollHeight'); + let clientHeight = text_div.prop('clientHeight'); + text_div.scrollTop( scrollHeight - clientHeight ); + } + + + // + // Public + // + + + // + // + var initComponent = function (div, container, state, default_types, update_method) { + // set this container's content div types attribute + if( state ) { + div.attr('types', state.types); + div.attr('update_method', state.update_method); + } else { + div.attr('types', default_types); + div.attr('update_method', update_method); + } + div.appendTo( container.getElement() ); + container.on('tab', onTabCreate); + } + + + // + // + 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 ( ! known_types.includes(msgtype) ) { + // this is a new output type that can be mapped to panes + console.log('detected new output type: ' + msgtype) + known_types.push(msgtype); + untagged.push(msgtype); + } + } + + let message_delivered = false; + let components = myLayout.root.getItemsByType('component'); + + components.forEach( function (component) { + if( component.hasId('inputComponent') ) { return; } // ignore the input component + + let text_div = component.container.getElement().children('.content'); + let attr_types = text_div.attr('types'); + let pane_types = attr_types ? attr_types.split(' ') : []; + let update_method = text_div.attr('update_method'); + let txt = args[0]; + + // is this message type listed in this pane's types (or is this pane catching 'all') + if( pane_types.includes(msgtype) || pane_types.includes('all') ) { + route_msg( text_div, txt, update_method ); + message_delivered = true; + } + + // is this pane catching 'upmapped' messages? + // And is this message type listed in the untagged types array? + if( pane_types.includes("untagged") && untagged.includes(msgtype) ) { + route_msg( text_div, txt, update_method ); + message_delivered = true; + } + }); + + if ( message_delivered ) { + return true; + } + // unhandled message + return false; + } + + + // + // 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 ) { + myLayout = new GoldenLayout( JSON.parse( savedState ), mainsub ); + } else { + myLayout = new GoldenLayout( config, mainsub ); + } + + // register our component and replace the default messagewindow with the Main component element + myLayout.registerComponent( 'Main', function (container, componentState) { + let main = $('#messagewindow').addClass('content'); + initComponent(main, container, componentState, 'untagged', 'newlines' ); + }); + + myLayout.registerComponent( 'input', function (container, componentState) { + $('#inputcontrol').remove(); // remove the cluttered, HTML-defined input divs + $('
') + .append( '
' ) + .append( '' ) + .appendTo( container.getElement() ); + }); + + myLayout.registerComponent( 'evennia', function (container, componentState) { + let div = $('
'); + initComponent(div, container, componentState, 'all', 'newlines'); + container.on('destroy', calculate_untagged_types); + }); + + // Make it go. + myLayout.init(); + + // Event when client window changes + $(window).bind("resize", scrollAll); + + // Set Save State callback + myLayout.on( 'stateChanged', onStateChanged ); + + console.log('Golden Layout Plugin Initialized.'); + } + + return { + init: init, + onText: onText, + getGL: function () { return myLayout }, + initComponent: initComponent, + } +})()); diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 0cc4302af4..bb88525cca 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -17,7 +17,6 @@ JQuery available. - @@ -64,8 +63,14 @@ JQuery available. + + + + + {% block guilib_import %} @@ -73,10 +78,13 @@ JQuery available. + + {% endblock %} From 43244b53b3cfeeaef8049bf4de69cbf3f25e44e3 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Thu, 21 Mar 2019 08:44:46 -0400 Subject: [PATCH 04/16] Fix the popup close bugs and expand functionality to allow dual inputs --- .../static/webclient/css/goldenlayout.css | 15 +- .../static/webclient/js/plugins/default_in.js | 48 +++-- .../webclient/js/plugins/goldenlayout.js | 201 ++++++++++++------ .../static/webclient/js/plugins/history.js | 139 +++++------- .../static/webclient/js/webclient_gui.js | 18 ++ 5 files changed, 256 insertions(+), 165 deletions(-) diff --git a/evennia/web/webclient/static/webclient/css/goldenlayout.css b/evennia/web/webclient/static/webclient/css/goldenlayout.css index 54b5663e8a..bbb099e712 100644 --- a/evennia/web/webclient/static/webclient/css/goldenlayout.css +++ b/evennia/web/webclient/static/webclient/css/goldenlayout.css @@ -88,15 +88,26 @@ label { height: 100%; } -#inputsend { +.inputsend { position: absolute; right: 0; z-index: 1; + height: inherit; + width: 30px; } -#inputfield { +.inputfield { position: absolute; top: 0; + height: inherit; + width: calc(100% - 30px); + background-color: black; + color: white; +} + +.inputfield:focus { + background-color: black; + color: white; } .glbutton { 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 d6779c8cfb..115629a820 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -8,17 +8,38 @@ let defaultin_plugin = (function () { // // handle the default key triggering onSend() var onKeydown = function (event) { - var inputfield = $("#inputfield"); - - // Enter Key without shift - if ( inputfield.is(":focus") && (event.which === 13) && (!event.shiftKey) ) { - 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"); + + // check for important keys + switch (event.which) { + 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(); + 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(''); + event.preventDefault(); + } + inputfield.blur(); + break; + + // Anything else, focus() a textarea if needed, and allow the default event + default: + // is anything actually focused? if not, focus the first .inputfield found in the DOM + if( !inputfield.hasClass('inputfield') ) { + // :first only matters if dual_input or similar multi-input plugins are in use + $('.inputfield:last').focus(); + } } return true; @@ -29,10 +50,11 @@ let defaultin_plugin = (function () { var init = function () { // Handle pressing the send button $("#inputsend") - .bind("click", function (event) { + .bind("click", function (evnt) { + // simulate a carriage return var e = $.Event( "keydown" ); e.which = 13; - $('#inputfield').trigger(e); + $('.inputfield:last').trigger(e); }); console.log('DefaultIn initialized'); diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js index f0aa47b67c..0df9f42539 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -14,25 +14,36 @@ plugin_handler.add('goldenlayout', (function () { content: [{ type: 'column', content: [{ - type: 'component', - componentName: 'Main', - isClosable: false, - componentState: { - types: 'untagged', - update_method: 'newlines', - } + type: 'row', + content: [{ + type: 'column', + content: [{ + type: 'component', + componentName: 'Main', + isClosable: false, + componentState: { + types: 'untagged', + update_method: 'newlines', + }, + }] + }], }, { type: 'component', componentName: 'input', id: 'inputComponent', - height: 15, + height: 12, + }, { + type: 'component', + componentName: 'input', + id: 'inputComponent', + height: 12, isClosable: false, }] }] }; - var newDragConfig = { + var newTabConfig = { title: 'Untitled', type: 'component', componentName: 'evennia', @@ -97,16 +108,26 @@ plugin_handler.add('goldenlayout', (function () { let content = element.find('.content'); let title = evnt.data.contentItem.config.title; let renamebox = document.getElementById('renamebox'); + + // check that no other popup is open + if( document.getElementById('typelist') || document.getElementById('updatelist') ) { + return; + } + if( !renamebox ) { renamebox = $('
'); renamebox.append(''); renamebox.insertBefore( content ); } else { let title = $('#renameboxin').val(); - evnt.data.setTitle( title ); - evnt.data.contentItem.setTitle( title ); - myLayout.emit('stateChanged'); - $('#renamebox').remove(); + + // check that the renamebox that is open is for this contentItem + if( $('#renamebox').parent()[0] === content.parent()[0] ) { + evnt.data.setTitle( title ); + evnt.data.contentItem.setTitle( title ); + myLayout.emit('stateChanged'); + $('#renamebox').remove(); + } } } @@ -140,9 +161,7 @@ plugin_handler.add('goldenlayout', (function () { // // - var commitCheckboxes = function (evnt) { - let element = $(evnt.data.contentItem.element); - let content = element.find('.content'); + var commitCheckboxes = function (evnt, content) { let checkboxes = $('#typelist :input'); let types = []; for (i=0; i') - .append( '
' ) - .append( '' ) - .appendTo( container.getElement() ); - }); - - myLayout.registerComponent( 'evennia', function (container, componentState) { - let div = $('
'); - initComponent(div, container, componentState, 'all', 'newlines'); - container.on('destroy', calculate_untagged_types); - }); - - // Make it go. + // + var postInit = function () { + // finish the setup and actually start GoldenLayout myLayout.init(); - // Event when client window changes + // Set the Event handler for when the client window changes size $(window).bind("resize", scrollAll); // Set Save State callback @@ -394,10 +415,64 @@ plugin_handler.add('goldenlayout', (function () { console.log('Golden Layout Plugin Initialized.'); } + + // + // 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 ) { + config = JSON.parse( savedState ); + } + + myLayout = new 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); + }); + + }); + + myLayout.registerComponent( 'evennia', function (container, componentState) { + let div = $('
'); + initComponent(div, container, componentState, 'all', 'newlines'); + container.on('destroy', calculate_untagged_types); + }); + } + + return { init: init, + postInit: postInit, + onKeydown: onKeydown, onText: onText, getGL: function () { return myLayout }, - initComponent: initComponent, + getConfig: function () { return config }, + setConfig: function (newconfig) { config = newconfig }, } })()); diff --git a/evennia/web/webclient/static/webclient/js/plugins/history.js b/evennia/web/webclient/static/webclient/js/plugins/history.js index 7860547440..c68d2b77a5 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/history.js +++ b/evennia/web/webclient/static/webclient/js/plugins/history.js @@ -6,72 +6,49 @@ let history_plugin = (function () { // Manage history for input line - var history_max = 20; - var history = {}; - var history_pos = {}; + var history_max = 21; + var history = new Array(); + var history_pos = 0; + + history[0] = ''; // the very latest input is empty for new entry. // - // Add a new textarea to track history for. - var track_history_for_id = function(id) { - if( ! history.hasOwnProperty( id ) ) { - history[id] = new Array; - history_pos[id] = -1; - } else { - console.log('IGNORED -- already tracking history for that DOM element!'); - } + // 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]; } // - // Return whichever inputfield (if any) is focused, out of the set we are tracking - var get_focused_input = function () { - let inputfield = $( document.activeElement ); - - // is the focused element one of the ones we are tracking history for? - if( history.hasOwnProperty( inputfield.attr('id') ) ) { - return inputfield; - } - return null; - } - - // - // move back from the history (to newer elements) - var back = function (id) { - // step back in history queue, to the most recently stored entry. - if( history_pos[id] >= 0 ) { - history_pos[id]--; - - // if we've stepped "before" the first element of our queue, return new, empty string - if( history_pos[id] == -1 ) { - return ''; - } - } - - return history[id][ history_pos[id] ]; - } - - // - // move forward into the history (to older elements) - var fwd = function (id) { - // step forward in history queue, restricted by bounds checking - if( history_pos[id] < Math.min( history[id].length - 1, history_max - 1 ) ) { - history_pos[id]++; - } - return history[id][ history_pos[id] ]; + // 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]; } // // add a new history line - var add = function (id, input) { + var add = function (input) { // add a new entry to history, don't repeat latest - if (input && input != history[id][0]) { - // make sure to trim the history queue length to 'history_max' - if (history[id].length + 1 >= history_max) { - history[id].pop(); // remove oldest entry from queue + if (input && input != history[history.length-2]) { + if (history.length >= history_max) { + history.shift(); // kill oldest entry } - history[id].unshift(input); // add newest entry to beginning of queue + history[history.length-1] = input; + history[history.length] = ''; } - // reset the position to the beginning of the queue - history_pos[id] = -1; + 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; } // Public @@ -79,52 +56,40 @@ let history_plugin = (function () { // // Handle up arrow and down arrow events. var onKeydown = function(event) { - var keycode = event.which; + var code = event.which; + var history_entry = null; + var inputfield = $('.inputfield:focus'); - // Is one of the two input fields focused? - let inputfield = get_focused_input(); - if( inputfield != null ) { - let id = inputfield.attr('id') - let history_entry = null; // check the keycode for up/down arrows - if (keycode === 40) { // Arrow down - history_entry = back(id); - } - else if (keycode === 38) { // Arrow up - history_entry = fwd(id); - } - - 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.blur().focus().val(history_entry); - event.preventDefault(); - return true; - } + // Only process up/down arrow if cursor is at the end of the line. + if (code === 38 && event.shiftKey) { // Arrow up + history_entry = back(); } + else if (code === 40 && event.shiftKey) { // Arrow down + history_entry = fwd(); + } + + // are we processing an up or down history event? + if (history_entry !== null) { + // Doing 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); + event.preventDefault(); + return true; + } + return false; } // // Listen for onSend lines to add to history var onSend = function (line) { - let inputfield = get_focused_input(); - if( inputfield != null ) { - add(inputfield.attr('id'), line); - } - return null; // we are not returning an altered input line + add(line); + return null; } // // Init function var init = function () { - track_history_for_id('inputfield'); // The default inputfield - - // check to see if the dual_input plugin is enabled. - if( !(typeof plugins['dual_input'] === "undefined") ) { - console.log('configuring history tracking for dual_input plugin'); - track_history_for_id('inputfield2'); - } - console.log('History Plugin Initialized.'); } diff --git a/evennia/web/webclient/static/webclient/js/webclient_gui.js b/evennia/web/webclient/static/webclient/js/webclient_gui.js index 146d4a5268..412827f153 100644 --- a/evennia/web/webclient/static/webclient/js/webclient_gui.js +++ b/evennia/web/webclient/static/webclient/js/webclient_gui.js @@ -228,6 +228,20 @@ var plugin_handler = (function () { } + // + // normally init() is all that is needed, but some cases may require a second + // pass to avoid chicken/egg dependencies between two plugins. + var postInit = function () { + // does this plugin need postInit() to be called? + for( let n=0; n < ordered_plugins.length; n++ ) { + let plugin = ordered_plugins[n]; + if( 'postInit' in plugin ) { + plugin.postInit(); + } + } + } + + return { add: add, onKeydown: onKeydown, @@ -241,6 +255,7 @@ var plugin_handler = (function () { onConnectionClose: onConnectionClose, onSend: onSend, init: init, + postInit: postInit, } })(); @@ -285,5 +300,8 @@ $(document).ready(function() { // Initialize all plugins plugin_handler.init(); + // Finish Initializing any plugins that need a second stage + plugin_handler.postInit(); + console.log("Completed Webclient setup"); }); From 85d1b78da1cb9e95a5aae46a03e4c292496ff312 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Thu, 21 Mar 2019 10:16:02 -0400 Subject: [PATCH 05/16] remove the dual_input.js from this branch --- .../static/webclient/js/plugins/dual_input.js | 107 ------------------ 1 file changed, 107 deletions(-) delete mode 100644 evennia/web/webclient/static/webclient/js/plugins/dual_input.js diff --git a/evennia/web/webclient/static/webclient/js/plugins/dual_input.js b/evennia/web/webclient/static/webclient/js/plugins/dual_input.js deleted file mode 100644 index eaa371d24d..0000000000 --- a/evennia/web/webclient/static/webclient/js/plugins/dual_input.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * - * Dual Input Pane Plugin (Requires splithandler plugin) - * - * This adds a second input window for games that really benefit from having two separate, - * high-complexity commands being created at the same time. - * - * Note: Incompatible with hotbuttons plugin because both Split() the same location - * Split.js doesn't seem to support adding multiple splits at the same level. - */ -plugin_handler.add('dual_input', (function () { - // - // - var splitHandlerUI = function ( input2 ) { - input2.addClass( 'split split-vertical' ); - - // Add second inputform between the existing #main and #inputform, - // replacing the previous gutter div added by the splithandler plugin - $('#input').prev().replaceWith(input2); - - Split(['#main','#input2','#input'], { - sizes: [80,10,10], - direction: 'vertical', - gutterSize: 4, - minSize: [150,50,50], - }); - } - - - // - // - var goldenLayoutUI = function ( input2 ) { - var myLayout = plugins['goldenlayout'].getGL(); - var input = myLayout.root.getComponentsByName('input'); - - myLayout.registerComponent( 'input2', function (container, componentState) { - input2.addClass( 'content' ); - input2.appendTo( container.getElement() ); - }); - - input.addChild({ - title: 'input2', - type: 'component', - componentName: 'input2', - componentId: 'input', - }); - } - - - // - // onKeydown check if the second inputfield is focused. - // If so, send the input on '' key. - var onKeydown = function () { - let inputfield = $("#inputfield2"); - if ( inputfield.is(":focus") ) { - if( (event.which === 13) && (!event.shiftKey) ) { - var outtext = inputfield.val(); - var lines = outtext.trim().replace(/[\r\n]+/,"\n").split("\n"); - - for (var i = 0; i < lines.length; i++) { - plugin_handler.onSend( lines[i].trim() ); - } - - inputfield.val(''); - event.preventDefault(); - return true; - } - } - return false; - } - - // - // Initialize me - var init = function() { - var input2 = $( [ - '
', - ' ', - '
', - ].join("\n") ); - - if( plugins.hasOwnProperty('splithandler') ) { - splitHandlerUI(input2); - } - - if( plugins.hasOwnProperty('goldenlayout') ) { - goldenLayoutUI(input2); - } - - $('#inputfield2').css({ - "display": "inline", - "height": "100%", - "width": "100%", - "background-color": "black", - "color": "white", - "padding": "0 .45rem", - "font-size": "1.1rem", - "font-family": "'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace" - }); - - console.log("Dual Input Plugin Initialized."); - } - - return { - init: init, - onKeydown: onKeydown, - } -})()); From 0b2fd9f4bfffe89b98db50ffcc387215b825d11a Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Thu, 21 Mar 2019 10:23:59 -0400 Subject: [PATCH 06/16] Add more detailed tooltips --- .../web/webclient/static/webclient/js/plugins/goldenlayout.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js index 0df9f42539..06388d6185 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -21,6 +21,7 @@ plugin_handler.add('goldenlayout', (function () { type: 'component', componentName: 'Main', isClosable: false, + tooltip: 'Main - drag to desird position.', componentState: { types: 'untagged', update_method: 'newlines', @@ -32,12 +33,14 @@ plugin_handler.add('goldenlayout', (function () { componentName: 'input', id: 'inputComponent', height: 12, + tooltip: 'Input - The last input in the layout is always the default.', }, { type: 'component', componentName: 'input', id: 'inputComponent', height: 12, isClosable: false, + tooltip: 'Input - The last input in the layout is always the default.', }] }] }; From 024c9fd85099f8852f343016482f3321261cd470 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Fri, 22 Mar 2019 07:54:05 -0400 Subject: [PATCH 07/16] Autoclose Dropdowns on certain UI events --- .../webclient/js/plugins/goldenlayout.js | 392 ++++++++++-------- 1 file changed, 222 insertions(+), 170 deletions(-) diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js index 06388d6185..ea1c305f21 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -60,7 +60,7 @@ plugin_handler.add('goldenlayout', (function () { // helper function: filter vals out of array function filter (vals, array) { let tmp = array.slice(); - for (i=0; i -1 ) { tmp.splice( tmp.indexOf(val), 1 ); @@ -73,7 +73,7 @@ plugin_handler.add('goldenlayout', (function () { // // Calculate all known_types minus the 'all' type, // then filter out all types that have been mapped to a pane. - var calculate_untagged_types = function () { + var calculateUntaggedTypes = function () { // set initial untagged list untagged = filter( ['all', 'untagged'], known_types); // for each .content pane @@ -86,6 +86,208 @@ plugin_handler.add('goldenlayout', (function () { } + // + // + 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(); + } + + + // + // 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 closeTypelistDropdown = function () { + let content = $('#typelist').parent().find('.content'); + let checkboxes = $('#typelist :input'); + + let types = []; + for (let i=0; i'); + let div = $('
'); + + if( selected_types ) { + selected_types = selected_types.split(' '); + } + for (let i=0; i'+type+''); + } 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 closeUpdatelistDropdown = function () { + let content = $('#updatelist').parent().find('.content'); + let value = $('input[name=upmethod]:checked').val(); + + content.attr('update_method', value ); + myLayout.emit('stateChanged'); + $('#updatelist').remove(); + } + + + // + // + var onUpdateMethodClicked = function (evnt) { + let element = $(evnt.data.contentItem.element); + let content = element.find('.content'); + let update_method = content.attr('update_method'); + let nlchecked = (update_method == 'newlines') ? 'checked="checked"' : ''; + let apchecked = (update_method == 'append') ? 'checked="checked"' : ''; + let rpchecked = (update_method == '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'); @@ -103,188 +305,38 @@ plugin_handler.add('goldenlayout', (function () { } - // - // - // Handle the renamePopup - var renamePopup = 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 popup is open - if( document.getElementById('typelist') || document.getElementById('updatelist') ) { - return; - } - - if( !renamebox ) { - renamebox = $('
'); - renamebox.append(''); - renamebox.insertBefore( content ); - } else { - let title = $('#renameboxin').val(); - - // check that the renamebox that is open is for this contentItem - if( $('#renamebox').parent()[0] === content.parent()[0] ) { - evnt.data.setTitle( title ); - evnt.data.contentItem.setTitle( title ); - myLayout.emit('stateChanged'); - $('#renamebox').remove(); - } - } - } - - - // - var onSelectTypesClicked = function (evnt) { - let element = $(evnt.data.contentItem.element); - let content = element.find('.content'); - let selected_types = content.attr('types'); - let menu = $('
'); - let div = $('
'); - - if( selected_types ) { - selected_types = selected_types.split(' '); - } - for (i=0; i'+type+''); - } else { - choice = $(''); - } - choice.appendTo(div); - } - div.appendTo(menu); - - element.prepend(menu); - } - - - // - // - var commitCheckboxes = function (evnt, content) { - let checkboxes = $('#typelist :input'); - let types = []; - for (i=0; i'); - 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 updatePopup - var updatePopup = function (evnt) { - let updatelist = document.getElementById('updatelist'); - - // check that no other popup is open - if( document.getElementById('renamebox') || document.getElementById('typelist') ) { - return; - } - - if( !updatelist ) { - onUpdateMethodClicked(evnt); - } else { - let element = $(evnt.data.contentItem.element); - let content = element.find('.content'); - - // check that the updatelist that is open is for this contentItem - if( $('#updatelist').parent().children('.lm_content')[0] === content.parent()[0] ) { - content.attr('update_method', $('input[name=update_method]:checked').val() ); - myLayout.emit('stateChanged'); - $('#updatelist').remove(); - } - } - } - - // // var onTabCreate = function (tab) { //HTML for the typeDropdown - let tabRenameControl = $('\u2B57'); - let typePopupControl = $(''); - let updatePopupControl = $(''); - let splitControl = $('+'); + let renameDropdownControl = $('\u2B57'); + let typeDropdownControl = $(''); + let updateDropdownControl = $(''); + let splitControl = $('+'); - // track popups when the associated control is clicked - tabRenameControl.click( tab, renamePopup ); + // track dropdowns when the associated control is clicked + renameDropdownControl.click( tab, renameDropdown ); - typePopupControl.click( tab, typePopup ); + typeDropdownControl.click( tab, typeDropdown ); - updatePopupControl.click( tab, updatePopup ); + 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( tabRenameControl ); - tab.element.append( typePopupControl ); - tab.element.append( updatePopupControl ); + 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 ); } @@ -306,7 +358,7 @@ plugin_handler.add('goldenlayout', (function () { // // - var route_msg = function (text_div, txt, update_method) { + var routeMsg = function (text_div, txt, update_method) { if ( update_method == 'replace' ) { text_div.html(txt) } else if ( update_method == 'append' ) { @@ -383,14 +435,14 @@ plugin_handler.add('goldenlayout', (function () { // is this message type listed in this pane's types (or is this pane catching 'all') if( pane_types.includes(msgtype) || pane_types.includes('all') ) { - route_msg( text_div, txt, update_method ); + routeMsg( text_div, txt, update_method ); message_delivered = true; } // is this pane catching 'upmapped' messages? // And is this message type listed in the untagged types array? if( pane_types.includes("untagged") && untagged.includes(msgtype) ) { - route_msg( text_div, txt, update_method ); + routeMsg( text_div, txt, update_method ); message_delivered = true; } }); @@ -464,7 +516,7 @@ plugin_handler.add('goldenlayout', (function () { myLayout.registerComponent( 'evennia', function (container, componentState) { let div = $('
'); initComponent(div, container, componentState, 'all', 'newlines'); - container.on('destroy', calculate_untagged_types); + container.on('destroy', calculateUntaggedTypes); }); } From 484fc1252458df2d1064807c9843688c6d131d7e Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Mon, 25 Mar 2019 22:39:58 -0400 Subject: [PATCH 08/16] fix tab-key handling --- evennia/web/webclient/static/webclient/js/plugins/default_in.js | 1 + 1 file changed, 1 insertion(+) 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 115629a820..0c19db9f4d 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -13,6 +13,7 @@ let defaultin_plugin = (function () { // 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 From e15370c93a0c6a62cbadb98ac2bc6cc53b06da59 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Tue, 26 Mar 2019 23:47:22 -0400 Subject: [PATCH 09/16] Add a way for plugins to specify new known_types and fix up hotbuttons for goldenlayout. --- .../webclient/js/plugins/goldenlayout.js | 1 + .../static/webclient/js/plugins/hotbuttons.js | 147 +++++++++++++----- 2 files changed, 112 insertions(+), 36 deletions(-) diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js index ea1c305f21..2b1962922b 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -529,5 +529,6 @@ plugin_handler.add('goldenlayout', (function () { getGL: function () { return myLayout }, getConfig: function () { return config }, setConfig: function (newconfig) { config = newconfig }, + addKnownType: function (newtype) { known_types.push(newtype) }, } })()); diff --git a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js index a20a620f52..ed00fcc636 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js +++ b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js @@ -22,42 +22,103 @@ * Start Evennia */ plugin_handler.add('hotbuttons', (function () { + var dependencies_met = true; // To start, assume either splithandler or goldenlayout plugin is enabled. + + var hotButtonConfig = { + content: [{ + type: 'column', + content: [{ + type: 'row', + content: [{ + type: 'column', + content: [{ + type: 'component', + componentName: 'Main', + isClosable: false, + tooltip: 'Main - drag to desird position.', + componentState: { + types: 'untagged', + update_method: 'newlines', + }, + }] + }], + }, { + type: 'component', + componentName: 'hotbuttons', + id: 'inputComponent', + height: 12, + tooltip: 'Input - The last input in the layout is always the default.', + }, { + type: 'component', + componentName: 'input', + id: 'inputComponent', + height: 12, + tooltip: 'Input - The last input in the layout is always the default.', + }, { + type: 'component', + componentName: 'input', + id: 'inputComponent', + height: 12, + isClosable: false, + tooltip: 'Input - The last input in the layout is always the default.', + }] + }] + }; var num_buttons = 9; var command_cache = new Array(num_buttons); + var buttons = null; // // Add Buttons var addButtonsUI = function () { - var buttons = $( [ - '
', - '
', - '
', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '
', - '
', - '
', + buttons = $( [ + '
', + '
', + '
', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '
', + '
', + '
', ].join("\n") ); - // Add buttons in front of the existing #inputform - $('#input').prev().replaceWith(buttons); + // Are we using splithandler? + if( plugins['splithandler'] ) { + // 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], - }); + Split(['#main','#buttons','#input'], { + sizes: [85,5,10], + direction: 'vertical', + gutterSize: 4, + minSize: [150,20,50], + }); + + return true; + } + + // Are we using GoldenLayout? + if( plugins['goldenlayout'] ) { + // update goldenlayout's global config + plugins['goldenlayout'].setConfig( hotButtonConfig ); + + // then wait for postInit() to create the required component + return true; + } + + // Neither so fail + return false; } + // // collect command text var assignButton = function(n, text) { // n is 1-based @@ -120,11 +181,9 @@ plugin_handler.add('hotbuttons', (function () { // // Handle the HotButtons part of a Webclient_Options event var onGotOptions = function(args, kwargs) { - console.log( args ); - console.log( kwargs ); - if( kwargs['HotButtons'] ) { - var buttons = kwargs['HotButtons']; - $.each( buttons, function( key, value ) { + if( dependencies_met && kwargs['HotButtons'] ) { + var button_options = kwargs['HotButtons']; + $.each( button_options, function( key, value ) { assignButton(key, value); }); } @@ -135,19 +194,35 @@ plugin_handler.add('hotbuttons', (function () { var init = function() { // Add buttons to the UI - addButtonsUI(); - - // assign button cache - for( var n=0; n Date: Tue, 26 Mar 2019 23:50:11 -0400 Subject: [PATCH 10/16] fix the ordering of plugins to properly work with goldenlayout --- evennia/web/webclient/templates/webclient/base.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 344936a765..66e0dcc773 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -83,10 +83,12 @@ JQuery available. --> - - + + From 0ca3bdae9f1e379c25bcfcdb606f21e3ea6bd658 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Mon, 1 Apr 2019 16:22:53 -0400 Subject: [PATCH 11/16] Add the ability to create more input windows --- .../webclient/js/plugins/goldenlayout.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js index 2b1962922b..75a137ac0f 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -56,6 +56,12 @@ plugin_handler.add('goldenlayout', (function () { }, }; + var newInputConfig = { + title: 'input', + type: 'component', + componentName: 'input', + id: 'inputComponent', + }; // helper function: filter vals out of array function filter (vals, array) { @@ -340,6 +346,23 @@ plugin_handler.add('goldenlayout', (function () { } + // + // + 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 () { @@ -511,6 +534,7 @@ plugin_handler.add('goldenlayout', (function () { $( $(evnt.target).siblings('.inputfield')[0] ).trigger(e); }); + container.on('tab', onInputCreate); }); myLayout.registerComponent( 'evennia', function (container, componentState) { From 40b77b2eb8ca8d2b840ea4a11b2ea8832a85238c Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Mon, 1 Apr 2019 22:09:18 -0400 Subject: [PATCH 12/16] fix a couple of subtle backwards compatibility bugs --- .../static/webclient/js/plugins/default_in.js | 25 +++++++++++++------ .../static/webclient/js/plugins/history.js | 17 +++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) 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 0c19db9f4d..83aeb96a06 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -11,6 +11,10 @@ let defaultin_plugin = (function () { // 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 @@ -22,13 +26,13 @@ let defaultin_plugin = (function () { break; case 13: // Enter key - var outtext = inputfield.val(); + 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(''); + inputfield.val(''); // Clear this inputfield event.preventDefault(); } inputfield.blur(); @@ -36,10 +40,15 @@ let defaultin_plugin = (function () { // Anything else, focus() a textarea if needed, and allow the default event default: - // is anything actually focused? if not, focus the first .inputfield found in the DOM - if( !inputfield.hasClass('inputfield') ) { - // :first only matters if dual_input or similar multi-input plugins are in use - $('.inputfield:last').focus(); + // 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(); + } } } @@ -49,13 +58,13 @@ 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 (evnt) { // simulate a carriage return var e = $.Event( "keydown" ); e.which = 13; - $('.inputfield:last').trigger(e); + $("#inputfield").focus().trigger(e); }); console.log('DefaultIn initialized'); diff --git a/evennia/web/webclient/static/webclient/js/plugins/history.js b/evennia/web/webclient/static/webclient/js/plugins/history.js index c68d2b77a5..8afcec44f3 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/history.js +++ b/evennia/web/webclient/static/webclient/js/plugins/history.js @@ -42,15 +42,6 @@ let history_plugin = (function () { 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; - } - // Public // @@ -58,7 +49,6 @@ let history_plugin = (function () { var onKeydown = function(event) { var code = event.which; var history_entry = null; - var inputfield = $('.inputfield:focus'); // Only process up/down arrow if cursor is at the end of the line. if (code === 38 && event.shiftKey) { // Arrow up @@ -70,7 +60,12 @@ let history_plugin = (function () { // are we processing an up or down history event? if (history_entry !== null) { - // Doing a history navigation; replace the text in the input and move the cursor to the end of the new value + // 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(history_entry); event.preventDefault(); From ea8ed1b903f33a997ed2c64b47a6ee1892e8a61d Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Mon, 1 Apr 2019 23:43:12 -0400 Subject: [PATCH 13/16] fix assigning commands to hotbuttons for golden-layout --- .../static/webclient/js/plugins/hotbuttons.js | 113 +++++++++++------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js index ed00fcc636..50cbcbbdc0 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js +++ b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js @@ -24,6 +24,9 @@ plugin_handler.add('hotbuttons', (function () { var dependencies_met = true; // To start, assume either splithandler or goldenlayout plugin is enabled. + var num_buttons = 9; + var command_cache = new Array; + var hotButtonConfig = { content: [{ type: 'column', @@ -65,14 +68,10 @@ plugin_handler.add('hotbuttons', (function () { }] }; - var num_buttons = 9; - var command_cache = new Array(num_buttons); - var buttons = null; - // - // Add Buttons + // Add Buttons UI for SplitHandler var addButtonsUI = function () { - buttons = $( [ + var buttons = $( [ '
', '
', '
', @@ -90,32 +89,20 @@ plugin_handler.add('hotbuttons', (function () { '
', ].join("\n") ); - // Are we using splithandler? - if( plugins['splithandler'] ) { - // Add buttons in front of the existing #inputform - $('#input').prev().replaceWith(buttons); + // 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], - }); + Split(['#main','#buttons','#input'], { + sizes: [85,5,10], + direction: 'vertical', + gutterSize: 4, + minSize: [150,20,50], + }); - return true; + for( var n=0; n'); + + var len = command_cache.length; + for( var x=len; x < len + num_buttons; x++ ) { + command_cache.push("unassigned"); + + // initialize button command cache and onClick handler + button = $(''); + myLayout.registerComponent( "input", function (container, componentState) { + var inputfield = $(""); + var button = $(""); - $('
') + $("
") .append( button ) .append( inputfield ) .appendTo( container.getElement() ); - button.bind('click', function (evnt) { + button.bind("click", function (evnt) { // focus our textarea - $( $(evnt.target).siblings('.inputfield')[0] ).focus(); + $( $(evnt.target).siblings(".inputfield")[0] ).focus(); // fake a carriage return event - var e = $.Event('keydown'); + var e = $.Event("keydown"); e.which = 13; - $( $(evnt.target).siblings('.inputfield')[0] ).trigger(e); + $( $(evnt.target).siblings(".inputfield")[0] ).trigger(e); }); - container.on('tab', onInputCreate); + container.on("tab", onInputCreate); }); - myLayout.registerComponent( 'evennia', function (container, componentState) { - let div = $('
'); - initComponent(div, container, componentState, 'all', 'newlines'); - container.on('destroy', calculateUntaggedTypes); + myLayout.registerComponent( "evennia", function (container, componentState) { + let div = $("
"); + initComponent(div, container, componentState, "all", "newlines"); + container.on("destroy", calculateUntaggedTypes); }); } @@ -555,4 +555,5 @@ plugin_handler.add('goldenlayout', (function () { setConfig: function (newconfig) { config = newconfig }, addKnownType: function (newtype) { known_types.push(newtype) }, } -})()); +})(); +window.plugin_handler.add("goldenlayout", goldenlayout); diff --git a/evennia/web/webclient/static/webclient/js/plugins/history.js b/evennia/web/webclient/static/webclient/js/plugins/history.js index 8afcec44f3..0b02b947a9 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/history.js +++ b/evennia/web/webclient/static/webclient/js/plugins/history.js @@ -10,7 +10,7 @@ let history_plugin = (function () { var history = new Array(); var history_pos = 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 @@ -37,7 +37,7 @@ let history_plugin = (function () { history.shift(); // kill oldest entry } history[history.length-1] = input; - history[history.length] = ''; + history[history.length] = ""; } history_pos = 0; } @@ -62,11 +62,11 @@ let history_plugin = (function () { if (history_entry !== 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'); + var inputfield = $(".inputfield:focus"); if( inputfield.length < 1 ) { // pre-goldenlayout backwards compatibility - inputfield = $('#inputfield'); + inputfield = $("#inputfield"); } - inputfield.val(''); + inputfield.val(""); inputfield.blur().focus().val(history_entry); event.preventDefault(); return true; @@ -85,7 +85,7 @@ let history_plugin = (function () { // // Init function var init = function () { - console.log('History Plugin Initialized.'); + console.log("History Plugin Initialized."); } return { @@ -94,4 +94,4 @@ let history_plugin = (function () { onSend: onSend, } })() -plugin_handler.add('history', history_plugin); +plugin_handler.add("history", history_plugin); diff --git a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js index 50cbcbbdc0..383f6d0055 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js +++ b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js @@ -1,6 +1,6 @@ /* * - * 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. @@ -18,10 +18,10 @@ * * after the other plugin tags. * - * Run: evennia collectstatic (say 'yes' to the overwrite prompt) + * Run: evennia collectstatic (say "yes" to the overwrite prompt) * Start Evennia */ -plugin_handler.add('hotbuttons', (function () { +let hotbuttons = (function () { var dependencies_met = true; // To start, assume either splithandler or goldenlayout plugin is enabled. var num_buttons = 9; @@ -29,41 +29,41 @@ plugin_handler.add('hotbuttons', (function () { var hotButtonConfig = { content: [{ - type: 'column', + type: "column", content: [{ - type: 'row', + type: "row", content: [{ - type: 'column', + type: "column", content: [{ - type: 'component', - componentName: 'Main', + type: "component", + componentName: "Main", isClosable: false, - tooltip: 'Main - drag to desird position.', + tooltip: "Main - drag to desird position.", componentState: { - types: 'untagged', - update_method: 'newlines', + types: "untagged", + update_method: "newlines", }, }] }], }, { - type: 'component', - componentName: 'hotbuttons', - id: 'inputComponent', + type: "component", + componentName: "hotbuttons", + id: "inputComponent", height: 12, - tooltip: 'Input - The last input in the layout is always the default.', + tooltip: "Input - The last input in the layout is always the default.", }, { - type: 'component', - componentName: 'input', - id: 'inputComponent', + type: "component", + componentName: "input", + id: "inputComponent", height: 12, - tooltip: 'Input - The last input in the layout is always the default.', + tooltip: "Input - The last input in the layout is always the default.", }, { - type: 'component', - componentName: 'input', - id: 'inputComponent', + type: "component", + componentName: "input", + id: "inputComponent", height: 12, isClosable: false, - tooltip: 'Input - The last input in the layout is always the default.', + tooltip: "Input - The last input in the layout is always the default.", }] }] }; @@ -72,29 +72,29 @@ plugin_handler.add('hotbuttons', (function () { // Add Buttons UI for SplitHandler var addButtonsUI = function () { var buttons = $( [ - '
', - '
', - '
', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '
', - '
', - '
', + "
", + "
", + "
", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "
", + "
", + "
", ].join("\n") ); // Add buttons in front of the existing #inputform - $('#input').prev().replaceWith(buttons); + $("#input").prev().replaceWith(buttons); - Split(['#main','#buttons','#input'], { + Split(["#main","#buttons","#input"], { sizes: [85,5,10], - direction: 'vertical', + direction: "vertical", gutterSize: 4, minSize: [150,20,50], }); @@ -150,9 +150,9 @@ plugin_handler.add('hotbuttons', (function () { 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 - var input = $('.inputfield:last'); + var input = $(".inputfield:last"); if( input.length < 1 ) { - input = $('#inputfield'); + input = $("#inputfield"); } assignButton( e.data, input.val() ); Evennia.msg("webclient_options", [], { "HotButtons": command_cache }); @@ -171,20 +171,20 @@ plugin_handler.add('hotbuttons', (function () { // // Create and register the hotbuttons golden-layout component var buildComponent = function () { - var myLayout = plugins['goldenlayout'].getGL(); + var myLayout = plugins["goldenlayout"].getGL(); - myLayout.registerComponent( 'hotbuttons', function (container, componentState) { - console.log('hotbuttons'); + myLayout.registerComponent( "hotbuttons", function (container, componentState) { + console.log("hotbuttons"); // build the buttons - var div = $('
'); + var div = $("
"); var len = command_cache.length; for( var x=len; x < len + num_buttons; x++ ) { command_cache.push("unassigned"); // initialize button command cache and onClick handler - button = $('", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - "
", - "
", - "
", - ].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", + "
", + "
", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "
", + "
", + "
", + ].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 button = $("