diff --git a/evennia/web/webclient/static/webclient/css/webclient.css b/evennia/web/webclient/static/webclient/css/webclient.css index 1c94a1f9fd..94344386a1 100644 --- a/evennia/web/webclient/static/webclient/css/webclient.css +++ b/evennia/web/webclient/static/webclient/css/webclient.css @@ -8,10 +8,11 @@ --- */ /* Overall element look */ -html, body, #clientwrapper { height: 100% } +html, body { + height: 100%; + width: 100%; +} body { - margin: 0; - padding: 0; background: #000; color: #ccc; font-size: .9em; @@ -19,6 +20,12 @@ body { line-height: 1.6em; overflow: hidden; } +@media screen and (max-width: 480px) { + body { + font-size: .5rem; + line-height: .7rem; + } +} a:link, a:visited { color: inherit; } @@ -74,93 +81,75 @@ div {margin:0px;} } /* Style specific classes corresponding to formatted, narative text. */ - +.wrapper { + height: 100%; +} /* Container surrounding entire client */ -#wrapper { - position: relative; - height: 100% +#clientwrapper { + height: 100%; } /* Main scrolling message area */ + #messagewindow { - position: absolute; - overflow: auto; - padding: 1em; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - top: 0; - left: 0; - right: 0; - bottom: 70px; + overflow-y: auto; + overflow-x: hidden; + overflow-wrap: break-word; } -/* Input area containing input field and button */ -#inputform { - position: absolute; - width: 100%; - padding: 0; - bottom: 0; - margin: 0; - padding-bottom: 10px; - border-top: 1px solid #555; -} - -#inputcontrol { - width: 100%; - padding: 0; +#messagewindow { + overflow-y: auto; + overflow-x: hidden; + overflow-wrap: break-word; } /* Input field */ -#inputfield, #inputsend, #inputsizer { - display: block; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - height: 50px; +#inputfield, #inputsizer { + height: 100%; background: #000; color: #fff; - padding: 0 .45em; - font-size: 1.1em; + padding: 0 .45rem; + font-size: 1.1rem; font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace; -} - -#inputfield, #inputsizer { - float: left; - width: 95%; - border: 0; resize: none; - line-height: normal; +} +#inputsend { + height: 100%; +} +#inputcontrol { + height: 100%; } #inputfield:focus { - outline: 0; -} - -#inputsizer { - margin-left: -9999px; -} - -/* Input 'send' button */ -#inputsend { - float: right; - width: 3%; - max-width: 25px; - margin-right: 10px; - border: 0; - background: #555; } /* prompt area above input field */ -#prompt { - margin-top: 10px; - padding: 0 .45em; +.prompt { + max-height: 3rem; +} + +.splitbutton { + position: absolute; + right: 1%; + top: 1%; + z-index: 1; + width: 2rem; + height: 2rem; + font-size: 2rem; + color: #a6a6a6; + background-color: transparent; + border: 0px; +} + +.splitbutton:hover { + color: white; + cursor: pointer; } #optionsbutton { - width: 40px; - font-size: 20px; + width: 2rem; + font-size: 2rem; color: #a6a6a6; background-color: transparent; border: 0px; @@ -173,8 +162,8 @@ div {margin:0px;} #toolbar { position: fixed; - top: 0; - right: 5px; + top: .5rem; + right: .5rem; z-index: 1; } @@ -248,6 +237,48 @@ div {margin:0px;} text-decoration: none; cursor: pointer; } +.gutter.gutter-vertical { + cursor: row-resize; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=') +} + +.gutter.gutter-horizontal { + cursor: col-resize; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==') +} + +.split { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + overflow-y: auto; + overflow-x: hidden; +} + +.content { + border: 1px solid #C0C0C0; + box-shadow: inset 0 1px 2px #e4e4e4; + background-color: black; + padding: 1rem; +} +@media screen and (max-width: 480px) { + .content { + padding: .5rem; + } +} + +.gutter { + background-color: grey; + + background-repeat: no-repeat; + background-position: 50%; +} + +.split.split-horizontal, .gutter.gutter-horizontal { + height: 100%; + float: left; +} /* XTERM256 colors */ diff --git a/evennia/web/webclient/static/webclient/js/splithandler.js b/evennia/web/webclient/static/webclient/js/splithandler.js new file mode 100644 index 0000000000..56890009e5 --- /dev/null +++ b/evennia/web/webclient/static/webclient/js/splithandler.js @@ -0,0 +1,77 @@ +// Use split.js to create a basic ui +var SplitHandler = (function () { + var num_splits = 0; + var split_panes = {}; + + var set_pane_types = function(splitpane, types) { + split_panes[splitpane]['types'] = types; + } + + var dynamic_split = function(splitpane, direction, update_method1, update_method2) { + var first = ++num_splits; + var second = ++num_splits; + + var first_div = $( '
' ) + var first_sub = $( '' ) + var second_div = $( '' ) + var second_sub = $( '' ) + + // check to see if this pane contains the primary message window. + contents = $('#'+splitpane).contents(); + if( contents ) { + // it does, so move it to the first new div (TODO -- selectable between first/second?) + contents.appendTo(first_sub); + } + + first_div.append( first_sub ); + second_div.append( second_sub ); + + // update the split_panes array to remove this split + delete( split_panes[splitpane] ); + + // now vaporize the current split_N-sub placeholder and create two new panes. + $('#'+splitpane).parent().append(first_div); + $('#'+splitpane).parent().append(second_div); + $('#'+splitpane).remove(); + + // And split + Split(['#split_'+first,'#split_'+second], { + direction: direction, + sizes: [50,50], + gutterSize: 4, + minSize: [50,50], + }); + + // store our new splits for future splits/uses by the main UI. + split_panes['split_'+first +'-sub'] = { 'types': [], 'update_method': update_method1 }; + split_panes['split_'+second+'-sub'] = { 'types': [], 'update_method': update_method2 }; + } + + + var init = function(settings) { + //change Mustache tags to ruby-style (Django gets mad otherwise) + var customTags = [ '<%', '%>' ]; + Mustache.tags = customTags; + + var input_template = $('#input-template').html(); + Mustache.parse(input_template); + + Split(['#main','#input'], { + direction: 'vertical', + sizes: [90,10], + gutterSize: 4, + minSize: [50,50], + }); + + var input_render = Mustache.render(input_template); + $('[data-role-input]').html(input_render); + console.log("SplitHandler initialized"); + } + + return { + init: init, + set_pane_types: set_pane_types, + dynamic_split: dynamic_split, + split_panes: split_panes, + } +})(); diff --git a/evennia/web/webclient/static/webclient/js/webclient_gui.js b/evennia/web/webclient/static/webclient/js/webclient_gui.js index 57d9b0b7c0..b4e5168769 100644 --- a/evennia/web/webclient/static/webclient/js/webclient_gui.js +++ b/evennia/web/webclient/static/webclient/js/webclient_gui.js @@ -17,6 +17,10 @@ var options = {}; + +var known_types = new Array(); + known_types.push('help'); + // // GUI Elements // @@ -106,6 +110,7 @@ function togglePopup(dialogname, content) { // Grab text from inputline and send to Evennia function doSendText() { + console.log("sending text"); if (!Evennia.isConnected()) { var reconnect = confirm("Not currently connected. Reconnect?"); if (reconnect) { @@ -158,6 +163,10 @@ function onKeydown (event) { var code = event.which; var history_entry = null; var inputfield = $("#inputfield"); + if (code === 9) { + return; + } + inputfield.focus(); if (code === 13) { // Enter key sends text @@ -205,64 +214,68 @@ function onKeyPress (event) { } var resizeInputField = function () { - var min_height = 50; - var max_height = 300; - var prev_text_len = 0; + return function() { + var wrapper = $("#inputform") + var input = $("#inputcontrol") + var prompt = $("#prompt") - // Check to see if we should change the height of the input area - return function () { - var inputfield = $("#inputfield"); - var scrollh = inputfield.prop("scrollHeight"); - var clienth = inputfield.prop("clientHeight"); - var newh = 0; - var curr_text_len = inputfield.val().length; - - if (scrollh > clienth && scrollh <= max_height) { - // Need to make it bigger - newh = scrollh; - } - else if (curr_text_len < prev_text_len) { - // There is less text in the field; try to make it smaller - // To avoid repaints, we draw the text in an offscreen element and - // determine its dimensions. - var sizer = $('#inputsizer') - .css("width", inputfield.prop("clientWidth")) - .text(inputfield.val()); - newh = sizer.prop("scrollHeight"); - } - - if (newh != 0) { - newh = Math.min(newh, max_height); - if (clienth != newh) { - inputfield.css("height", newh + "px"); - doWindowResize(); - } - } - prev_text_len = curr_text_len; + input.height(wrapper.height() - (input.offset().top - wrapper.offset().top)); } }(); // Handle resizing of client function doWindowResize() { - var formh = $('#inputform').outerHeight(true); - var message_scrollh = $("#messagewindow").prop("scrollHeight"); - $("#messagewindow") - .css({"bottom": formh}) // leave space for the input form - .scrollTop(message_scrollh); // keep the output window scrolled to the bottom + resizeInputField(); + var resizable = $("[data-update-append]"); + var parents = resizable.closest(".split") + parents.animate({ + scrollTop: parents.prop("scrollHeight") + }, 0); } // Handle text coming from the server function onText(args, kwargs) { - // append message to previous ones, then scroll so latest is at - // the bottom. Send 'cls' kwarg to modify the output class. - var renderto = "main"; - if (kwargs["type"] == "help") { - if (("helppopup" in options) && (options["helppopup"])) { - renderto = "#helpdialog"; + var use_default_pane = true; + + if ( kwargs && 'type' in kwargs ) { + var 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); + } + + if ( msgtype == 'help' ) { + if (("helppopup" in options) && (options["helppopup"])) { + openPopup("#helpdialog", args[0]); + return; + } + // fall through to the default output + + } else { + // pass this message to each pane that has this msgtype mapped + if( SplitHandler ) { + for ( var key in SplitHandler.split_panes) { + var pane = SplitHandler.split_panes[key]; + console.log(pane); + // is this message type mapped to this pane? + if ( (pane['types'].length > 0) && pane['types'].includes(msgtype) ) { + // yes, so append/replace this pane's inner div with this message + if ( pane['update_method'] == 'replace' ) { + $('#'+key).html(args[0]) + } else { + $('#'+key).append(args[0]).animate({ scrollTop: document.getElementById("#"+key).scrollHeight }, 0); + } + // record sending this message to a pane, no need to update the default div + use_default_pane = false; + } + } + } } } - if (renderto == "main") { + // append message to default pane, then scroll so latest is at the bottom. + if(use_default_pane) { var mwin = $("#messagewindow"); var cls = kwargs == null ? 'out' : kwargs['cls']; mwin.append("This error could also be due to not being able to access the online jQuery javascript library.
- - -