From cdcd8a272f7cb93e2bf545e08d54e7a37aaaee71 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Sun, 11 Oct 2020 09:52:44 -0400 Subject: [PATCH] Make the goldenlayout layout work with Evennia options --- .../webclient/js/plugins/goldenlayout.js | 204 ++++++++++++------ .../static/webclient/js/plugins/hotbuttons.js | 84 ++------ .../static/webclient/js/plugins/options.js | 178 --------------- .../static/webclient/js/plugins/options2.js | 72 +++---- 4 files changed, 196 insertions(+), 342 deletions(-) delete mode 100644 evennia/web/webclient/static/webclient/js/plugins/options.js diff --git a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js index 36862272aa..4e21265f42 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js +++ b/evennia/web/webclient/static/webclient/js/plugins/goldenlayout.js @@ -275,8 +275,18 @@ let goldenlayout = (function () { component.container.extendState({ "types": types, "updateMethod": updateMethod }); }); + // update the layout options when the stat changes from our previous stored state. var state = JSON.stringify( myLayout.toConfig() ); - localStorage.setItem( "evenniaGoldenLayoutSavedState", state ); + + if( state !== window.options["webclientLayout"] ) { + localStorage.setItem( "evenniaGoldenLayoutSavedState", state ); + + // Also update the server-side options, if the connection is ready to go. + if( Evennia.isConnected() && myLayout.isInitialised ) { + window.options["webclientLayout"] = state; + Evennia.msg("webclient_options", [], window.options); + } + } } @@ -347,22 +357,6 @@ let goldenlayout = (function () { } - // - // - 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) { @@ -379,6 +373,51 @@ let goldenlayout = (function () { } + // + // + var registerComponents = function (myLayout) { + + // 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 input component + myLayout.registerComponent( "input", function (container, componentState) { + var promptfield = $("
"); + var formcontrol = $(""); + var button = $(""); + + var inputfield = $("
") + .append( button ) + .append( formcontrol ); + + $("
") + .append( promptfield ) + .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); + }); + + // register the generic "evennia" component + myLayout.registerComponent( "evennia", function (container, componentState) { + let div = $("
"); + initComponent(div, container, componentState, "all", "newlines"); + container.on("destroy", calculateUntaggedTypes); + }); + } + // // Public // @@ -403,6 +442,42 @@ let goldenlayout = (function () { } + // + // Add new HTML message to an existing Div pane, while + // honoring the pane's updateMethod and scroll state, etc. + // + var addMessageToPaneDiv = function (textDiv, message) { + let atBottom = false; + let updateMethod = textDiv.attr("updateMethod"); + + if ( updateMethod === "replace" ) { + textDiv.html(message); + } else if ( updateMethod === "append" ) { + textDiv.append(message); + } else { // line feed + textDiv.append("
" + message + "
"); + } + + // Calculate the scrollback state. + // + // This check helps us avoid scrolling to the bottom when someone is + // manually scrolled back, trying to read their backlog. + // Auto-scrolling would force them to re-scroll to their previous scroll position. + // Which, on fast updating games, destroys the utility of scrolling entirely. + // + //if( textDiv.scrollTop === (textDiv.scrollHeight - textDiv.offsetHeight) ) { + atBottom = true; + //} + + // if we are at the bottom of the window already, scroll to display the new content + if( atBottom ) { + let scrollHeight = textDiv.prop("scrollHeight"); + let clientHeight = textDiv.prop("clientHeight"); + textDiv.scrollTop( scrollHeight - clientHeight ); + } + } + + // // returns an array of pane divs that the given message should be sent to // @@ -445,6 +520,49 @@ let goldenlayout = (function () { } + // + // + var onGotOptions = function (args, kwargs) { + // Reset the UI if the JSON layout sent from the server doesn't match the client's current JSON + if( ("webclientLayout" in kwargs) && (kwargs["webclientLayout"] !== window.options["webclientLayout"]) ) { + var mainsub = document.getElementById("main-sub"); + + // rebuild the original HTML stacking + var messageDiv = $("#messagewindow").detach(); + messageDiv.prependTo( mainsub ); + + // out with the old + myLayout.destroy(); + + // in with the new + myLayout = new GoldenLayout( JSON.parse( kwargs["webclientLayout"] ), mainsub ); + + // re-register our main, input and generic evennia components. + registerComponents( myLayout ); + + // call all other plugins to give them a chance to registerComponents. + for( let plugin in window.plugins ) { + if( "onLayoutChange" in window.plugins[plugin] ) { + window.plugins[plugin].onLayoutChange(); + } + } + + // finish the setup and actually start GoldenLayout + myLayout.init(); + + // work out which types are untagged based on our pre-configured layout + calculateUntaggedTypes(); + + // Set the Event handler for when the client window changes size + $(window).bind("resize", scrollAll); + + // Set Save State callback + myLayout.on( "stateChanged", onStateChanged ); + + return true; + } + } + // // var onText = function (args, kwargs) { @@ -453,11 +571,9 @@ let goldenlayout = (function () { var msgHandled = false; divs.forEach( function (div) { - let updateMethod = div.attr("updateMethod"); let txt = args[0]; - // yes, so add this text message to the target div - routeMsg( div, txt, updateMethod ); + addMessageToPaneDiv( div, txt ); msgHandled = true; }); @@ -483,14 +599,16 @@ let goldenlayout = (function () { // - // required Init me + // required Init 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 + // Overwrite the global-variable configuration from + // webclient/js/plugins/goldenlayout_default_config.js + // with the version from localstorage window.goldenlayout_config = JSON.parse( savedState ); } @@ -499,56 +617,20 @@ let goldenlayout = (function () { $("#prompt").remove(); // remove the HTML-defined prompt div $("#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 promptfield = $("
"); - var formcontrol = $(""); - var button = $(""); - - var inputfield = $("
") - .append( button ) - .append( formcontrol ); - - $("
") - .append( promptfield ) - .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); - }); + registerComponents( myLayout ); } - return { init: init, postInit: postInit, onKeydown: onKeydown, + onGotOptions: onGotOptions, onText: onText, getGL: function () { return myLayout; }, addKnownType: addKnownType, onTabCreate: onTabCreate, routeMessage: routeMessage, + addMessageToPaneDiv: addMessageToPaneDiv, } }()); window.plugin_handler.add("goldenlayout", goldenlayout); diff --git a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js index 023db7eb5e..6b7af9b2af 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js +++ b/evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js @@ -17,16 +17,16 @@ * 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: + * Edit mygame/web/template_overrides/webclient/base.html and before the goldenlayout.js plugin, add: * - * before the goldenlayout.js plugin tags or after the splithandler.js plugin tags * - * If you are using goldenlayout.js, uncomment the hotbuttons component in goldenlayout_default_config.js + * Then 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 + * REQUIRES: goldenlayout.js */ let hotbuttons = (function () { var dependenciesMet = false; @@ -95,47 +95,22 @@ let hotbuttons = (function () { } + // Public + // - // 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; nDon\'t echo prompts to the main text area', - '
', - '', - '
', - '
', - '', - '
', - '', - '
', - ].join("\n"); - - // Create a new options Dialog - plugins['popups'].createDialog( 'optionsdialog', 'Options', content ); - } - - // - // addHelpUI - var addHelpUI = function () { - // Create a new Help Dialog - plugins['popups'].createDialog( 'helpdialog', 'Help', "" ); - } - - // addToolbarButton - var addToolbarButton = function () { - var optionsbutton = $( [ - '', - ].join("") ); - $('#toolbar').append( optionsbutton ); - } - - // - // Opens the options dialog - var doOpenOptions = function () { - if (!Evennia.isConnected()) { - alert("You need to be connected."); - return; - } - - plugins['popups'].togglePopup("#optionsdialog"); - } - - // - // When the user changes a setting from the interface - var onOptionCheckboxChanged = function () { - var name = $(this).data("setting"); - var value = this.checked; - - var changedoptions = {}; - changedoptions[name] = value; - Evennia.msg("webclient_options", [], changedoptions); - - options[name] = value; - } - - // Public functions - - // - // onKeydown check for 'ESC' key. - var onKeydown = function (event) { - var code = event.which; - - if (code === 27) { // Escape key - if ($('#helpdialog').is(':visible')) { - plugins['popups'].closePopup("#helpdialog"); - return true; - } - if ($('#optionsdialog').is(':visible')) { - plugins['popups'].closePopup("#optionsdialog"); - return true; - } - } - return false; - } - - // - // Called when options settings are sent from server - var onGotOptions = function (args, kwargs) { - options = kwargs; - - $.each(kwargs, function(key, value) { - var elem = $("[data-setting='" + key + "']"); - if (elem.length === 0) { - console.log("Could not find option: " + key); - console.log(args); - console.log(kwargs); - } else { - elem.prop('checked', value); - }; - }); - } - - // - // Called when the user logged in - var onLoggedIn = function (args, kwargs) { - $('#optionsbutton').removeClass('hidden'); - Evennia.msg("webclient_options", [], {}); - } - - // - // Display a "prompt" command from the server - var onPrompt = function (args, kwargs) { - // also display the prompt in the output window if gagging is disabled - if (("gagprompt" in options) && (!options["gagprompt"])) { - plugin_handler.onText(args, kwargs); - } - - // don't claim this Prompt as completed. - return false; - } - - // - // Make sure to close any dialogs on connection lost - var onConnectionClose = function () { - $('#optionsbutton').addClass('hidden'); - plugins['popups'].closePopup("#optionsdialog"); - plugins['popups'].closePopup("#helpdialog"); - } - - // - // Make sure to close any dialogs on connection lost - var onText = function (args, kwargs) { - // is helppopup set? and if so, does this Text have type 'help'? - if ('helppopup' in options && options['helppopup'] ) { - if (kwargs && ('type' in kwargs) && (kwargs['type'] == 'help') ) { - $('#helpdialogcontent').prepend('
'+ args + '
'); - plugins['popups'].togglePopup("#helpdialog"); - return true; - } - } - - return false; - } - - // - // Register and init plugin - var init = function () { - // Add GUI components - addOptionsUI(); - addHelpUI(); - - // Add Options toolbar button. - addToolbarButton(); - - // Pressing the settings button - $("#optionsbutton").bind("click", doOpenOptions); - - // Checking a checkbox in the settings dialog - $("[data-setting]").bind("change", onOptionCheckboxChanged); - - console.log('Options Plugin Initialized.'); - } - - return { - init: init, - onKeydown: onKeydown, - onLoggedIn: onLoggedIn, - onGotOptions: onGotOptions, - onPrompt: onPrompt, - onConnectionClose: onConnectionClose, - onText: onText, - } -})() -plugin_handler.add('options', options_plugin); diff --git a/evennia/web/webclient/static/webclient/js/plugins/options2.js b/evennia/web/webclient/static/webclient/js/plugins/options2.js index 2e5ff4a410..14063c7c85 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/options2.js +++ b/evennia/web/webclient/static/webclient/js/plugins/options2.js @@ -4,7 +4,7 @@ */ let options2 = (function () { - var options_container = null ; + var optionsContainer = null ; // // When the user changes a setting from the interface @@ -51,35 +51,6 @@ let options2 = (function () { } - // - // Create and register the "options" golden-layout component - var createOptionsComponent = function () { - var myLayout = window.plugins["goldenlayout"].getGL(); - - myLayout.registerComponent( "options", function (container, componentState) { - var plugins = window.plugins; - options_container = container.getElement(); - - // build the buttons - var div = $("
"); - - for( let plugin in plugins ) { - if( "onOptionsUI" in plugins[plugin] ) { - var card = $("
"); - var body = $("
"); - - plugins[plugin].onOptionsUI( body ); - - card.append(body); - card.appendTo( div ); - } - } - - div.appendTo( options_container ); - }); - } - - // handler for the "Options" button var onOpenCloseOptions = function () { var optionsComponent = { @@ -92,7 +63,7 @@ let options2 = (function () { // Create a new GoldenLayout tab filled with the optionsComponent above var myLayout = window.plugins["goldenlayout"].getGL(); - if( ! options_container ) { + if( ! optionsContainer ) { // open new optionsComponent var main = myLayout.root.getItemsByType("stack")[0].getActiveContentItem(); @@ -102,16 +73,16 @@ let options2 = (function () { .closeElement .off("click") .click( function () { - options_container = null; + optionsContainer = null; tab.contentItem.remove(); }); - options_container = tab.contentItem; + optionsContainer = tab.contentItem; } }); main.parent.addChild( optionsComponent ); } else { - options_container.remove(); - options_container = null; + optionsContainer.remove(); + optionsContainer = null; } } @@ -132,6 +103,34 @@ let options2 = (function () { }); } + // + // Create and register the "options" golden-layout component + var onLayoutChanged = function () { + var myLayout = window.plugins["goldenlayout"].getGL(); + + myLayout.registerComponent( "options", function (container, componentState) { + var plugins = window.plugins; + optionsContainer = container.getElement(); + + // build the buttons + var div = $("
"); + + for( let plugin in plugins ) { + if( "onOptionsUI" in plugins[plugin] ) { + var card = $("
"); + var body = $("
"); + + plugins[plugin].onOptionsUI( body ); + + card.append(body); + card.appendTo( div ); + } + } + + div.appendTo( optionsContainer ); + }); + } + // // Called when the user logged in var onLoggedIn = function (args, kwargs) { @@ -165,7 +164,7 @@ let options2 = (function () { var postInit = function() { // Are we using GoldenLayout? if( window.plugins["goldenlayout"] ) { - createOptionsComponent(); + onLayoutChanged(); $("#optionsbutton").bind("click", onOpenCloseOptions); } @@ -176,6 +175,7 @@ let options2 = (function () { init: init, postInit: postInit, onGotOptions: onGotOptions, + onLayoutChanged: onLayoutChanged, onLoggedIn: onLoggedIn, onOptionsUI: onOptionsUI, onPrompt: onPrompt,