Merge with develop and fix merge conflicts
This commit is contained in:
commit
72f4fedcbe
148 changed files with 20005 additions and 2718 deletions
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
116
evennia/web/webclient/static/webclient/js/plugins/history.js
Normal file
116
evennia/web/webclient/static/webclient/js/plugins/history.js
Normal 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);
|
||||
154
evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js
Normal file
154
evennia/web/webclient/static/webclient/js/plugins/hotbuttons.js
Normal 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,
|
||||
}
|
||||
})());
|
||||
|
|
@ -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 it’s 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);
|
||||
35
evennia/web/webclient/static/webclient/js/plugins/oob.js
Normal file
35
evennia/web/webclient/static/webclient/js/plugins/oob.js
Normal 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);
|
||||
160
evennia/web/webclient/static/webclient/js/plugins/options.js
Normal file
160
evennia/web/webclient/static/webclient/js/plugins/options.js
Normal 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">',
|
||||
'⚙',
|
||||
'<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);
|
||||
101
evennia/web/webclient/static/webclient/js/plugins/popups.js
Normal file
101
evennia/web/webclient/static/webclient/js/plugins/popups.js
Normal 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">×</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);
|
||||
|
|
@ -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">⇹</button>') );
|
||||
toolbar.append( $('<button id="panebutton" type="button">⚙</button>') );
|
||||
toolbar.append( $('<button id="undobutton" type="button">↶</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);
|
||||
|
|
@ -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 it’s 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");
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">⚙</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=">"/>
|
||||
</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">×</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">×</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="">></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue