mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 09:46:32 +01:00
Add golden-layout as an alternative to splithandler
This commit is contained in:
parent
90171eaff7
commit
422a2bdc8a
4 changed files with 518 additions and 2 deletions
106
evennia/web/webclient/static/webclient/css/goldenlayout.css
Normal file
106
evennia/web/webclient/static/webclient/css/goldenlayout.css
Normal 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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;">⮛</span>');
|
||||
let updatePopupControl = $('<span class="lm_title" style="font-size: 1.5em;width: 1em;">⮛</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"">></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,
|
||||
}
|
||||
})());
|
||||
|
|
@ -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 %}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue