Merge branch 'develop-plugin-based-webclient' of https://github.com/friarzen/evennia into friarzen-develop-plugin-based-webclient

This commit is contained in:
Griatch 2018-09-29 18:32:06 +02:00
commit c6fa04407c
14 changed files with 1230 additions and 759 deletions

View file

@ -105,6 +105,13 @@ div {margin:0px;}
}
/* Input field */
#input {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
}
#inputfield, #inputsizer {
height: 100%;
background: #000;

View file

@ -0,0 +1,44 @@
/*
*
* Evennia Webclient default 'send-text-on-enter-key' IO plugin
*
*/
let defaultin_plugin = (function () {
//
// handle the default <enter> key triggering onSend()
var onKeydown = function (event) {
if ( (event.which === 13) && (!event.shiftKey) ) { // Enter Key without shift
var inputfield = $("#inputfield");
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();
}
return true;
}
//
// Mandatory plugin init function
var init = function () {
// Handle pressing the send button
$("#inputsend")
.bind("click", function (event) {
var e = $.Event( "keydown" );
e.which = 13;
$('#inputfield').trigger(e);
});
console.log('DefaultIn initialized');
}
return {
init: init,
onKeydown: onKeydown,
}
})();
plugin_handler.add('defaultin', defaultin_plugin);

View file

@ -0,0 +1,60 @@
/*
*
* Evennia Webclient default outputs plugin
*
*/
let defaultout_plugin = (function () {
//
// By default add all unclaimed onText messages to the #messagewindow <div> and scroll
var onText = function (args, kwargs) {
// append message to default pane, then scroll so latest is at the bottom.
var mwin = $("#messagewindow");
var cls = kwargs == null ? 'out' : kwargs['cls'];
mwin.append("<div class='" + cls + "'>" + args[0] + "</div>");
var scrollHeight = mwin.parent().parent().prop("scrollHeight");
mwin.parent().parent().animate({ scrollTop: scrollHeight }, 0);
return true;
}
//
// By default just show the prompt.
var onPrompt = function (args, kwargs) {
// show prompt
$('#prompt')
.addClass("out")
.html(args[0]);
return true;
}
//
// By default just show an error for the Unhandled Event.
var onUnknownCmd = function (args, kwargs) {
var mwin = $("#messagewindow");
mwin.append(
"<div class='msg err'>"
+ "Error or Unhandled event:<br>"
+ cmdname + ", "
+ JSON.stringify(args) + ", "
+ JSON.stringify(kwargs) + "<p></div>");
mwin.scrollTop(mwin[0].scrollHeight);
return true;
}
//
// Mandatory plugin init function
var init = function () {
console.log('DefaultOut initialized');
}
return {
init: init,
onText: onText,
onPrompt: onPrompt,
onUnknownCmd: onUnknownCmd,
}
})();
plugin_handler.add('defaultout', defaultout_plugin);

View file

@ -0,0 +1,17 @@
/*
*
* Evennia Webclient default unload plugin
*
*/
let unload_plugin = (function () {
let onBeforeUnload = function () {
return "You are about to leave the game. Please confirm.";
}
return {
init: function () {},
onBeforeUnload: onBeforeUnload,
}
})();
plugin_handler.add('unload', unload_plugin);

View file

@ -0,0 +1,116 @@
/*
*
* Evennia Webclient Command History plugin
*
*/
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.
//
// 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];
}
//
// 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 (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
}
history[history.length-1] = input;
history[history.length] = '';
}
// reset the position to the last history entry
history_pos = 0;
}
//
// Go to the last history line
var end = function () {
// move to the end of the history stack
history_pos = 0;
return history[history.length -1];
}
//
// 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
//
// Handle up arrow and down arrow events.
var onKeydown = function(event) {
var code = event.which;
var history_entry = null;
var inputfield = $("#inputfield");
if (inputfield[0].selectionStart == inputfield.val().length) {
// Only process up/down arrow if cursor is at the end of the line.
if (code === 38) { // Arrow up
history_entry = back();
}
else if (code === 40) { // Arrow down
history_entry = fwd();
}
}
if (history_entry !== null) {
// Doing a history navigation; replace the text in the input.
inputfield.val(history_entry);
}
else {
// Save the current contents of the input to the history scratch area.
setTimeout(function () {
// Need to wait until after the key-up to capture the value.
scratch(inputfield.val());
end();
}, 0);
}
return false;
}
//
// Listen for onSend lines to add to history
var onSend = function (line) {
add(line);
}
//
// Init function
var init = function () {
console.log('History Plugin Initialized.');
}
return {
init: init,
onKeydown: onKeydown,
onSend: onSend,
}
})()
plugin_handler.add('history', history_plugin);

View file

@ -0,0 +1,88 @@
/*
*
* Desktop Notifications Plugin
*
*/
let notifications_plugin = (function () {
// Notifications
var unread = 0;
var originalTitle = document.title;
var focused = true;
var favico;
var onBlur = function (e) {
focused = false;
}
//
// Notifications for unfocused window
var onFocus = function (e) {
focused = true;
document.title = originalTitle;
unread = 0;
favico.badge(0);
}
//
// on receiving new text from the server, if we are not focused, send a notification to the desktop
var onText = function (args, kwargs) {
if(!focused) {
// Changes unfocused browser tab title to number of unread messages
unread++;
favico.badge(unread);
document.title = "(" + unread + ") " + originalTitle;
if ("Notification" in window) {
if (("notification_popup" in options) && (options["notification_popup"])) {
// There is a Promise-based API for this, but its not supported
// in Safari and some older browsers:
// https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission#Browser_compatibility
Notification.requestPermission(function(result) {
if(result === "granted") {
var title = originalTitle === "" ? "Evennia" : originalTitle;
var options = {
body: text.replace(/(<([^>]+)>)/ig,""),
icon: "/static/website/images/evennia_logo.png"
}
var n = new Notification(title, options);
n.onclick = function(e) {
e.preventDefault();
window.focus();
this.close();
}
}
});
}
if (("notification_sound" in options) && (options["notification_sound"])) {
var audio = new Audio("/static/webclient/media/notification.wav");
audio.play();
}
}
}
return false;
}
//
// required init function
var init = function () {
if ("Notification" in window) {
Notification.requestPermission();
}
favico = new Favico({
animation: 'none'
});
$(window).blur(onBlur);
$(window).focus(onFocus);
console.log('Notifications Plugin Initialized.');
}
return {
init: init,
onText: onText,
}
})()
plugin_handler.add('notifications', notifications_plugin);

