Merge with develop and fix merge conflicts

This commit is contained in:
Griatch 2018-10-01 20:58:16 +02:00
commit 72f4fedcbe
148 changed files with 20005 additions and 2718 deletions

View file

@ -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,116 @@ 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;
background: #000;
color: #fff;
padding: 0 .45em;
font-size: 1.1em;
font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace;
#input {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
}
#inputfield, #inputsizer {
float: left;
width: 95%;
border: 0;
height: 100%;
background: #000;
color: #fff;
padding: 0 .45rem;
font-size: 1.1rem;
font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace;
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 {
width: 2rem;
font-size: 2rem;
color: #a6a6a6;
background-color: transparent;
border: 0px;
}
#splitbutton:hover {
color: white;
cursor: pointer;
}
#panebutton {
width: 2rem;
font-size: 2rem;
color: #a6a6a6;
background-color: transparent;
border: 0px;
}
#panebutton:hover {
color: white;
cursor: pointer;
}
#undobutton {
width: 2rem;
font-size: 2rem;
color: #a6a6a6;
background-color: transparent;
border: 0px;
}
#undobutton:hover {
color: white;
cursor: pointer;
}
.button {
width: fit-content;
padding: 1em;
color: black;
border: 1px solid black;
background-color: darkgray;
margin: 0 auto;
}
.splitbutton:hover {
cursor: pointer;
}
#optionsbutton {
width: 40px;
font-size: 20px;
width: 2rem;
font-size: 2rem;
color: #a6a6a6;
background-color: transparent;
border: 0px;
@ -173,8 +203,8 @@ div {margin:0px;}
#toolbar {
position: fixed;
top: 0;
right: 5px;
top: .5rem;
right: .5rem;
z-index: 1;
}
@ -195,7 +225,8 @@ div {margin:0px;}
z-index: 10;
background-color: #fefefe;
border: 1px solid #888;
color: black;
color: lightgray;
background-color: #2c2c2c;
}
@ -233,11 +264,12 @@ div {margin:0px;}
cursor: move;
font-weight: bold;
font-size: 16px;
background-color: #d9d9d9;
color: white;
background-color: #595959;
}
.dialogclose {
color: #aaa;
color: #d5d5d5;
float: right;
font-size: 28px;
font-weight: bold;
@ -248,6 +280,52 @@ 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;
}
.split-sub {
padding: .5rem;
}
.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 */

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,154 @@
/*
*
* Assignable 'hot-buttons' Plugin
*
* This adds a bar of 9 buttons that can be shift-click assigned whatever is in the textinput buffer, so you can simply
* click the button again and have it execute those commands, instead of having to type it all out again and again.
*
* It stores these commands as server side options.
*
* NOTE: This is a CONTRIB. To use this in your game:
*
* Stop Evennia
*
* Copy this file to mygame/web/static_overrides/webclient/js/plugins/hotbuttons.js
* Copy evennia/web/webclient/templates/webclient/base.html to mygame/web/template_overrides/webclient/base.html
*
* Edit mygame/web/template_overrides/webclient/base.html to add:
* <script src={% static "webclient/js/plugins/hotbuttons.js" %} language="javascript" type="text/javascript"></script>
* after the other <script></script> plugin tags.
*
* Run: evennia collectstatic (say 'yes' to the overwrite prompt)
* Start Evennia
*/
plugin_handler.add('hotbuttons', (function () {
var num_buttons = 9;
var command_cache = new Array(num_buttons);
//
// Add Buttons
var addButtonsUI = function () {
var buttons = $( [
'<div id="buttons" class="split split-vertical">',
' <div id="buttonsform" class="wrapper">',
' <div id="buttonscontrol" class="input-group">',
' <button class="btn" id="assign_button0" type="button" value="button0">unassigned</button>',
' <button class="btn" id="assign_button1" type="button" value="button1">unassigned</button>',
' <button class="btn" id="assign_button2" type="button" value="button2">unassigned</button>',
' <button class="btn" id="assign_button3" type="button" value="button3">unassigned</button>',
' <button class="btn" id="assign_button4" type="button" value="button4">unassigned</button>',
' <button class="btn" id="assign_button5" type="button" value="button5">unassigned</button>',
' <button class="btn" id="assign_button6" type="button" value="button6">unassigned</button>',
' <button class="btn" id="assign_button7" type="button" value="button7">unassigned</button>',
' <button class="btn" id="assign_button8" type="button" value="button8">unassigned</button>',
' </div>',
' </div>',
'</div>',
].join("\n") );
// Add buttons in front of the existing #inputform
buttons.insertBefore('#inputform');
$('#inputform').addClass('split split-vertical');
Split(['#buttons','#inputform'], {
direction: 'vertical',
sizes: [50,50],
gutterSize: 4,
minSize: 150,
});
}
//
// collect command text
var assignButton = function(n, text) { // n is 1-based
// make sure text has something in it
if( text && text.length ) {
// cache the command text
command_cache[n] = text;
// is there a space in the command, indicating "command argument" syntax?
if( text.indexOf(" ") > 0 ) {
// use the first word as the text on the button
$("#assign_button"+n).text( text.slice(0, text.indexOf(" ")) );
} else {
// use the single-word-text on the button
$("#assign_button"+n).text( text );
}
}
}
//
// Shift click a button to clear it
var clearButton = function(n) {
// change button text to "unassigned"
$("#assign_button"+n).text( "unassigned" );
// clear current command
command_cache[n] = "unassigned";
}
//
// actually send the command associated with the button that is clicked
var sendImmediate = function(n) {
var text = command_cache[n];
if( text.length ) {
Evennia.msg("text", [text], {});
}
}
//
// send, assign, or clear the button
var hotButtonClicked = function(e) {
var button = $("#assign_button"+e.data);
console.log("button " + e.data + " clicked");
if( button.text() == "unassigned" ) {
// Assign the button and send the full button state to the server using a Webclient_Options event
assignButton( e.data, $('#inputfield').val() );
Evennia.msg("webclient_options", [], { "HotButtons": command_cache });
} else {
if( e.shiftKey ) {
// Clear the button and send the full button state to the server using a Webclient_Options event
clearButton(e.data);
Evennia.msg("webclient_options", [], { "HotButtons": command_cache });
} else {
sendImmediate(e.data);
}
}
}
// Public
//
// Handle the HotButtons part of a Webclient_Options event
var onGotOptions = function(args, kwargs) {
console.log( args );
console.log( kwargs );
if( kwargs['HotButtons'] ) {
var buttons = kwargs['HotButtons'];
$.each( buttons, function( key, value ) {
assignButton(key, value);
});
}
}
//
// Initialize me
var init = function() {
// Add buttons to the UI
addButtonsUI();
// assign button cache
for( var n=0; n<num_buttons; n++ ) {
command_cache[n] = "unassigned";
$("#assign_button"+n).click( n, hotButtonClicked );
}
console.log("HotButtons Plugin Initialized.");
}
return {
init: init,
onGotOptions: onGotOptions,
}
})());

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,160 @@
/*
*
* 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
'<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 />',
'<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,414 @@
/*
*
* 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 = [];
var known_types = ['all', 'rest'];
// 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 Pane', '');
}
function addPaneDialog () {
plugins['popups'].createDialog('panedialog', 'Assign Pane Options', '');
}
//
// 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();
var selection = '<select name="pane">';
for ( var pane in split_panes ) {
selection = selection + '<option value="' + pane + '">' + pane + '</option>';
}
selection = "Pane to split: " + selection + "</select> ";
dialog.append(selection);
dialog.append('<input type="radio" name="direction" value="vertical" checked>top/bottom </>');
dialog.append('<input type="radio" name="direction" value="horizontal">side-by-side <hr />');
dialog.append('Pane 1: <input type="text" name="new_pane1" value="" />');
dialog.append('<input type="radio" name="flow1" value="linefeed" checked>newlines </>');
dialog.append('<input type="radio" name="flow1" value="replace">replace </>');
dialog.append('<input type="radio" name="flow1" value="append">append <hr />');
dialog.append('Pane 2: <input type="text" name="new_pane2" value="" />');
dialog.append('<input type="radio" name="flow2" value="linefeed" checked>newlines </>');
dialog.append('<input type="radio" name="flow2" value="replace">replace </>');
dialog.append('<input type="radio" name="flow2" value="append">append <hr />');
dialog.append('<div id="splitclose" class="btn btn-large btn-outline-primary float-right">Split</div>');
$("#splitclose").bind("click", onSplitDialogClose);
plugins['popups'].togglePopup("#splitdialog");
}
//
// Close "Split Controls" Dialog
var onSplitDialogClose = function () {
var pane = $("select[name=pane]").val();
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 = $("#panedialogcontent");
dialog.empty();
var selection = '<select name="assign-pane">';
for ( var pane in split_panes ) {
selection = selection + '<option value="' + pane + '">' + pane + '</option>';
}
selection = "Assign to pane: " + selection + "</select> <hr />";
dialog.append(selection);
var multiple = '<select multiple name="assign-type">';
for ( var type in known_types ) {
multiple = multiple + '<option value="' + known_types[type] + '">' + known_types[type] + '</option>';
}
multiple = "Content types: " + multiple + "</select> <hr />";
dialog.append(multiple);
dialog.append('<div id="paneclose" class="btn btn-large btn-outline-primary float-right">Assign</div>');
$("#paneclose").bind("click", onPaneControlDialogClose);
plugins['popups'].togglePopup("#panedialog");
}
//
// Close "Pane Controls" dialog
var onPaneControlDialogClose = function () {
var pane = $("select[name=assign-pane]").val();
var types = $("select[name=assign-type]").val();
// var types = new Array;
// $('#splitdialogcontent input[type=checkbox]:checked').each(function() {
// types.push( $(this).attr('value') );
// });
set_pane_types( pane, types );
plugins['popups'].closePopup("#panedialog");
}
//
// helper function sending text to a pane
var txtToPane = function (panekey, txt) {
var pane = split_panes[panekey];
var text_div = $('#' + panekey + '-sub');
if ( pane['update_method'] == 'replace' ) {
text_div.html(txt)
} else if ( pane['update_method'] == 'append' ) {
text_div.append(txt);
var scrollHeight = text_div.parent().prop("scrollHeight");
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
} else { // line feed
text_div.append("<div class='out'>" + txt + "</div>");
var scrollHeight = text_div.parent().prop("scrollHeight");
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
}
}
//
// plugin functions
//
//
// Accept plugin onText events
var onText = function (args, kwargs) {
// If the message is not itself tagged, we'll assume it
// should go into any panes with 'all' or 'rest' set
var msgtype = "rest";
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);
}
}
var target_panes = [];
var rest_panes = [];
for (var key in split_panes) {
var pane = split_panes[key];
// is this message type mapped to this pane (or does the pane has an 'all' type)?
if (pane['types'].length > 0) {
if (pane['types'].includes(msgtype) || pane['types'].includes('all')) {
target_panes.push(key);
} else if (pane['types'].includes('rest')) {
// store rest-panes in case we have no explicit to send to
rest_panes.push(key);
}
} else {
// unassigned panes are assumed to be rest-panes too
rest_panes.push(key);
}
}
var ntargets = target_panes.length;
var nrests = rest_panes.length;
if (ntargets > 0) {
// we have explicit target panes to send to
for (var i=0; i<ntargets; i++) {
txtToPane(target_panes[i], args[0]);
}
return true;
} else if (nrests > 0) {
// no targets, send remainder to rest-panes/unassigned
for (var i=0; i<nrests; i++) {
txtToPane(rest_panes[i], args[0]);
}
return true;
}
// unhandled message
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': 'linefeed' };
// 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

@ -5,506 +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 options = {};
//
// 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() {
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");
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
closePopup("#optionsdialog");
closePopup("#helpdialog");
}
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 () {
var min_height = 50;
var max_height = 300;
var prev_text_len = 0;
// 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();
// 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;
}
}
}
prev_text_len = curr_text_len;
console.log('NO plugin handled this Keydown');
}
}();
// 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
}
// 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";
// 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 (renderto == "main") {
var mwin = $("#messagewindow");
var cls = kwargs == null ? 'out' : kwargs['cls'];
mwin.append("<div class='" + cls + "'>" + args[0] + "</div>");
mwin.animate({
scrollTop: document.getElementById("messagewindow").scrollHeight
}, 0);
onNewLine(args[0], null);
} else {
openPopup(renderto, args[0]);
}
}
//
// Evennia Public Event Handlers
//
// 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();
}
// 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 (("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);
}
// 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);
}
}
}
// 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');
}
// 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');
}
// 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');
}
// 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);
}
}
onText(["The connection was closed or lost."], {'cls': 'err'});
}
// 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);
}
//
// 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,
}
})();
//
// Register Events
// Webclient Initialization
//
// Event when client finishes loading
$(document).ready(function() {
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);
$("#inputfield").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
);
// Initialize all plugins
plugin_handler.init();
console.log("Completed Webclient setup");
});
})();

View file

@ -13,6 +13,10 @@ JQuery available.
<meta http-equiv="content-type", content="application/xhtml+xml; charset=UTF-8" />
<meta name="author" content="Evennia" />
<meta name="generator" content="Evennia" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- 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" %}>
@ -20,15 +24,23 @@ JQuery available.
<!-- Import JQuery and warn if there is a problem -->
{% block jquery_import %}
<script src="https://code.jquery.com/jquery-2.1.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript" charset="utf-8"></script>
{% endblock %}
<script type="text/javascript" charset="utf-8">
if(!window.jQuery) {
document.write("<div class='err'>jQuery library not found or the online version could not be reached.</div>");
document.write("<div class='err'>jQuery library not found or the online version could not be reached. Check so Javascript is not blocked in your browser.</div>");
}
</script>
<!-- This is will only fire if javascript is actually active -->
<script language="javascript" type="text/javascript">
$(document).ready(function() {
$('#noscript').remove();
$('#clientwrapper').removeClass('d-none');
})
</script>
<!-- Set up Websocket url and load the evennia.js library-->
<script language="javascript" type="text/javascript">
{% if websocket_enabled %}
@ -44,20 +56,42 @@ JQuery available.
{% endif %}
{% if websocket_url %}
var wsurl = "{{websocket_url}}:{{websocket_port}}";
var wsurl = "{{websocket_url}}";
{% else %}
var wsurl = "ws://" + this.location.hostname + ":{{websocket_port}}";
{% endif %}
</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>
<!-- 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>
<script type="text/javascript" charset="utf-8">
if(!window.Favico) {
document.write("<div class='err'>Favico.js library not found or the online version could not be reached. Check so Javascript is not blocked in your browser.</div>");
}
</script>
<!-- jQuery first, then Tether, then Bootstrap JS. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
{% block scripts %}
{% endblock %}
</head>
<body>
@ -80,10 +114,9 @@ JQuery available.
</div>
<!-- main client -->
<div id=clientwrapper>
<div id=clientwrapper class="d-none">
{% block client %}
{% endblock %}
</div>
</body>
</html>

View file

@ -6,45 +6,31 @@
- guilib_import - for using your own gui lib
-->
{% block client %}
<div id="wrapper">
<div id="toolbar">
<button id="optionsbutton" type="button" class="hidden">&#x2699;</button>
</div>
<div id="messagewindow" role="log"></div>
<div id="inputform">
<div id="prompt"></div>
<div id="inputcontrol">
<textarea id="inputfield" type="text"></textarea>
<input id="inputsend" type="button" value="&gt;"/>
</div>
</div>
<div id="inputsizer"></div>
</div>
<!-- Basic toolbar -->
<div id="toolbar"></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>
<!-- "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>
<div id="helpdialog" class="dialog">
<div class="dialogtitle">Help<span class="dialogclose">&times;</span></div>
<div class="dialogcontentparent">
<div class="dialogcontent">
</div>
<!-- "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>
<div id="inputcontrol" class="input-group">
<textarea id="inputfield" type="text" class="form-control"></textarea>
<span class="input-group-btn">
<button class="btn btn-large btn-outline-primary" id="inputsend" type="button" value="">&gt;</button>
</span>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}

View file

@ -5,5 +5,6 @@ webpage 'application'.
from django.conf.urls import *
from evennia.web.webclient import views as webclient_views
app_name = "webclient"
urlpatterns = [
url(r'^$', webclient_views.webclient, name="index")]

View file

@ -34,7 +34,13 @@ def _shared_login(request):
if webclient_uid:
# The webclient has previously registered a login to this browser_session
if not account.is_authenticated() and not website_uid:
account = AccountDB.objects.get(id=webclient_uid)
try:
account = AccountDB.objects.get(id=webclient_uid)
except AccountDB.DoesNotExist:
# this can happen e.g. for guest accounts or deletions
csession["website_authenticated_uid"] = False
csession["webclient_authenticated_uid"] = False
return
try:
# calls our custom authenticate in web/utils/backends.py
account = authenticate(autologin=account)