Add golden-layout as an alternative to splithandler

This commit is contained in:
Brenden Tuck 2019-02-18 23:48:55 -05:00
parent 90171eaff7
commit 422a2bdc8a
4 changed files with 518 additions and 2 deletions

View file

@ -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;
}

View file

@ -8,7 +8,6 @@ let defaultin_plugin = (function () {
//
// handle the default <enter> 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();

View file

@ -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<vals.length; i++) {
let val = vals[i];
while( tmp.indexOf(val) > -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 = $('<div id="renamebox">');
renamebox.append('<input type="textbox" id="renameboxin" value="'+title+'">');
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 = $('<div id="typelist">');
let div = $('<div class="typelistsub">');
if( selected_types ) {
selected_types = selected_types.split(' ');
}
for (i=0; i<known_types.length;i++) {
let type = known_types[i];
let choice;
if( selected_types && selected_types.includes(type) ) {
choice = $('<label><input type="checkbox" value="'+type+'" checked="checked"/>'+type+'</label>');
} else {
choice = $('<label><input type="checkbox" value="'+type+'"/>'+type+'</label>');
}
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<checkboxes.length; i++ ) {
let box = checkboxes[i];
if( $(box).prop('checked') ) {
types.push( $(box).val() );
}
}
content.attr('types', types.join(' '));
myLayout.emit('stateChanged');
}
//
// Handle the typePopup
var typePopup = function (evnt) {
let typelist = document.getElementById('typelist');
if( !typelist ) {
onSelectTypesClicked(evnt);
} else {
commitCheckboxes(evnt);
calculate_untagged_types();
$('#typelist').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 = $('<div id="updatelist">');
let div = $('<div class="updatelistsub">');
let newlines = $('<label><input type="radio" name="update_method" value="newlines" '+nlchecked+'/>Newlines</label>');
let append = $('<label><input type="radio" name="update_method" value="append" '+apchecked+'/>Append</label>');
let replace = $('<label><input type="radio" name="update_method" value="replace" '+rpchecked+'/>Replace</label>');
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 = $('<span class="lm_title" style="font-size: 1em;width: 1.5em;">\u2B57</span>');
let typePopupControl = $('<span class="lm_title" style="font-size: 1.5em;width: 1em;">&#11163;</span>');
let updatePopupControl = $('<span class="lm_title" style="font-size: 1.5em;width: 1em;">&#11163;</span>');
let splitControl = $('<span class="lm_title" style="font-size: 2em;width: 1em;">+</span>');
// 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('<div class="out">' + txt + '</div>');
}
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
$('<div id="inputcontrol">')
.append( '<div class="inputwrap"><button id="inputsend" type="button"">&gt;</button></div>' )
.append( '<textarea id="inputfield" type="text" class="form-control"></textarea>' )
.appendTo( container.getElement() );
});
myLayout.registerComponent( 'evennia', function (container, componentState) {
let div = $('<div class="content"></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,
}
})());

View file

@ -17,7 +17,6 @@ JQuery available.
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link rel='stylesheet' type="text/css" media="screen" href={% static "webclient/css/webclient.css" %}>
<link rel="icon" type="image/x-icon" href="/static/website/images/evennia_logo.png" />
@ -64,8 +63,14 @@ JQuery available.
<script src={% static "webclient/js/evennia.js" %} language="javascript" type="text/javascript" charset="utf-8"/></script>
<!-- set up splits before loading the GUI -->
<!--
<script src="https://unpkg.com/split.js@1.5.9/dist/split.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
-->
<script type="text/javascript" src="https://golden-layout.com/files/latest/js/goldenlayout.min.js"></script>
<link type="text/css" rel="stylesheet" href="https://golden-layout.com/files/latest/css/goldenlayout-base.css" />
<link type="text/css" rel="stylesheet" href="https://golden-layout.com/files/latest/css/goldenlayout-dark-theme.css" />
<link type="text/css" rel="stylesheet" href={% static "webclient/css/goldenlayout.css" %} />
<!-- Load gui library -->
{% block guilib_import %}
@ -73,10 +78,13 @@ JQuery available.
<script src={% static "webclient/js/plugins/popups.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/options.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/history.js" %} language="javascript" type="text/javascript"></script>
<!--
<script src={% static "webclient/js/plugins/splithandler.js" %} language="javascript" type="text/javascript"></script>
-->
<script src={% static "webclient/js/plugins/default_in.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/oob.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/notifications.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/goldenlayout.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/default_out.js" %} language="javascript" type="text/javascript"></script>
{% endblock %}