View file

@ -0,0 +1,35 @@
/*
*
* OOB Plugin
* enables '##send { "command", [ args ], { kwargs } }' as a way to inject OOB instructions
*
*/
let oob_plugin = (function () {
//
// Check outgoing text for handtyped/injected JSON OOB instruction
var onSend = function (line) {
if (line.length > 7 && line.substr(0, 7) == "##send ") {
// send a specific oob instruction ["cmdname",[args],{kwargs}]
line = line.slice(7);
var cmdarr = JSON.parse(line);
var cmdname = cmdarr[0];
var args = cmdarr[1];
var kwargs = cmdarr[2];
log(cmdname, args, kwargs);
return (cmdname, args, kwargs);
}
}
//
// init function
var init = function () {
console.log('OOB Plugin Initialized.');
}
return {
init: init,
onSend: onSend,
}
})()
plugin_handler.add('oob', oob_plugin);

View file

@ -0,0 +1,162 @@
/*
*
* Evennia Options GUI plugin
*
* This code deals with all of the UI and events related to Options.
*
*/
let options_plugin = (function () {
//
// addOptionsUI
var addOptionsUI = function () {
var content = [ // TODO dynamically create this based on the options{} hash
'<h3>Output display</h3>',
'<label><input type="checkbox" data-setting="gagprompt" value="value">Don\'t echo prompts to the main text area</label>',
'<br />',
'<label><input type="checkbox" data-setting="helppopup" value="value">Open help in popup window</label>',
'<br />',
'<hr />',
'<h3>Notifications</h3>',
'<label><input type="checkbox" data-setting="notification_popup" value="value">Popup notification</label>',
'<br />',
'<label><input type="checkbox" data-setting="notification_sound" value="value">Play a sound</label>',
'<br />',
].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 = $( [
'<button id="optionsbutton" type="button" aria-haspopup="true" aria-owns="#optionsdialog">',
'&#x2699;',
'<span class="sr-only sr-only-focusable">Settings</span>',
'</button>',
].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");
} else {
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");
}
//
// 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,
}
})()
plugin_handler.add('options', options_plugin);

View file

@ -0,0 +1,101 @@
/*
* Popups GUI functions plugin
*/
let popups_plugin = (function () {
//
// openPopup
var openPopup = function (dialogname, content) {
var dialog = $(dialogname);
if (!dialog.length) {
console.log("Dialog " + renderto + " not found.");
return;
}
if (content) {
var contentel = dialog.find(".dialogcontent");
contentel.html(content);
}
dialog.show();
}
//
// closePopup
var closePopup = function (dialogname) {
var dialog = $(dialogname);
dialog.hide();
}
//
// togglePopup
var togglePopup = function (dialogname, content) {
var dialog = $(dialogname);
if (dialog.css('display') == 'none') {
openPopup(dialogname, content);
} else {
closePopup(dialogname);
}
}
//
// createDialog
var createDialog = function (dialogid, dialogtitle, content) {
var dialog = $( [
'<div id="'+ dialogid +'" class="dialog">',
' <div class="dialogtitle">'+ dialogtitle +'<span class="dialogclose">&times;</span></div>',
' <div class="dialogcontentparent">',
' <div id="'+ dialogid +'content" class="dialogcontent">'+ content +'</div>',
' </div>',
' </div>',
'</div>',
].join("\n") );
$('body').append( dialog );
$('#'+ dialogid +' .dialogclose').bind('click', function (event) { $('#'+dialogid).hide(); });
}
//
// User clicked on a dialog to drag it
var doStartDragDialog = function (event) {
var dialog = $(event.target).closest(".dialog");
dialog.css('cursor', 'move');
var position = dialog.offset();
var diffx = event.pageX;
var diffy = event.pageY;
var drag = function(event) {
var y = position.top + event.pageY - diffy;
var x = position.left + event.pageX - diffx;
dialog.offset({top: y, left: x});
};
var undrag = function() {
$(document).unbind("mousemove", drag);
$(document).unbind("mouseup", undrag);
dialog.css('cursor', '');
}
$(document).bind("mousemove", drag);
$(document).bind("mouseup", undrag);
}
//
// required plugin function
var init = function () {
// Makes dialogs draggable
$(".dialogtitle").bind("mousedown", doStartDragDialog);
console.log('Popups Plugin Initialized.');
}
return {
init: init,
openPopup: openPopup,
closePopup: closePopup,
togglePopup: togglePopup,
createDialog: createDialog,
}
})()
plugin_handler.add('popups', popups_plugin);

View file

@ -0,0 +1,368 @@
/*
*
* Plugin to use split.js to create a basic windowed ui
*
*/
let splithandler_plugin = (function () {
var num_splits = 0;
var split_panes = {};
var backout_list = new Array;
var known_types = new Array();
// Exported Functions
//
// function to assign "Text types to catch" to a pane
var set_pane_types = function (splitpane, types) {
split_panes[splitpane]['types'] = types;
}
//
// Add buttons to the Evennia webcilent toolbar
function addToolbarButtons () {
var toolbar = $('#toolbar');
toolbar.append( $('<button id="splitbutton" type="button">&#x21f9;</button>') );
toolbar.append( $('<button id="panebutton" type="button">&#x2699;</button>') );
toolbar.append( $('<button id="undobutton" type="button">&#x21B6;</button>') );
$('#undobutton').hide();
}
function addSplitDialog () {
plugins['popups'].createDialog('splitdialog', 'Split Dialog', '');
}
function addPaneDialog () {
plugins['popups'].createDialog('panedialog', 'Pane Dialog', '');
}
//
// Handle resizing the InputField after a client resize event so that the splits dont get too big.
function resizeInputField () {
var wrapper = $("#inputform")
var input = $("#inputcontrol")
var prompt = $("#prompt")
input.height( wrapper.height() - (input.offset().top - wrapper.offset().top) );
}
//
// Handle resizing of client
function doWindowResize() {
var resizable = $("[data-update-append]");
var parents = resizable.closest(".split");
resizeInputField();
parents.animate({
scrollTop: parents.prop("scrollHeight")
}, 0);
}
//
// create a new UI split
var dynamic_split = function (splitpane, direction, pane_name1, pane_name2, update_method1, update_method2, sizes) {
// find the sub-div of the pane we are being asked to split
splitpanesub = splitpane + '-sub';
// create the new div stack to replace the sub-div with.
var first_div = $( '<div id="'+pane_name1+'" class="split split-'+direction+'" />' )
var first_sub = $( '<div id="'+pane_name1+'-sub" class="split-sub" />' )
var second_div = $( '<div id="'+pane_name2+'" class="split split-'+direction+'" />' )
var second_sub = $( '<div id="'+pane_name2+'-sub" class="split-sub" />' )
// check to see if this sub-pane contains anything
contents = $('#'+splitpanesub).contents();
if( contents ) {
// it does, so move it to the first new div-sub (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 pane name, but store it for the backout stack
var backout_settings = split_panes[splitpane];
delete( split_panes[splitpane] );
// now vaporize the current split_N-sub placeholder and create two new panes.
$('#'+splitpane).append(first_div);
$('#'+splitpane).append(second_div);
$('#'+splitpane+'-sub').remove();
// And split
Split(['#'+pane_name1,'#'+pane_name2], {
direction: direction,
sizes: sizes,
gutterSize: 4,
minSize: [50,50],
});
// store our new split sub-divs for future splits/uses by the main UI.
split_panes[pane_name1] = { 'types': [], 'update_method': update_method1 };
split_panes[pane_name2] = { 'types': [], 'update_method': update_method2 };
// add our new split to the backout stack
backout_list.push( {'pane1': pane_name1, 'pane2': pane_name2, 'undo': backout_settings} );
$('#undobutton').show();
}
//
// Reverse the last UI split
var undo_split = function () {
// pop off the last split pair
var back = backout_list.pop();
if( !back ) {
return;
}
if( backout_list.length === 0 ) {
$('#undobutton').hide();
}
// Collect all the divs/subs in play
var pane1 = back['pane1'];
var pane2 = back['pane2'];
var pane1_sub = $('#'+pane1+'-sub');
var pane2_sub = $('#'+pane2+'-sub');
var pane1_parent = $('#'+pane1).parent();
var pane2_parent = $('#'+pane2).parent();
if( pane1_parent.attr('id') != pane2_parent.attr('id') ) {
// sanity check failed...somebody did something weird...bail out
console.log( pane1 );
console.log( pane2 );
console.log( pane1_parent );
console.log( pane2_parent );
return;
}
// create a new sub-pane in the panes parent
var parent_sub = $( '<div id="'+pane1_parent.attr('id')+'-sub" class="split-sub" />' )
// check to see if the special #messagewindow is in either of our sub-panes.
var msgwindow = pane1_sub.find('#messagewindow')
if( !msgwindow ) {
//didn't find it in pane 1, try pane 2
msgwindow = pane2_sub.find('#messagewindow')
}
if( msgwindow ) {
// It is, so collect all contents into it instead of our parent_sub div
// then move it to parent sub div, this allows future #messagewindow divs to flow properly
msgwindow.append( pane1_sub.contents() );
msgwindow.append( pane2_sub.contents() );
parent_sub.append( msgwindow );
} else {
//didn't find it, so move the contents of the two panes' sub-panes into the new sub-pane
parent_sub.append( pane1_sub.contents() );
parent_sub.append( pane2_sub.contents() );
}
// clear the parent
pane1_parent.empty();
// add the new sub-pane back to the parent div
pane1_parent.append(parent_sub);
// pull the sub-div's from split_panes
delete split_panes[pane1];
delete split_panes[pane2];
// add our parent pane back into the split_panes list for future splitting
split_panes[pane1_parent.attr('id')] = back['undo'];
}
//
// UI elements
//
//
// Draw "Split Controls" Dialog
var onSplitDialog = function () {
var dialog = $("#splitdialogcontent");
dialog.empty();
dialog.append("<h3>Split?</h3>");
dialog.append('<input type="radio" name="direction" value="vertical" checked> top/bottom<br />');
dialog.append('<input type="radio" name="direction" value="horizontal"> side-by-side<br />');
dialog.append("<h3>Split Which Pane?</h3>");
for ( var pane in split_panes ) {
dialog.append('<input type="radio" name="pane" value="'+ pane +'">'+ pane +'<br />');
}
dialog.append("<h3>New Pane Names</h3>");
dialog.append('<input type="text" name="new_pane1" value="" />');
dialog.append('<input type="text" name="new_pane2" value="" />');
dialog.append("<h3>New First Pane Flow</h3>");
dialog.append('<input type="radio" name="flow1" value="append" checked>append<br />');
dialog.append('<input type="radio" name="flow1" value="replace">replace<br />');
dialog.append("<h3>New Second Pane Flow</h3>");
dialog.append('<input type="radio" name="flow2" value="append" checked>append<br />');
dialog.append('<input type="radio" name="flow2" value="replace">replace<br />');
dialog.append('<div id="splitclose" class="button">Split It</div>');
$("#splitclose").bind("click", onSplitDialogClose);
plugins['popups'].togglePopup("#splitdialog");
}
//
// Close "Split Controls" Dialog
var onSplitDialogClose = function () {
var pane = $("input[name=pane]:checked").attr("value");
var direction = $("input[name=direction]:checked").attr("value");
var new_pane1 = $("input[name=new_pane1]").val();
var new_pane2 = $("input[name=new_pane2]").val();
var flow1 = $("input[name=flow1]:checked").attr("value");
var flow2 = $("input[name=flow2]:checked").attr("value");
if( new_pane1 == "" ) {
new_pane1 = 'pane_'+num_splits;
num_splits++;
}
if( new_pane2 == "" ) {
new_pane2 = 'pane_'+num_splits;
num_splits++;
}
if( document.getElementById(new_pane1) ) {
alert('An element: "' + new_pane1 + '" already exists');
return;
}
if( document.getElementById(new_pane2) ) {
alert('An element: "' + new_pane2 + '" already exists');
return;
}
dynamic_split( pane, direction, new_pane1, new_pane2, flow1, flow2, [50,50] );
plugins['popups'].closePopup("#splitdialog");
}
//
// Draw "Pane Controls" dialog
var onPaneControlDialog = function () {
var dialog = $("#splitdialogcontent");
dialog.empty();
dialog.append("<h3>Set Which Pane?</h3>");
for ( var pane in split_panes ) {
dialog.append('<input type="radio" name="pane" value="'+ pane +'">'+ pane +'<br />');
}
dialog.append("<h3>Which content types?</h3>");
for ( var type in known_types ) {
dialog.append('<input type="checkbox" value="'+ known_types[type] +'">'+ known_types[type] +'<br />');
}
dialog.append('<div id="paneclose" class="button">Make It So</div>');
$("#paneclose").bind("click", onPaneControlDialogClose);
plugins['popups'].togglePopup("#splitdialog");
}
//
// Close "Pane Controls" dialog
var onPaneControlDialogClose = function () {
var pane = $("input[name=pane]:checked").attr("value");
var types = new Array;
$('#splitdialogcontent input[type=checkbox]:checked').each(function() {
types.push( $(this).attr('value') );
});
set_pane_types( pane, types );
plugins['popups'].closePopup("#splitdialog");
}
//
// plugin functions
//
//
// Accept plugin onText events
var onText = function (args, kwargs) {
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);
}
for ( var key in split_panes) {
var pane = split_panes[key];
// 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
var text_div = $('#'+key+'-sub');
if ( pane['update_method'] == 'replace' ) {
text_div.html(args[0])
} else {
text_div.append(args[0]);
var scrollHeight = text_div.parent().prop("scrollHeight");
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
}
return true;
}
}
}
return false;
}
//
// Required plugin "init" function
var init = function(settings) {
known_types.push('help');
Split(['#main','#input'], {
direction: 'vertical',
sizes: [90,10],
gutterSize: 4,
minSize: [50,50],
});
split_panes['main'] = { 'types': [], 'update_method': 'append' };
// Create our UI
addToolbarButtons();
addSplitDialog();
addPaneDialog();
// Register our utility button events
$("#splitbutton").bind("click", onSplitDialog);
$("#panebutton").bind("click", onPaneControlDialog);
$("#undobutton").bind("click", undo_split);
// Event when client window changes
$(window).bind("resize", doWindowResize);
$("[data-role-input]").bind("resize", doWindowResize)
.bind("paste", resizeInputField)
.bind("cut", resizeInputField);
// Event when any key is pressed
$(document).keyup(resizeInputField);
console.log("Splithandler Plugin Initialized.");
}
return {
init: init,
onText: onText,
dynamic_split: dynamic_split,
undo_split: undo_split,
set_pane_types: set_pane_types,
}
})()
plugin_handler.add('splithandler', splithandler_plugin);

View file

@ -1,145 +0,0 @@
// Use split.js to create a basic ui
var SplitHandler = (function () {
var split_panes = {};
var backout_list = new Array;
var set_pane_types = function(splitpane, types) {
split_panes[splitpane]['types'] = types;
}
var dynamic_split = function(splitpane, direction, pane_name1, pane_name2, update_method1, update_method2, sizes) {
// find the sub-div of the pane we are being asked to split
splitpanesub = splitpane + '-sub';
// create the new div stack to replace the sub-div with.
var first_div = $( '<div id="'+pane_name1+'" class="split split-'+direction+'" />' )
var first_sub = $( '<div id="'+pane_name1+'-sub" class="split-sub" />' )
var second_div = $( '<div id="'+pane_name2+'" class="split split-'+direction+'" />' )
var second_sub = $( '<div id="'+pane_name2+'-sub" class="split-sub" />' )
// check to see if this sub-pane contains anything
contents = $('#'+splitpanesub).contents();
if( contents ) {
// it does, so move it to the first new div-sub (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 pane name, but store it for the backout stack
var backout_settings = split_panes[splitpane];
delete( split_panes[splitpane] );
// now vaporize the current split_N-sub placeholder and create two new panes.
$('#'+splitpane).append(first_div);
$('#'+splitpane).append(second_div);
$('#'+splitpane+'-sub').remove();
// And split
Split(['#'+pane_name1,'#'+pane_name2], {
direction: direction,
sizes: sizes,
gutterSize: 4,
minSize: [50,50],
});
// store our new split sub-divs for future splits/uses by the main UI.
split_panes[pane_name1] = { 'types': [], 'update_method': update_method1 };
split_panes[pane_name2] = { 'types': [], 'update_method': update_method2 };
// add our new split to the backout stack
backout_list.push( {'pane1': pane_name1, 'pane2': pane_name2, 'undo': backout_settings} );
}
var undo_split = function() {
// pop off the last split pair
var back = backout_list.pop();
if( !back ) {
return;
}
// Collect all the divs/subs in play
var pane1 = back['pane1'];
var pane2 = back['pane2'];
var pane1_sub = $('#'+pane1+'-sub');
var pane2_sub = $('#'+pane2+'-sub');
var pane1_parent = $('#'+pane1).parent();
var pane2_parent = $('#'+pane2).parent();
if( pane1_parent.attr('id') != pane2_parent.attr('id') ) {
// sanity check failed...somebody did something weird...bail out
console.log( pane1 );
console.log( pane2 );
console.log( pane1_parent );
console.log( pane2_parent );
return;
}
// create a new sub-pane in the panes parent
var parent_sub = $( '<div id="'+pane1_parent.attr('id')+'-sub" class="split-sub" />' )
// check to see if the special #messagewindow is in either of our sub-panes.
var msgwindow = pane1_sub.find('#messagewindow')
if( !msgwindow ) {
//didn't find it in pane 1, try pane 2
msgwindow = pane2_sub.find('#messagewindow')
}
if( msgwindow ) {
// It is, so collect all contents into it instead of our parent_sub div
// then move it to parent sub div, this allows future #messagewindow divs to flow properly
msgwindow.append( pane1_sub.contents() );
msgwindow.append( pane2_sub.contents() );
parent_sub.append( msgwindow );
} else {
//didn't find it, so move the contents of the two panes' sub-panes into the new sub-pane
parent_sub.append( pane1_sub.contents() );
parent_sub.append( pane2_sub.contents() );
}
// clear the parent
pane1_parent.empty();
// add the new sub-pane back to the parent div
pane1_parent.append(parent_sub);
// pull the sub-div's from split_panes
delete split_panes[pane1];
delete split_panes[pane2];
// add our parent pane back into the split_panes list for future splitting
split_panes[pane1_parent.attr('id')] = back['undo'];
}
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],
});
split_panes['main'] = { 'types': [], 'update_method': 'append' };
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,
undo_split: undo_split,
}
})();

View file

@ -5,625 +5,285 @@
* This is used in conjunction with the main evennia.js library, which
* handles all the communication with the Server.
*
* The job of this code is to create listeners to subscribe to evennia
* messages, via Evennia.emitter.on(cmdname, listener) and to handle
* input from the user and send it to
* Evennia.msg(cmdname, args, kwargs, [callback]).
* The job of this code is to coordinate between listeners subscribed to
* evennia messages and any registered plugins that want to process those
* messages and send data back to Evennia
*
* This is done via Evennia.emitter.on(cmdname, listener) and calling
* each plugin's init() function to give each plugin a chance to register
* input handlers or other events on startup.
*
* Once a plugin has determined it wants to send a message back to the
* server, it generates an onSend() function event which allows all
* other plugins a chance to modify the event and then uses
* Evennia.msg(cmdname, args, kwargs, [callback]) to finally send the data.
*
*/
(function () {
"use strict"
var num_splits = 0; //unique id counter for default split-panel names
var options = {};
var known_types = new Array();
known_types.push('help');
//
// GUI Elements
// Global Plugins system
//
var options = {}; // Global "settings" object that all plugins can use to
// save/pass data to each other and the server.
// format should match:
// { 'plugin_name': { 'option_key': value, ... }, ... }
// Manage history for input line
var input_history = function() {
var history_max = 21;
var history = new Array();
var history_pos = 0;
history[0] = ''; // the very latest input is empty for new entry.
var back = function () {
// step backwards in history stack
history_pos = Math.min(++history_pos, history.length - 1);
return history[history.length - 1 - history_pos];
};
var fwd = function () {
// step forwards in history stack
history_pos = Math.max(--history_pos, 0);
return history[history.length - 1 - history_pos];
};
var add = function (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
}
history[history.length-1] = input;
history[history.length] = '';
};
// reset the position to the last history entry
history_pos = 0;
};
var end = function () {
// move to the end of the history stack
history_pos = 0;
return history[history.length -1];
}
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;
}
return {back: back,
fwd: fwd,
add: add,
end: end,
scratch: scratch}
}();
function openPopup(dialogname, content) {
var dialog = $(dialogname);
if (!dialog.length) {
console.log("Dialog " + renderto + " not found.");
return;
}
if (content) {
var contentel = dialog.find(".dialogcontent");
contentel.html(content);
}
dialog.show();
}
function closePopup(dialogname) {
var dialog = $(dialogname);
dialog.hide();
}
function togglePopup(dialogname, content) {
var dialog = $(dialogname);
if (dialog.css('display') == 'none') {
openPopup(dialogname, content);
} else {
closePopup(dialogname);
}
}
var plugins = {}; // Global plugin objects by name.
// Each must have an init() function.
//
// GUI Event Handlers
// Global plugin_handler
//
var plugin_handler = (function () {
"use strict"
// 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) {
onText(["Attempting to reconnnect..."], {cls: "sys"});
Evennia.connect();
}
// Don't try to send anything until the connection is back.
return;
}
var inputfield = $("#inputfield");
var outtext = inputfield.val();
var lines = outtext.trim().replace(/[\r]+/,"\n").replace(/[\n]+/, "\n").split("\n");
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (line.length > 7 && line.substr(0, 7) == "##send ") {
// send a specific oob instruction ["cmdname",[args],{kwargs}]
line = line.slice(7);
var cmdarr = JSON.parse(line);
var cmdname = cmdarr[0];
var args = cmdarr[1];
var kwargs = cmdarr[2];
log(cmdname, args, kwargs);
Evennia.msg(cmdname, args, kwargs);
} else {
input_history.add(line);
inputfield.val("");
Evennia.msg("text", [line], {});
}
}
}
var ordered_plugins = new Array; // plugins in <html> loaded order
// Opens the options dialog
function doOpenOptions() {
if (!Evennia.isConnected()) {
alert("You need to be connected.");
return;
//
// Plugin Support Functions
//
// Add a new plugin
var add = function (name, plugin) {
plugins[name] = plugin;
ordered_plugins.push( plugin );
}
togglePopup("#optionsdialog");
}
// Closes the currently open dialog
function doCloseDialog(event) {
var dialog = $(event.target).closest(".dialog");
dialog.hide();
}
//
// GUI Event Handlers
//
// catch all keyboard input, handle special chars
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
doSendText();
event.preventDefault();
}
else if (inputfield[0].selectionStart == inputfield.val().length) {
// Only process up/down arrow if cursor is at the end of the line.
if (code === 38) { // Arrow up
history_entry = input_history.back();
}
else if (code === 40) { // Arrow down
history_entry = input_history.fwd();
}
}
if (code === 27) { // Escape key
if ($('#helpdialog').is(':visible')) {
closePopup("#helpdialog");
} else {
closePopup("#optionsdialog");
}
}
if (history_entry !== null) {
// Doing a history navigation; replace the text in the input.
inputfield.val(history_entry);
event.preventDefault();
}
else {
// Save the current contents of the input to the history scratch area.
setTimeout(function () {
// Need to wait until after the key-up to capture the value.
input_history.scratch(inputfield.val());
input_history.end();
}, 0);
}
};
function onKeyPress (event) {
// Prevent carriage returns inside the input area.
if (event.which === 13) {
event.preventDefault();
}
}
var resizeInputField = function () {
return function() {
var wrapper = $("#inputform")
var input = $("#inputcontrol")
var prompt = $("#prompt")
input.height(wrapper.height() - (input.offset().top - wrapper.offset().top));
}
}();
// Handle resizing of client
function doWindowResize() {
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) {
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);
}
// 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];
// 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
var text_div = $('#'+key+'-sub');
if ( pane['update_method'] == 'replace' ) {
text_div.html(args[0])
} else {
text_div.append(args[0]);
var scrollHeight = text_div.parent().prop("scrollHeight");
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
}
// record sending this message to a pane, no need to update the default div
use_default_pane = false;
// catch all keyboard input, handle special chars
var onKeydown = function (event) {
// cycle through each plugin's keydown
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
// does this plugin handle keydown events?
if( 'onKeydown' in plugin ) {
// yes, does this plugin claim this event exclusively?
if( plugin.onKeydown(event) ) {
// 'true' claims this event has been handled
return;
}
}
}
console.log('NO plugin handled this Keydown');
}
// 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("<div class='" + cls + "'>" + args[0] + "</div>");
var scrollHeight = mwin.parent().parent().prop("scrollHeight");
mwin.parent().parent().animate({ scrollTop: scrollHeight }, 0);
onNewLine(args[0], null);
}
}
// Handle prompt output from the server
function onPrompt(args, kwargs) {
// show prompt
$('#prompt')
.addClass("out")
.html(args[0]);
doWindowResize();
// also display the prompt in the output window if gagging is disabled
if (("gagprompt" in options) && (!options["gagprompt"])) {
onText(args, kwargs);
}
}
// Called when the user logged in
function onLoggedIn() {
$('#optionsbutton').removeClass('hidden');
Evennia.msg("webclient_options", [], {});
}
// Called when a setting changed
function onGotOptions(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);
} else {
elem.prop('checked', value);
};
});
}
// Called when the user changed a setting from the interface
function onOptionCheckboxChanged() {
var name = $(this).data("setting");
var value = this.checked;
var changedoptions = {};
changedoptions[name] = value;
Evennia.msg("webclient_options", [], changedoptions);
options[name] = value;
}
// Silences events we don't do anything with.
function onSilence(cmdname, args, kwargs) {}
// Handle the server connection closing
function onConnectionClose(conn_name, evt) {
$('#optionsbutton').addClass('hidden');
closePopup("#optionsdialog");
onText(["The connection was closed or lost."], {'cls': 'err'});
}
// Handle unrecognized commands from server
function onDefault(cmdname, args, kwargs) {
var mwin = $("#messagewindow");
mwin.append(
"<div class='msg err'>"
+ "Error or Unhandled event:<br>"
+ cmdname + ", "
+ JSON.stringify(args) + ", "
+ JSON.stringify(kwargs) + "<p></div>");
mwin.scrollTop(mwin[0].scrollHeight);
}
// Ask if user really wants to exit session when closing
// the tab or reloading the page. Note: the message is not shown
// in Firefox, there it's a standard error.
function onBeforeUnload() {
return "You are about to leave the game. Please confirm.";
}
// Notifications
var unread = 0;
var originalTitle = document.title;
var focused = true;
var favico;
function onBlur(e) {
focused = false;
}
// Notifications for unfocused window
function onFocus(e) {
focused = true;
document.title = originalTitle;
unread = 0;
favico.badge(0);
}
function onNewLine(text, originator) {
if(!focused) {
// Changes unfocused browser tab title to number of unread messages
unread++;
favico.badge(unread);
document.title = "(" + unread + ") " + originalTitle;
if ("Notification" in window){
if (("notification_popup" in options) && (options["notification_popup"])) {
// There is a Promise-based API for this, but its not supported
// in Safari and some older browsers:
// https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission#Browser_compatibility
Notification.requestPermission(function(result) {
if(result === "granted") {
var title = originalTitle === "" ? "Evennia" : originalTitle;
var options = {
body: text.replace(/(<([^>]+)>)/ig,""),
icon: "/static/website/images/evennia_logo.png"
}
var n = new Notification(title, options);
n.onclick = function(e) {
e.preventDefault();
window.focus();
this.close();
}
// Ask if user really wants to exit session when closing
// the tab or reloading the page. Note: the message is not shown
// in Firefox, there it's a standard error.
var onBeforeUnload = function () {
// cycle through each plugin to look for unload handlers
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onBeforeUnload' in plugin ) {
plugin.onBeforeUnload();
}
});
}
if (("notification_sound" in options) && (options["notification_sound"])) {
var audio = new Audio("/static/webclient/media/notification.wav");
audio.play();
}
}
}
}
// User clicked on a dialog to drag it
function doStartDragDialog(event) {
var dialog = $(event.target).closest(".dialog");
dialog.css('cursor', 'move');
var position = dialog.offset();
var diffx = event.pageX;
var diffy = event.pageY;
var drag = function(event) {
var y = position.top + event.pageY - diffy;
var x = position.left + event.pageX - diffx;
dialog.offset({top: y, left: x});
};
var undrag = function() {
$(document).unbind("mousemove", drag);
$(document).unbind("mouseup", undrag);
dialog.css('cursor', '');
}
}
$(document).bind("mousemove", drag);
$(document).bind("mouseup", undrag);
}
function onSplitDialogClose() {
var pane = $("input[name=pane]:checked").attr("value");
var direction = $("input[name=direction]:checked").attr("value");
var new_pane1 = $("input[name=new_pane1]").val();
var new_pane2 = $("input[name=new_pane2]").val();
var flow1 = $("input[name=flow1]:checked").attr("value");
var flow2 = $("input[name=flow2]:checked").attr("value");
//
// Evennia Public Event Handlers
//
if( new_pane1 == "" ) {
new_pane1 = 'pane_'+num_splits;
num_splits++;
// Handle onLoggedIn from the server
var onLoggedIn = function (args, kwargs) {
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onLoggedIn' in plugin ) {
plugin.onLoggedIn(args, kwargs);
}
}
}
if( new_pane2 == "" ) {
new_pane2 = 'pane_'+num_splits;
num_splits++;
// Handle onGotOptions from the server
var onGotOptions = function (args, kwargs) {
// does any plugin handle Options?
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onGotOptions' in plugin ) {
plugin.onGotOptions(args, kwargs);
}
}
}
if( document.getElementById(new_pane1) ) {
alert('An element: "' + new_pane1 + '" already exists');
return;
// Handle text coming from the server
var onText = function (args, kwargs) {
// does this plugin handle this onText event?
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onText' in plugin ) {
if( plugin.onText(args, kwargs) ) {
// True -- means this plugin claims this Text exclusively.
return;
}
}
}
console.log('NO plugin handled this Text');
}
if( document.getElementById(new_pane2) ) {
alert('An element: "' + new_pane2 + '" already exists');
return;
// Handle prompt output from the server
var onPrompt = function (args, kwargs) {
// does this plugin handle this onPrompt event?
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onPrompt' in plugin ) {
if( plugin.onPrompt(args, kwargs) ) {
// True -- means this plugin claims this Prompt exclusively.
return;
}
}
}
console.log('NO plugin handled this Prompt');
}
SplitHandler.dynamic_split( pane, direction, new_pane1, new_pane2, flow1, flow2, [50,50] );
closePopup("#splitdialog");
}
function onSplitDialog() {
var dialog = $("#splitdialogcontent");
dialog.empty();
dialog.append("<h3>Split?</h3>");
dialog.append('<input type="radio" name="direction" value="vertical" checked> top/bottom<br />');
dialog.append('<input type="radio" name="direction" value="horizontal"> side-by-side<br />');
dialog.append("<h3>Split Which Pane?</h3>");
for ( var pane in SplitHandler.split_panes ) {
dialog.append('<input type="radio" name="pane" value="'+ pane +'">'+ pane +'<br />');
// Handle unrecognized commands from server
var onDefault = function (cmdname, args, kwargs) {
// does this plugin handle this UnknownCmd?
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onUnknownCmd' in plugin ) {
if( plugin.onUnknownCmd(args, kwargs) ) {
// True -- means this plugin claims this UnknownCmd exclusively.
return;
}
}
}
console.log('NO plugin handled this Unknown Evennia Command');
}
dialog.append("<h3>New Pane Names</h3>");
dialog.append('<input type="text" name="new_pane1" value="" />');
dialog.append('<input type="text" name="new_pane2" value="" />');
dialog.append("<h3>New First Pane</h3>");
dialog.append('<input type="radio" name="flow1" value="append" checked>append new incoming messages<br />');
dialog.append('<input type="radio" name="flow1" value="replace">replace old messages with new ones<br />');
// Handle the server connection closing
var onConnectionClose = function (args, kwargs) {
// give every plugin a chance to do stuff onConnectionClose
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onConnectionClose' in plugin ) {
plugin.onConnectionClose(args, kwargs);
}
}
dialog.append("<h3>New Second Pane</h3>");
dialog.append('<input type="radio" name="flow2" value="append" checked>append new incoming messages<br />');
dialog.append('<input type="radio" name="flow2" value="replace">replace old messages with new ones<br />');
dialog.append('<div id="splitclose" class="button">Split It</div>');
$("#splitclose").bind("click", onSplitDialogClose);
togglePopup("#splitdialog");
}
function onPaneControlDialogClose() {
var pane = $("input[name=pane]:checked").attr("value");
var types = new Array;
$('#splitdialogcontent input[type=checkbox]:checked').each(function() {
types.push( $(this).attr('value') );
});
SplitHandler.set_pane_types( pane, types );
closePopup("#splitdialog");
}
function onPaneControlDialog() {
var dialog = $("#splitdialogcontent");
dialog.empty();
dialog.append("<h3>Set Which Pane?</h3>");
for ( var pane in SplitHandler.split_panes ) {
dialog.append('<input type="radio" name="pane" value="'+ pane +'">'+ pane +'<br />');
onText(["The connection was closed or lost."], {'cls': 'err'});
}
dialog.append("<h3>Which content types?</h3>");
for ( var type in known_types ) {
dialog.append('<input type="checkbox" value="'+ known_types[type] +'">'+ known_types[type] +'<br />');
// Silences events we don't do anything with.
var onSilence = function (cmdname, args, kwargs) {}
//
// Global onSend() function to iterate through all plugins before sending text to the server.
// This can be called by other plugins for "Triggers", <enter>, and other automated sends
//
var onSend = function (line) {
if (!Evennia.isConnected()) {
var reconnect = confirm("Not currently connected. Reconnect?");
if (reconnect) {
onText(["Attempting to reconnnect..."], {cls: "sys"});
Evennia.connect();
}
// Don't try to send anything until the connection is back.
return;
}
// default output command
var cmd = {
command: "text",
args: [ line ],
kwargs: {}
};
// Give each plugin a chance to use/modify the outgoing command for aliases/history/etc
for( let n=0; n < ordered_plugins.length; n++ ) {
let plugin = ordered_plugins[n];
if( 'onSend' in plugin ) {
var outCmd = plugin.onSend(line);
if( outCmd ) {
cmd = outCmd;
}
}
}
// console.log('sending: ' + cmd.command + ', [' + cmd.args[0].toString() + '], ' + cmd.kwargs.toString() );
Evennia.msg(cmd.command, cmd.args, cmd.kwargs);
}
dialog.append('<div id="paneclose" class="button">Make It So</div>');
$("#paneclose").bind("click", onPaneControlDialogClose);
//
// call each plugins' init function (the only required function)
//
var init = function () {
for( let n=0; n < ordered_plugins.length; n++ ) {
ordered_plugins[n].init();
}
}
return {
add: add,
onKeydown: onKeydown,
onBeforeUnload: onBeforeUnload,
onLoggedIn: onLoggedIn,
onText: onText,
onGotOptions: onGotOptions,
onPrompt: onPrompt,
onDefault: onDefault,
onSilence: onSilence,
onConnectionClose: onConnectionClose,
onSend: onSend,
init: init,
}
})();
togglePopup("#splitdialog");
}
//
// Register Events
// Webclient Initialization
//
// Event when client finishes loading
$(document).ready(function() {
if( SplitHandler ) {
SplitHandler.init();
$("#splitbutton").bind("click", onSplitDialog);
$("#panebutton").bind("click", onPaneControlDialog);
$("#undobutton").bind("click", SplitHandler.undo_split);
$("#optionsbutton").hide();
} else {
$("#splitbutton").hide();
$("#panebutton").hide();
$("#undobutton").hide();
}
if ("Notification" in window) {
Notification.requestPermission();
}
favico = new Favico({
animation: 'none'
});
// Event when client window changes
$(window).bind("resize", doWindowResize);
$(window).blur(onBlur);
$(window).focus(onFocus);
//$(document).on("visibilitychange", onVisibilityChange);
$("[data-role-input]").bind("resize", doWindowResize)
.keypress(onKeyPress)
.bind("paste", resizeInputField)
.bind("cut", resizeInputField);
// Event when any key is pressed
$(document).keydown(onKeydown)
.keyup(resizeInputField);
// Pressing the send button
$("#inputsend").bind("click", doSendText);
// Pressing the settings button
$("#optionsbutton").bind("click", doOpenOptions);
// Checking a checkbox in the settings dialog
$("[data-setting]").bind("change", onOptionCheckboxChanged);
// Pressing the close button on a dialog
$(".dialogclose").bind("click", doCloseDialog);
// Makes dialogs draggable
$(".dialogtitle").bind("mousedown", doStartDragDialog);
// This is safe to call, it will always only
// initialize once.
Evennia.init();
// register listeners
Evennia.emitter.on("text", onText);
Evennia.emitter.on("prompt", onPrompt);
Evennia.emitter.on("default", onDefault);
Evennia.emitter.on("connection_close", onConnectionClose);
Evennia.emitter.on("logged_in", onLoggedIn);
Evennia.emitter.on("webclient_options", onGotOptions);
// silence currently unused events
Evennia.emitter.on("connection_open", onSilence);
Evennia.emitter.on("connection_error", onSilence);
// Handle pressing the send button
$("#inputsend").bind("click", doSendText);
// register listeners
Evennia.emitter.on("logged_in", plugin_handler.onLoggedIn);
Evennia.emitter.on("text", plugin_handler.onText);
Evennia.emitter.on("webclient_options", plugin_handler.onGotOptions);
Evennia.emitter.on("prompt", plugin_handler.onPrompt);
Evennia.emitter.on("default", plugin_handler.onDefault);
Evennia.emitter.on("connection_close", plugin_handler.onConnectionClose);
// silence currently unused events
Evennia.emitter.on("connection_open", plugin_handler.onSilence);
Evennia.emitter.on("connection_error", plugin_handler.onSilence);
// Event when closing window (have to have Evennia initialized)
$(window).bind("beforeunload", onBeforeUnload);
$(window).bind("beforeunload", plugin_handler.onBeforeUnload);
$(window).bind("unload", Evennia.connection.close);
doWindowResize();
// Event when any key is pressed
$(document).keydown(plugin_handler.onKeydown)
// set an idle timer to send idle every 3 minutes,
// to avoid proxy servers timing out on us
setInterval(function() {
// Connect to server
Evennia.msg("text", ["idle"], {});
},
60000*3
setInterval( function() { // Connect to server
Evennia.msg("text", ["idle"], {});
},
60000*3
);
console.log("Completed GUI setup");
// Initialize all plugins
plugin_handler.init();
console.log("Completed Webclient setup");
});
})();

View file

@ -63,15 +63,21 @@ JQuery available.
</script>
<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/split.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
<script src={% static "webclient/js/splithandler.js" %} language="javascript"></script>
<!-- Load gui library -->
{% block guilib_import %}
<script src={% static "webclient/js/webclient_gui.js" %} language="javascript" type="text/javascript" charset="utf-8"></script>
<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/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/splithandler.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/default_out.js" %} language="javascript" type="text/javascript"></script>
{% endblock %}
<script src="https://cdn.rawgit.com/ejci/favico.js/master/favico-0.3.10.min.js" language="javascript" type="text/javascript" charset="utf-8"></script>

View file

@ -6,67 +6,19 @@
- guilib_import - for using your own gui lib
-->
{% block client %}
<div id="toolbar">
<button id="optionsbutton" type="button" aria-haspopup="true" aria-owns="#optionsdialog">&#x2699;<span class="sr-only sr-only-focusable">Settings</span></button>
<button id="splitbutton" type="button" aria-haspopup="true" aria-owns="#optionsdialog">&#x21f9;<span class="sr-only sr-only-focusable">Splits</span></button>
<button id="panebutton" type="button" aria-haspopup="true" aria-owns="#optionsdialog">&#x2699;<span class="sr-only sr-only-focusable">Splits</span></button>
<button id="undobutton" type="button" aria-haspopup="true" aria-owns="#optionsdialog">&#x21B6;<span class="sr-only sr-only-focusable">Splits</span></button>
</div>
<!-- The "Main" Content -->
<!-- Basic toolbar -->
<div id="toolbar"></div>
<!-- "Main" Content -->
<div id="main" class="split split-vertical" data-role-default>
<div id="main-sub" class="split-sub">
<div id="messagewindow"></div>
</div>
</div>
<!-- The "Input" Pane -->
<div id="input" class="split split-vertical" data-role-input data-update-append></div>
<!-- Basic UI Components -->
<div id="splitdialog" class="dialog">
<div class="dialogtitle">Split Pane<span class="dialogclose">&times;</span></div>
<div class="dialogcontentparent">
<div id="splitdialogcontent" class="dialogcontent">
</div>
</div>
</div>
<div id="optionsdialog" class="dialog">
<div class="dialogtitle">Options<span class="dialogclose">&times;</span></div>
<div class="dialogcontentparent">
<div class="dialogcontent">
<h3>Output display</h3>
<label><input type="checkbox" data-setting="gagprompt" value="value">Don't echo prompts to the main text area</label><br />
<label><input type="checkbox" data-setting="helppopup" value="value">Open help in popup window</label><br />
<hr />
<h3>Notifications</h3>
<label><input type="checkbox" data-setting="notification_popup" value="value">Popup notification</label><br />
<label><input type="checkbox" data-setting="notification_sound" value="value">Play a sound</label><br />
</div>
</div>
</div>
<div id="helpdialog" class="dialog">
<div class="dialogtitle">Help<span class="dialogclose">&times;</span></div>
<div class="dialogcontentparent">
<div class="dialogcontent">
</div>
</div>
</div>
<script type="text/html" id="split-template">
<div class="split content<%#horizontal%> split-horizontal<%/horizontal%>" id='<%id%>'>
</div>
</script>
<script type="text/html" id="output-template">
<div id="<%id%>" role="log" data-role-output data-update-append data-tags='[<%#tags%>"<%.%>", <%/tags%>]'></div>
</script>
<script type="text/html" id="input-template">
<!-- "Input" Pane -->
<div id="input" class="split split-vertical" data-role-input data-update-append>
<div id="inputform" class="wrapper">
<div id="prompt" class="prompt">
</div>
@ -77,7 +29,7 @@
</span>
</div>
</div>
</script>
</div>
{% endblock %}
{% block scripts %}