Start restructuring web presences
This commit is contained in:
parent
98a200533f
commit
dac2be3074
92 changed files with 212 additions and 122 deletions
151
evennia/web/static/webclient/css/goldenlayout.css
Normal file
151
evennia/web/static/webclient/css/goldenlayout.css
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
#clientwrapper #main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#clientwrapper #main #main-sub {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#optionsbutton {
|
||||
font-size: .9rem;
|
||||
width: .9rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body .lm_popout {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lm_header .lm_tab {
|
||||
padding-right: 6px;
|
||||
padding-left: 3px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 0px;
|
||||
margin-top: 1px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.lm_title {
|
||||
text-align: center;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.lm_tab.lm_active {
|
||||
padding-bottom: 5px;
|
||||
padding-top: 0px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.lm_close_tab {
|
||||
opacity: 1 !important;
|
||||
background-image: none !important;
|
||||
top: 0px !important;
|
||||
right: 0px !important;
|
||||
}
|
||||
|
||||
.lm_close_tab:before {
|
||||
font-size: 1.15em;
|
||||
width: 1.15em;
|
||||
content: "\2715";
|
||||
}
|
||||
|
||||
#typelist {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.typelistsub {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: max-content;
|
||||
background-color: #333333;
|
||||
line-height: .5em;
|
||||
padding: .3em;
|
||||
font-size: .9em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#renamebox {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#renameboxin {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#updatelist {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.updatelistsub {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: max-content;
|
||||
background-color: #333333;
|
||||
line-height: .5em;
|
||||
padding: .3em;
|
||||
font-size: .9em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#inputcontrol {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.inputwrap {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.inputfieldwrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.inputsend {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
width: 30px;
|
||||
color: white;
|
||||
background-color: black;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.inputfield {
|
||||
height: 100%;
|
||||
width: calc(100% - 30px);
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.inputfield:focus {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.glbutton {
|
||||
font-size: 0.6em;
|
||||
line-height: 1.3em;
|
||||
padding: .5em;
|
||||
}
|
||||
907
evennia/web/static/webclient/css/webclient.css
Normal file
907
evennia/web/static/webclient/css/webclient.css
Normal file
|
|
@ -0,0 +1,907 @@
|
|||
/* ---
|
||||
|
||||
Style sheet for Evennia's web client.
|
||||
|
||||
This should possibly somehow be incoorporated with the
|
||||
overall website theme in the future?
|
||||
|
||||
--- */
|
||||
|
||||
/* Overall element look */
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
body {
|
||||
background: #000;
|
||||
color: #ccc;
|
||||
font-size: .9em;
|
||||
font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace;
|
||||
line-height: 1.4em;
|
||||
overflow: hidden;
|
||||
}
|
||||
@media screen and (max-width: 480px) {
|
||||
body {
|
||||
font-size: .5rem;
|
||||
line-height: .7rem;
|
||||
}
|
||||
}
|
||||
|
||||
a:link, a:visited { color: inherit; }
|
||||
a:hover, a:active { color: inherit;
|
||||
font-weight: bold;}
|
||||
|
||||
/* Set this to e.g. bolder if wanting to have ansi-highlights bolden
|
||||
* stand-alone text.*/
|
||||
strong {font-weight: bold;}
|
||||
|
||||
div {margin:0px;}
|
||||
|
||||
.hidden { display: none; }
|
||||
|
||||
/* Utility messages (green) */
|
||||
.sys { color: #0f0 }
|
||||
|
||||
/* Messages echoed back after input */
|
||||
.inp { color: #555 }
|
||||
|
||||
/* Messages returned from the server (most messages) */
|
||||
.out {
|
||||
color: #aaa;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
/* Error messages (red) */
|
||||
.err { color: #f00; }
|
||||
|
||||
/* Prompt base (white) */
|
||||
.prompt {color: #fff }
|
||||
|
||||
.underline { text-decoration: underline; }
|
||||
|
||||
/* Inverse - this will make a dotted outline. is there a more
|
||||
* ANSI-similar way? */
|
||||
.inverse {
|
||||
outline-style: dotted;
|
||||
outline-color: invert;
|
||||
}
|
||||
|
||||
/* Create blinking text */
|
||||
.blink {
|
||||
animation: blink-animation 1s steps(5, start) infinite;
|
||||
-webkit-animation: blink-animation 1s steps(5, start) infinite;
|
||||
}
|
||||
@keyframes blink-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes blink-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Style specific classes corresponding to formatted, narative text. */
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.tabspace {
|
||||
white-space: pre;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
}
|
||||
|
||||
/* Container surrounding entire client */
|
||||
#clientwrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Main scrolling message area */
|
||||
|
||||
#messagewindow {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
#messagewindow {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Input field */
|
||||
#input {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#inputfield, #inputsizer {
|
||||
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;
|
||||
}
|
||||
#inputsend {
|
||||
height: 100%;
|
||||
}
|
||||
#inputcontrol {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#inputfield:focus {
|
||||
}
|
||||
|
||||
/* prompt area above input field */
|
||||
.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: 2rem;
|
||||
font-size: 2rem;
|
||||
color: #a6a6a6;
|
||||
background-color: transparent;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
#optionsbutton:hover {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#toolbar {
|
||||
position: fixed;
|
||||
top: .5rem;
|
||||
right: .5rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* No javascript warning */
|
||||
#connecting {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding: .5em .9em
|
||||
}
|
||||
|
||||
/* Dialog window */
|
||||
.dialog {
|
||||
display: none;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -75%);
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background-color: #fefefe;
|
||||
border: 1px solid #888;
|
||||
color: lightgray;
|
||||
background-color: #2c2c2c;
|
||||
|
||||
}
|
||||
|
||||
#optionsdialog {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#helpdialog {
|
||||
width: 725px;
|
||||
height: 65%;
|
||||
}
|
||||
|
||||
#helpdialog .dialogcontent {
|
||||
background-color: #1e1e1e;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dialogcontentparent {
|
||||
height: calc(100% - 44px - 40px);
|
||||
}
|
||||
|
||||
.dialogcontent {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dialogtitle {
|
||||
border-bottom: 1px solid #888;
|
||||
padding: 10px 20px;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
cursor: move;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
background-color: #595959;
|
||||
}
|
||||
|
||||
.dialogclose {
|
||||
color: #d5d5d5;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dialogclose:hover, .dialogclose:focus {
|
||||
color: black;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/* MXP links */
|
||||
#mxplink {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* XTERM256 colors */
|
||||
|
||||
.color-000 { color: #000000; }
|
||||
.color-001 { color: #800000; }
|
||||
.color-002 { color: #008000; }
|
||||
.color-003 { color: #808000; }
|
||||
.color-004 { color: #000080; }
|
||||
.color-005 { color: #800080; }
|
||||
.color-006 { color: #008080; }
|
||||
.color-007 { color: #c0c0c0; }
|
||||
.color-008 { color: #808080; }
|
||||
.color-009 { color: #ff0000; }
|
||||
.color-010 { color: #00ff00; }
|
||||
.color-011 { color: #ffff00; }
|
||||
.color-012 { color: #0000ff; }
|
||||
.color-013 { color: #ff00ff; }
|
||||
.color-014 { color: #00ffff; }
|
||||
.color-015 { color: #ffffff; }
|
||||
.color-016 { color: #000000; }
|
||||
.color-017 { color: #00005f; }
|
||||
.color-018 { color: #000087; }
|
||||
.color-019 { color: #0000af; }
|
||||
.color-020 { color: #0000df; }
|
||||
.color-021 { color: #0000ff; }
|
||||
.color-022 { color: #005f00; }
|
||||
.color-023 { color: #005f5f; }
|
||||
.color-024 { color: #005f87; }
|
||||
.color-025 { color: #005faf; }
|
||||
.color-026 { color: #005fdf; }
|
||||
.color-027 { color: #005fff; }
|
||||
.color-028 { color: #008700; }
|
||||
.color-029 { color: #00875f; }
|
||||
.color-030 { color: #008787; }
|
||||
.color-031 { color: #0087af; }
|
||||
.color-032 { color: #0087df; }
|
||||
.color-033 { color: #0087ff; }
|
||||
.color-034 { color: #00af00; }
|
||||
.color-035 { color: #00af5f; }
|
||||
.color-036 { color: #00af87; }
|
||||
.color-037 { color: #00afaf; }
|
||||
.color-038 { color: #00afdf; }
|
||||
.color-039 { color: #00afff; }
|
||||
.color-040 { color: #00df00; }
|
||||
.color-041 { color: #00df5f; }
|
||||
.color-042 { color: #00df87; }
|
||||
.color-043 { color: #00dfaf; }
|
||||
.color-044 { color: #00dfdf; }
|
||||
.color-045 { color: #00dfff; }
|
||||
.color-046 { color: #00ff00; }
|
||||
.color-047 { color: #00ff5f; }
|
||||
.color-048 { color: #00ff87; }
|
||||
.color-049 { color: #00ffaf; }
|
||||
.color-050 { color: #00ffdf; }
|
||||
.color-051 { color: #00ffff; }
|
||||
.color-052 { color: #5f0000; }
|
||||
.color-053 { color: #5f005f; }
|
||||
.color-054 { color: #5f0087; }
|
||||
.color-055 { color: #5f00af; }
|
||||
.color-056 { color: #5f00df; }
|
||||
.color-057 { color: #5f00ff; }
|
||||
.color-058 { color: #5f5f00; }
|
||||
.color-059 { color: #5f5f5f; }
|
||||
.color-060 { color: #5f5f87; }
|
||||
.color-061 { color: #5f5faf; }
|
||||
.color-062 { color: #5f5fdf; }
|
||||
.color-063 { color: #5f5fff; }
|
||||
.color-064 { color: #5f8700; }
|
||||
.color-065 { color: #5f875f; }
|
||||
.color-066 { color: #5f8787; }
|
||||
.color-067 { color: #5f87af; }
|
||||
.color-068 { color: #5f87df; }
|
||||
.color-069 { color: #5f87ff; }
|
||||
.color-070 { color: #5faf00; }
|
||||
.color-071 { color: #5faf5f; }
|
||||
.color-072 { color: #5faf87; }
|
||||
.color-073 { color: #5fafaf; }
|
||||
.color-074 { color: #5fafdf; }
|
||||
.color-075 { color: #5fafff; }
|
||||
.color-076 { color: #5fdf00; }
|
||||
.color-077 { color: #5fdf5f; }
|
||||
.color-078 { color: #5fdf87; }
|
||||
.color-079 { color: #5fdfaf; }
|
||||
.color-080 { color: #5fdfdf; }
|
||||
.color-081 { color: #5fdfff; }
|
||||
.color-082 { color: #5fff00; }
|
||||
.color-083 { color: #5fff5f; }
|
||||
.color-084 { color: #5fff87; }
|
||||
.color-085 { color: #5fffaf; }
|
||||
.color-086 { color: #5fffdf; }
|
||||
.color-087 { color: #5fffff; }
|
||||
.color-088 { color: #870000; }
|
||||
.color-089 { color: #87005f; }
|
||||
.color-090 { color: #870087; }
|
||||
.color-091 { color: #8700af; }
|
||||
.color-092 { color: #8700df; }
|
||||
.color-093 { color: #8700ff; }
|
||||
.color-094 { color: #875f00; }
|
||||
.color-095 { color: #875f5f; }
|
||||
.color-096 { color: #875f87; }
|
||||
.color-097 { color: #875faf; }
|
||||
.color-098 { color: #875fdf; }
|
||||
.color-099 { color: #875fff; }
|
||||
.color-100 { color: #878700; }
|
||||
.color-101 { color: #87875f; }
|
||||
.color-102 { color: #878787; }
|
||||
.color-103 { color: #8787af; }
|
||||
.color-104 { color: #8787df; }
|
||||
.color-105 { color: #8787ff; }
|
||||
.color-106 { color: #87af00; }
|
||||
.color-107 { color: #87af5f; }
|
||||
.color-108 { color: #87af87; }
|
||||
.color-109 { color: #87afaf; }
|
||||
.color-110 { color: #87afdf; }
|
||||
.color-111 { color: #87afff; }
|
||||
.color-112 { color: #87df00; }
|
||||
.color-113 { color: #87df5f; }
|
||||
.color-114 { color: #87df87; }
|
||||
.color-115 { color: #87dfaf; }
|
||||
.color-116 { color: #87dfdf; }
|
||||
.color-117 { color: #87dfff; }
|
||||
.color-118 { color: #87ff00; }
|
||||
.color-119 { color: #87ff5f; }
|
||||
.color-120 { color: #87ff87; }
|
||||
.color-121 { color: #87ffaf; }
|
||||
.color-122 { color: #87ffdf; }
|
||||
.color-123 { color: #87ffff; }
|
||||
.color-124 { color: #af0000; }
|
||||
.color-125 { color: #af005f; }
|
||||
.color-126 { color: #af0087; }
|
||||
.color-127 { color: #af00af; }
|
||||
.color-128 { color: #af00df; }
|
||||
.color-129 { color: #af00ff; }
|
||||
.color-130 { color: #af5f00; }
|
||||
.color-131 { color: #af5f5f; }
|
||||
.color-132 { color: #af5f87; }
|
||||
.color-133 { color: #af5faf; }
|
||||
.color-134 { color: #af5fdf; }
|
||||
.color-135 { color: #af5fff; }
|
||||
.color-136 { color: #af8700; }
|
||||
.color-137 { color: #af875f; }
|
||||
.color-138 { color: #af8787; }
|
||||
.color-139 { color: #af87af; }
|
||||
.color-140 { color: #af87df; }
|
||||
.color-141 { color: #af87ff; }
|
||||
.color-142 { color: #afaf00; }
|
||||
.color-143 { color: #afaf5f; }
|
||||
.color-144 { color: #afaf87; }
|
||||
.color-145 { color: #afafaf; }
|
||||
.color-146 { color: #afafdf; }
|
||||
.color-147 { color: #afafff; }
|
||||
.color-148 { color: #afdf00; }
|
||||
.color-149 { color: #afdf5f; }
|
||||
.color-150 { color: #afdf87; }
|
||||
.color-151 { color: #afdfaf; }
|
||||
.color-152 { color: #afdfdf; }
|
||||
.color-153 { color: #afdfff; }
|
||||
.color-154 { color: #afff00; }
|
||||
.color-155 { color: #afff5f; }
|
||||
.color-156 { color: #afff87; }
|
||||
.color-157 { color: #afffaf; }
|
||||
.color-158 { color: #afffdf; }
|
||||
.color-159 { color: #afffff; }
|
||||
.color-160 { color: #df0000; }
|
||||
.color-161 { color: #df005f; }
|
||||
.color-162 { color: #df0087; }
|
||||
.color-163 { color: #df00af; }
|
||||
.color-164 { color: #df00df; }
|
||||
.color-165 { color: #df00ff; }
|
||||
.color-166 { color: #df5f00; }
|
||||
.color-167 { color: #df5f5f; }
|
||||
.color-168 { color: #df5f87; }
|
||||
.color-169 { color: #df5faf; }
|
||||
.color-170 { color: #df5fdf; }
|
||||
.color-171 { color: #df5fff; }
|
||||
.color-172 { color: #df8700; }
|
||||
.color-173 { color: #df875f; }
|
||||
.color-174 { color: #df8787; }
|
||||
.color-175 { color: #df87af; }
|
||||
.color-176 { color: #df87df; }
|
||||
.color-177 { color: #df87ff; }
|
||||
.color-178 { color: #dfaf00; }
|
||||
.color-179 { color: #dfaf5f; }
|
||||
.color-180 { color: #dfaf87; }
|
||||
.color-181 { color: #dfafaf; }
|
||||
.color-182 { color: #dfafdf; }
|
||||
.color-183 { color: #dfafff; }
|
||||
.color-184 { color: #dfdf00; }
|
||||
.color-185 { color: #dfdf5f; }
|
||||
.color-186 { color: #dfdf87; }
|
||||
.color-187 { color: #dfdfaf; }
|
||||
.color-188 { color: #dfdfdf; }
|
||||
.color-189 { color: #dfdfff; }
|
||||
.color-190 { color: #dfff00; }
|
||||
.color-191 { color: #dfff5f; }
|
||||
.color-192 { color: #dfff87; }
|
||||
.color-193 { color: #dfffaf; }
|
||||
.color-194 { color: #dfffdf; }
|
||||
.color-195 { color: #dfffff; }
|
||||
.color-196 { color: #ff0000; }
|
||||
.color-197 { color: #ff005f; }
|
||||
.color-198 { color: #ff0087; }
|
||||
.color-199 { color: #ff00af; }
|
||||
.color-200 { color: #ff00df; }
|
||||
.color-201 { color: #ff00ff; }
|
||||
.color-202 { color: #ff5f00; }
|
||||
.color-203 { color: #ff5f5f; }
|
||||
.color-204 { color: #ff5f87; }
|
||||
.color-205 { color: #ff5faf; }
|
||||
.color-206 { color: #ff5fdf; }
|
||||
.color-207 { color: #ff5fff; }
|
||||
.color-208 { color: #ff8700; }
|
||||
.color-209 { color: #ff875f; }
|
||||
.color-210 { color: #ff8787; }
|
||||
.color-211 { color: #ff87af; }
|
||||
.color-212 { color: #ff87df; }
|
||||
.color-213 { color: #ff87ff; }
|
||||
.color-214 { color: #ffaf00; }
|
||||
.color-215 { color: #ffaf5f; }
|
||||
.color-216 { color: #ffaf87; }
|
||||
.color-217 { color: #ffafaf; }
|
||||
.color-218 { color: #ffafdf; }
|
||||
.color-219 { color: #ffafff; }
|
||||
.color-220 { color: #ffdf00; }
|
||||
.color-221 { color: #ffdf5f; }
|
||||
.color-222 { color: #ffdf87; }
|
||||
.color-223 { color: #ffdfaf; }
|
||||
.color-224 { color: #ffdfdf; }
|
||||
.color-225 { color: #ffdfff; }
|
||||
.color-226 { color: #ffff00; }
|
||||
.color-227 { color: #ffff5f; }
|
||||
.color-228 { color: #ffff87; }
|
||||
.color-229 { color: #ffffaf; }
|
||||
.color-230 { color: #ffffdf; }
|
||||
.color-231 { color: #ffffff; }
|
||||
.color-232 { color: #080808; }
|
||||
.color-233 { color: #121212; }
|
||||
.color-234 { color: #1c1c1c; }
|
||||
.color-235 { color: #262626; }
|
||||
.color-236 { color: #303030; }
|
||||
.color-237 { color: #3a3a3a; }
|
||||
.color-238 { color: #444444; }
|
||||
.color-239 { color: #4e4e4e; }
|
||||
.color-240 { color: #585858; }
|
||||
.color-241 { color: #606060; }
|
||||
.color-242 { color: #666666; }
|
||||
.color-243 { color: #767676; }
|
||||
.color-244 { color: #808080; }
|
||||
.color-245 { color: #8a8a8a; }
|
||||
.color-246 { color: #949494; }
|
||||
.color-247 { color: #9e9e9e; }
|
||||
.color-248 { color: #a8a8a8; }
|
||||
.color-249 { color: #b2b2b2; }
|
||||
.color-250 { color: #bcbcbc; }
|
||||
.color-251 { color: #c6c6c6; }
|
||||
.color-252 { color: #d0d0d0; }
|
||||
.color-253 { color: #dadada; }
|
||||
.color-254 { color: #e4e4e4; }
|
||||
.color-255 { color: #eeeeee; }
|
||||
.bgcolor-000 { background-color: #000000; }
|
||||
.bgcolor-001 { background-color: #800000; }
|
||||
.bgcolor-002 { background-color: #008000; }
|
||||
.bgcolor-003 { background-color: #808000; }
|
||||
.bgcolor-004 { background-color: #000080; }
|
||||
.bgcolor-005 { background-color: #800080; }
|
||||
.bgcolor-006 { background-color: #008080; }
|
||||
.bgcolor-007 { background-color: #c0c0c0; }
|
||||
.bgcolor-008 { background-color: #808080; }
|
||||
.bgcolor-009 { background-color: #ff0000; }
|
||||
.bgcolor-010 { background-color: #00ff00; }
|
||||
.bgcolor-011 { background-color: #ffff00; }
|
||||
.bgcolor-012 { background-color: #0000ff; }
|
||||
.bgcolor-013 { background-color: #ff00ff; }
|
||||
.bgcolor-014 { background-color: #00ffff; }
|
||||
.bgcolor-015 { background-color: #ffffff; }
|
||||
.bgcolor-016 { background-color: #000000; }
|
||||
.bgcolor-017 { background-color: #00005f; }
|
||||
.bgcolor-018 { background-color: #000087; }
|
||||
.bgcolor-019 { background-color: #0000af; }
|
||||
.bgcolor-020 { background-color: #0000df; }
|
||||
.bgcolor-021 { background-color: #0000ff; }
|
||||
.bgcolor-022 { background-color: #005f00; }
|
||||
.bgcolor-023 { background-color: #005f5f; }
|
||||
.bgcolor-024 { background-color: #005f87; }
|
||||
.bgcolor-025 { background-color: #005faf; }
|
||||
.bgcolor-026 { background-color: #005fdf; }
|
||||
.bgcolor-027 { background-color: #005fff; }
|
||||
.bgcolor-028 { background-color: #008700; }
|
||||
.bgcolor-029 { background-color: #00875f; }
|
||||
.bgcolor-030 { background-color: #008787; }
|
||||
.bgcolor-031 { background-color: #0087af; }
|
||||
.bgcolor-032 { background-color: #0087df; }
|
||||
.bgcolor-033 { background-color: #0087ff; }
|
||||
.bgcolor-034 { background-color: #00af00; }
|
||||
.bgcolor-035 { background-color: #00af5f; }
|
||||
.bgcolor-036 { background-color: #00af87; }
|
||||
.bgcolor-037 { background-color: #00afaf; }
|
||||
.bgcolor-038 { background-color: #00afdf; }
|
||||
.bgcolor-039 { background-color: #00afff; }
|
||||
.bgcolor-040 { background-color: #00df00; }
|
||||
.bgcolor-041 { background-color: #00df5f; }
|
||||
.bgcolor-042 { background-color: #00df87; }
|
||||
.bgcolor-043 { background-color: #00dfaf; }
|
||||
.bgcolor-044 { background-color: #00dfdf; }
|
||||
.bgcolor-000 { background-color: #000000; }
|
||||
.bgcolor-001 { background-color: #800000; }
|
||||
.bgcolor-002 { background-color: #008000; }
|
||||
.bgcolor-003 { background-color: #808000; }
|
||||
.bgcolor-004 { background-color: #000080; }
|
||||
.bgcolor-005 { background-color: #800080; }
|
||||
.bgcolor-006 { background-color: #008080; }
|
||||
.bgcolor-007 { background-color: #c0c0c0; }
|
||||
.bgcolor-008 { background-color: #808080; }
|
||||
.bgcolor-009 { background-color: #ff0000; }
|
||||
.bgcolor-010 { background-color: #00ff00; }
|
||||
.bgcolor-011 { background-color: #ffff00; }
|
||||
.bgcolor-012 { background-color: #0000ff; }
|
||||
.bgcolor-013 { background-color: #ff00ff; }
|
||||
.bgcolor-014 { background-color: #00ffff; }
|
||||
.bgcolor-015 { background-color: #ffffff; }
|
||||
.bgcolor-016 { background-color: #000000; }
|
||||
.bgcolor-017 { background-color: #00005f; }
|
||||
.bgcolor-018 { background-color: #000087; }
|
||||
.bgcolor-019 { background-color: #0000af; }
|
||||
.bgcolor-020 { background-color: #0000df; }
|
||||
.bgcolor-021 { background-color: #0000ff; }
|
||||
.bgcolor-022 { background-color: #005f00; }
|
||||
.bgcolor-023 { background-color: #005f5f; }
|
||||
.bgcolor-024 { background-color: #005f87; }
|
||||
.bgcolor-025 { background-color: #005faf; }
|
||||
.bgcolor-026 { background-color: #005fdf; }
|
||||
.bgcolor-027 { background-color: #005fff; }
|
||||
.bgcolor-028 { background-color: #008700; }
|
||||
.bgcolor-029 { background-color: #00875f; }
|
||||
.bgcolor-030 { background-color: #008787; }
|
||||
.bgcolor-031 { background-color: #0087af; }
|
||||
.bgcolor-032 { background-color: #0087df; }
|
||||
.bgcolor-033 { background-color: #0087ff; }
|
||||
.bgcolor-034 { background-color: #00af00; }
|
||||
.bgcolor-035 { background-color: #00af5f; }
|
||||
.bgcolor-036 { background-color: #00af87; }
|
||||
.bgcolor-037 { background-color: #00afaf; }
|
||||
.bgcolor-038 { background-color: #00afdf; }
|
||||
.bgcolor-039 { background-color: #00afff; }
|
||||
.bgcolor-040 { background-color: #00df00; }
|
||||
.bgcolor-041 { background-color: #00df5f; }
|
||||
.bgcolor-042 { background-color: #00df87; }
|
||||
.bgcolor-043 { background-color: #00dfaf; }
|
||||
.bgcolor-044 { background-color: #00dfdf; }
|
||||
.bgcolor-045 { background-color: #00dfff; }
|
||||
.bgcolor-046 { background-color: #00ff00; }
|
||||
.bgcolor-047 { background-color: #00ff5f; }
|
||||
.bgcolor-048 { background-color: #00ff87; }
|
||||
.bgcolor-049 { background-color: #00ffaf; }
|
||||
.bgcolor-050 { background-color: #00ffdf; }
|
||||
.bgcolor-051 { background-color: #00ffff; }
|
||||
.bgcolor-052 { background-color: #5f0000; }
|
||||
.bgcolor-053 { background-color: #5f005f; }
|
||||
.bgcolor-054 { background-color: #5f0087; }
|
||||
.bgcolor-055 { background-color: #5f00af; }
|
||||
.bgcolor-056 { background-color: #5f00df; }
|
||||
.bgcolor-057 { background-color: #5f00ff; }
|
||||
.bgcolor-058 { background-color: #5f5f00; }
|
||||
.bgcolor-059 { background-color: #5f5f5f; }
|
||||
.bgcolor-060 { background-color: #5f5f87; }
|
||||
.bgcolor-061 { background-color: #5f5faf; }
|
||||
.bgcolor-062 { background-color: #5f5fdf; }
|
||||
.bgcolor-063 { background-color: #5f5fff; }
|
||||
.bgcolor-064 { background-color: #5f8700; }
|
||||
.bgcolor-065 { background-color: #5f875f; }
|
||||
.bgcolor-066 { background-color: #5f8787; }
|
||||
.bgcolor-067 { background-color: #5f87af; }
|
||||
.bgcolor-068 { background-color: #5f87df; }
|
||||
.bgcolor-069 { background-color: #5f87ff; }
|
||||
.bgcolor-070 { background-color: #5faf00; }
|
||||
.bgcolor-071 { background-color: #5faf5f; }
|
||||
.bgcolor-072 { background-color: #5faf87; }
|
||||
.bgcolor-073 { background-color: #5fafaf; }
|
||||
.bgcolor-074 { background-color: #5fafdf; }
|
||||
.bgcolor-075 { background-color: #5fafff; }
|
||||
.bgcolor-076 { background-color: #5fdf00; }
|
||||
.bgcolor-077 { background-color: #5fdf5f; }
|
||||
.bgcolor-078 { background-color: #5fdf87; }
|
||||
.bgcolor-079 { background-color: #5fdfaf; }
|
||||
.bgcolor-080 { background-color: #5fdfdf; }
|
||||
.bgcolor-081 { background-color: #5fdfff; }
|
||||
.bgcolor-082 { background-color: #5fff00; }
|
||||
.bgcolor-083 { background-color: #5fff5f; }
|
||||
.bgcolor-084 { background-color: #5fff87; }
|
||||
.bgcolor-085 { background-color: #5fffaf; }
|
||||
.bgcolor-086 { background-color: #5fffdf; }
|
||||
.bgcolor-087 { background-color: #5fffff; }
|
||||
.bgcolor-088 { background-color: #870000; }
|
||||
.bgcolor-089 { background-color: #87005f; }
|
||||
.bgcolor-090 { background-color: #870087; }
|
||||
.bgcolor-091 { background-color: #8700af; }
|
||||
.bgcolor-092 { background-color: #8700df; }
|
||||
.bgcolor-093 { background-color: #8700ff; }
|
||||
.bgcolor-094 { background-color: #875f00; }
|
||||
.bgcolor-095 { background-color: #875f5f; }
|
||||
.bgcolor-096 { background-color: #875f87; }
|
||||
.bgcolor-097 { background-color: #875faf; }
|
||||
.bgcolor-098 { background-color: #875fdf; }
|
||||
.bgcolor-099 { background-color: #875fff; }
|
||||
.bgcolor-100 { background-color: #878700; }
|
||||
.bgcolor-101 { background-color: #87875f; }
|
||||
.bgcolor-102 { background-color: #878787; }
|
||||
.bgcolor-103 { background-color: #8787af; }
|
||||
.bgcolor-104 { background-color: #8787df; }
|
||||
.bgcolor-105 { background-color: #8787ff; }
|
||||
.bgcolor-106 { background-color: #87af00; }
|
||||
.bgcolor-107 { background-color: #87af5f; }
|
||||
.bgcolor-108 { background-color: #87af87; }
|
||||
.bgcolor-109 { background-color: #87afaf; }
|
||||
.bgcolor-110 { background-color: #87afdf; }
|
||||
.bgcolor-111 { background-color: #87afff; }
|
||||
.bgcolor-112 { background-color: #87df00; }
|
||||
.bgcolor-113 { background-color: #87df5f; }
|
||||
.bgcolor-114 { background-color: #87df87; }
|
||||
.bgcolor-115 { background-color: #87dfaf; }
|
||||
.bgcolor-116 { background-color: #87dfdf; }
|
||||
.bgcolor-117 { background-color: #87dfff; }
|
||||
.bgcolor-118 { background-color: #87ff00; }
|
||||
.bgcolor-119 { background-color: #87ff5f; }
|
||||
.bgcolor-120 { background-color: #87ff87; }
|
||||
.bgcolor-121 { background-color: #87ffaf; }
|
||||
.bgcolor-122 { background-color: #87ffdf; }
|
||||
.bgcolor-123 { background-color: #87ffff; }
|
||||
.bgcolor-124 { background-color: #af0000; }
|
||||
.bgcolor-125 { background-color: #af005f; }
|
||||
.bgcolor-126 { background-color: #af0087; }
|
||||
.bgcolor-127 { background-color: #af00af; }
|
||||
.bgcolor-128 { background-color: #af00df; }
|
||||
.bgcolor-129 { background-color: #af00ff; }
|
||||
.bgcolor-130 { background-color: #af5f00; }
|
||||
.bgcolor-131 { background-color: #af5f5f; }
|
||||
.bgcolor-132 { background-color: #af5f87; }
|
||||
.bgcolor-133 { background-color: #af5faf; }
|
||||
.bgcolor-134 { background-color: #af5fdf; }
|
||||
.bgcolor-135 { background-color: #af5fff; }
|
||||
.bgcolor-136 { background-color: #af8700; }
|
||||
.bgcolor-137 { background-color: #af875f; }
|
||||
.bgcolor-138 { background-color: #af8787; }
|
||||
.bgcolor-139 { background-color: #af87af; }
|
||||
.bgcolor-140 { background-color: #af87df; }
|
||||
.bgcolor-141 { background-color: #af87ff; }
|
||||
.bgcolor-142 { background-color: #afaf00; }
|
||||
.bgcolor-143 { background-color: #afaf5f; }
|
||||
.bgcolor-144 { background-color: #afaf87; }
|
||||
.bgcolor-145 { background-color: #afafaf; }
|
||||
.bgcolor-146 { background-color: #afafdf; }
|
||||
.bgcolor-147 { background-color: #afafff; }
|
||||
.bgcolor-148 { background-color: #afdf00; }
|
||||
.bgcolor-149 { background-color: #afdf5f; }
|
||||
.bgcolor-150 { background-color: #afdf87; }
|
||||
.bgcolor-151 { background-color: #afdfaf; }
|
||||
.bgcolor-152 { background-color: #afdfdf; }
|
||||
.bgcolor-153 { background-color: #afdfff; }
|
||||
.bgcolor-154 { background-color: #afff00; }
|
||||
.bgcolor-155 { background-color: #afff5f; }
|
||||
.bgcolor-156 { background-color: #afff87; }
|
||||
.bgcolor-157 { background-color: #afffaf; }
|
||||
.bgcolor-158 { background-color: #afffdf; }
|
||||
.bgcolor-159 { background-color: #afffff; }
|
||||
.bgcolor-160 { background-color: #df0000; }
|
||||
.bgcolor-161 { background-color: #df005f; }
|
||||
.bgcolor-162 { background-color: #df0087; }
|
||||
.bgcolor-163 { background-color: #df00af; }
|
||||
.bgcolor-164 { background-color: #df00df; }
|
||||
.bgcolor-165 { background-color: #df00ff; }
|
||||
.bgcolor-166 { background-color: #df5f00; }
|
||||
.bgcolor-167 { background-color: #df5f5f; }
|
||||
.bgcolor-168 { background-color: #df5f87; }
|
||||
.bgcolor-169 { background-color: #df5faf; }
|
||||
.bgcolor-170 { background-color: #df5fdf; }
|
||||
.bgcolor-171 { background-color: #df5fff; }
|
||||
.bgcolor-172 { background-color: #df8700; }
|
||||
.bgcolor-173 { background-color: #df875f; }
|
||||
.bgcolor-174 { background-color: #df8787; }
|
||||
.bgcolor-175 { background-color: #df87af; }
|
||||
.bgcolor-176 { background-color: #df87df; }
|
||||
.bgcolor-177 { background-color: #df87ff; }
|
||||
.bgcolor-178 { background-color: #dfaf00; }
|
||||
.bgcolor-179 { background-color: #dfaf5f; }
|
||||
.bgcolor-180 { background-color: #dfaf87; }
|
||||
.bgcolor-181 { background-color: #dfafaf; }
|
||||
.bgcolor-182 { background-color: #dfafdf; }
|
||||
.bgcolor-183 { background-color: #dfafff; }
|
||||
.bgcolor-184 { background-color: #dfdf00; }
|
||||
.bgcolor-185 { background-color: #dfdf5f; }
|
||||
.bgcolor-186 { background-color: #dfdf87; }
|
||||
.bgcolor-187 { background-color: #dfdfaf; }
|
||||
.bgcolor-188 { background-color: #dfdfdf; }
|
||||
.bgcolor-189 { background-color: #dfdfff; }
|
||||
.bgcolor-190 { background-color: #dfff00; }
|
||||
.bgcolor-191 { background-color: #dfff5f; }
|
||||
.bgcolor-192 { background-color: #dfff87; }
|
||||
.bgcolor-193 { background-color: #dfffaf; }
|
||||
.bgcolor-194 { background-color: #dfffdf; }
|
||||
.bgcolor-195 { background-color: #dfffff; }
|
||||
.bgcolor-196 { background-color: #ff0000; }
|
||||
.bgcolor-197 { background-color: #ff005f; }
|
||||
.bgcolor-198 { background-color: #ff0087; }
|
||||
.bgcolor-199 { background-color: #ff00af; }
|
||||
.bgcolor-200 { background-color: #ff00df; }
|
||||
.bgcolor-201 { background-color: #ff00ff; }
|
||||
.bgcolor-202 { background-color: #ff5f00; }
|
||||
.bgcolor-203 { background-color: #ff5f5f; }
|
||||
.bgcolor-204 { background-color: #ff5f87; }
|
||||
.bgcolor-205 { background-color: #ff5faf; }
|
||||
.bgcolor-206 { background-color: #ff5fdf; }
|
||||
.bgcolor-207 { background-color: #ff5fff; }
|
||||
.bgcolor-208 { background-color: #ff8700; }
|
||||
.bgcolor-209 { background-color: #ff875f; }
|
||||
.bgcolor-210 { background-color: #ff8787; }
|
||||
.bgcolor-211 { background-color: #ff87af; }
|
||||
.bgcolor-212 { background-color: #ff87df; }
|
||||
.bgcolor-213 { background-color: #ff87ff; }
|
||||
.bgcolor-214 { background-color: #ffaf00; }
|
||||
.bgcolor-215 { background-color: #ffaf5f; }
|
||||
.bgcolor-216 { background-color: #ffaf87; }
|
||||
.bgcolor-217 { background-color: #ffafaf; }
|
||||
.bgcolor-218 { background-color: #ffafdf; }
|
||||
.bgcolor-219 { background-color: #ffafff; }
|
||||
.bgcolor-220 { background-color: #ffdf00; }
|
||||
.bgcolor-221 { background-color: #ffdf5f; }
|
||||
.bgcolor-222 { background-color: #ffdf87; }
|
||||
.bgcolor-223 { background-color: #ffdfaf; }
|
||||
.bgcolor-224 { background-color: #ffdfdf; }
|
||||
.bgcolor-225 { background-color: #ffdfff; }
|
||||
.bgcolor-226 { background-color: #ffff00; }
|
||||
.bgcolor-227 { background-color: #ffff5f; }
|
||||
.bgcolor-228 { background-color: #ffff87; }
|
||||
.bgcolor-229 { background-color: #ffffaf; }
|
||||
.bgcolor-230 { background-color: #ffffdf; }
|
||||
.bgcolor-231 { background-color: #ffffff; }
|
||||
.bgcolor-232 { background-color: #080808; }
|
||||
.bgcolor-233 { background-color: #121212; }
|
||||
.bgcolor-234 { background-color: #1c1c1c; }
|
||||
.bgcolor-235 { background-color: #262626; }
|
||||
.bgcolor-236 { background-color: #303030; }
|
||||
.bgcolor-237 { background-color: #3a3a3a; }
|
||||
.bgcolor-238 { background-color: #444444; }
|
||||
.bgcolor-239 { background-color: #4e4e4e; }
|
||||
.bgcolor-240 { background-color: #585858; }
|
||||
.bgcolor-241 { background-color: #606060; }
|
||||
.bgcolor-242 { background-color: #666666; }
|
||||
.bgcolor-243 { background-color: #767676; }
|
||||
.bgcolor-244 { background-color: #808080; }
|
||||
.bgcolor-245 { background-color: #8a8a8a; }
|
||||
.bgcolor-246 { background-color: #949494; }
|
||||
.bgcolor-247 { background-color: #9e9e9e; }
|
||||
.bgcolor-248 { background-color: #a8a8a8; }
|
||||
.bgcolor-249 { background-color: #b2b2b2; }
|
||||
.bgcolor-250 { background-color: #bcbcbc; }
|
||||
.bgcolor-251 { background-color: #c6c6c6; }
|
||||
.bgcolor-252 { background-color: #d0d0d0; }
|
||||
.bgcolor-253 { background-color: #dadada; }
|
||||
.bgcolor-254 { background-color: #e4e4e4; }
|
||||
.bgcolor-255 { background-color: #eeeeee; }
|
||||
BIN
evennia/web/static/webclient/fonts/DejaVuSansMono-webfont.woff
Normal file
BIN
evennia/web/static/webclient/fonts/DejaVuSansMono-webfont.woff
Normal file
Binary file not shown.
6
evennia/web/static/webclient/fonts/DejaVuSansMono.css
Normal file
6
evennia/web/static/webclient/fonts/DejaVuSansMono.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@font-face {
|
||||
font-family: 'DejaVu Sans Mono';
|
||||
src: url('/static/webclient/fonts/DejaVuSansMono-webfont.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
462
evennia/web/static/webclient/js/evennia.js
Normal file
462
evennia/web/static/webclient/js/evennia.js
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
Evenna webclient library
|
||||
|
||||
This javascript library handles all communication between Evennia and
|
||||
whatever client front end is used.
|
||||
|
||||
The library will try to communicate with Evennia using websockets
|
||||
(evennia/server/portal/webclient.py). However, if the web browser is
|
||||
old and does not support websockets, it will instead fall back to a
|
||||
long-polling (AJAX/COMET) type of connection (using
|
||||
evennia/server/portal/webclient_ajax.py)
|
||||
|
||||
All messages are valid JSON arrays on this single form:
|
||||
|
||||
["cmdname", args, kwargs],
|
||||
|
||||
where args is an JSON array and kwargs is a JSON object. These will be both
|
||||
used as arguments emitted to a callback named "cmdname" as cmdname(args, kwargs).
|
||||
|
||||
This library makes the "Evennia" object available. It has the
|
||||
following official functions:
|
||||
|
||||
- Evennia.init(options)
|
||||
This stores the connections/emitters and creates the websocket/ajax connection.
|
||||
This can be called as often as desired - the lib will still only be
|
||||
initialized once. The argument is an js object with the following possible keys:
|
||||
'connection': This defaults to Evennia.WebsocketConnection but
|
||||
can also be set to Evennia.CometConnection for backwards
|
||||
compatibility. See below.
|
||||
'emitter': An optional custom command handler for distributing
|
||||
data from the server to suitable listeners. If not given,
|
||||
a default will be used.
|
||||
- Evennia.msg(funcname, [args,...], callback)
|
||||
Send a command to the server. You can also provide a function
|
||||
to call with the return of the call (note that commands will
|
||||
not return anything unless specified to do so server-side).
|
||||
|
||||
A "connection" object must have the method
|
||||
- msg(data) - this should relay data to the Server. This function should itself handle
|
||||
the conversion to JSON before sending across the wire.
|
||||
- When receiving data from the Server (always data = [cmdname, args, kwargs]), this must be
|
||||
JSON-unpacked and the result redirected to Evennia.emit(data[0], data[1], data[2]).
|
||||
An "emitter" object must have a function
|
||||
- emit(cmdname, args, kwargs) - this will be called by the backend and is expected to
|
||||
relay the data to its correct gui element.
|
||||
- The default emitter also has the following methods:
|
||||
- on(cmdname, listener) - this ties a listener to the backend. This function
|
||||
should be called as listener(args, kwargs) when the backend calls emit.
|
||||
- off(cmdname) - remove the listener for this cmdname.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
"use strict"
|
||||
var cmdid = 0;
|
||||
var cmdmap = {};
|
||||
|
||||
var Evennia = {
|
||||
|
||||
debug: true,
|
||||
initialized: false,
|
||||
|
||||
// Initialize the Evennia object with emitter and connection.
|
||||
//
|
||||
// Args:
|
||||
// opts (obj):
|
||||
// emitter - custom emitter. If not given,
|
||||
// will use a default emitter. Must have
|
||||
// an "emit" function.
|
||||
// connection - This defaults to using either
|
||||
// a WebsocketConnection or a AjaxCometConnection
|
||||
// depending on what the browser supports. If given
|
||||
// it must have a 'msg' method and make use of
|
||||
// Evennia.emit to return data to Client.
|
||||
//
|
||||
init: function(opts) {
|
||||
if (this.initialized) {
|
||||
// make it safe to call multiple times.
|
||||
return;
|
||||
}
|
||||
this.initialized = true;
|
||||
|
||||
opts = opts || {};
|
||||
this.emitter = opts.emitter || new DefaultEmitter();
|
||||
|
||||
if (opts.connection) {
|
||||
this.connection = opts.connection;
|
||||
}
|
||||
else if (window.WebSocket && wsactive) {
|
||||
this.connection = new WebsocketConnection();
|
||||
} else {
|
||||
this.connection = new AjaxCometConnection();
|
||||
}
|
||||
log('Evennia initialized.')
|
||||
},
|
||||
|
||||
// Connect to the Evennia server.
|
||||
// Re-establishes the connection after it is lost.
|
||||
//
|
||||
connect: function() {
|
||||
if (this.connection.isOpen()) {
|
||||
// Already connected.
|
||||
return;
|
||||
}
|
||||
this.connection.connect();
|
||||
log('Evennia reconnecting.')
|
||||
},
|
||||
|
||||
// Returns true if the connection is open.
|
||||
//
|
||||
isConnected: function () {
|
||||
return this.connection.isOpen();
|
||||
},
|
||||
|
||||
// client -> Evennia.
|
||||
// called by the frontend to send a command to Evennia.
|
||||
//
|
||||
// args:
|
||||
// cmdname (str): String identifier to call
|
||||
// kwargs (obj): Data argument for calling as cmdname(kwargs)
|
||||
// callback (func): If given, will be given an eventual return
|
||||
// value from the backend.
|
||||
//
|
||||
msg: function (cmdname, args, kwargs, callback) {
|
||||
if (!cmdname) {
|
||||
return;
|
||||
}
|
||||
if (kwargs) {
|
||||
kwargs.cmdid = cmdid++;
|
||||
}
|
||||
var outargs = args ? args : [];
|
||||
var outkwargs = kwargs ? kwargs : {};
|
||||
var data = [cmdname, outargs, outkwargs];
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
cmdmap[cmdid] = callback;
|
||||
}
|
||||
this.connection.msg(data);
|
||||
|
||||
},
|
||||
|
||||
// Evennia -> Client.
|
||||
// Called by the backend to send the data to the
|
||||
// emitter, which in turn distributes it to its
|
||||
// listener(s).
|
||||
//
|
||||
// Args:
|
||||
// event (event): Event received from Evennia
|
||||
// args (array): Arguments to listener
|
||||
// kwargs (obj): keyword-args to listener
|
||||
//
|
||||
emit: function (cmdname, args, kwargs) {
|
||||
if (kwargs.cmdid) {
|
||||
cmdmap[kwargs.cmdid].apply(this, [args, kwargs]);
|
||||
delete cmdmap[kwargs.cmdid];
|
||||
}
|
||||
else {
|
||||
this.emitter.emit(cmdname, args, kwargs);
|
||||
}
|
||||
},
|
||||
|
||||
}; // end of evennia object
|
||||
|
||||
|
||||
// Basic emitter to distribute data being sent to the client from
|
||||
// the Server. An alternative can be overridden by giving it
|
||||
// in Evennia.init({emitter:myemitter})
|
||||
//
|
||||
var DefaultEmitter = function () {
|
||||
var listeners = {};
|
||||
// Emit data to all listeners tied to a given cmdname.
|
||||
// If the cmdname is not recognized, call a listener
|
||||
// named 'default' with arguments [cmdname, args, kwargs].
|
||||
// If no 'default' is found, ignore silently.
|
||||
//
|
||||
// Args:
|
||||
// cmdname (str): Name of command, used to find
|
||||
// all listeners to this call; each will be
|
||||
// called as function(kwargs).
|
||||
// kwargs (obj): Argument to the listener.
|
||||
//
|
||||
var emit = function (cmdname, args, kwargs) {
|
||||
if (listeners[cmdname]) {
|
||||
listeners[cmdname].apply(this, [args, kwargs]);
|
||||
}
|
||||
else if (listeners["default"]) {
|
||||
listeners["default"].apply(this, [cmdname, args, kwargs]);
|
||||
}
|
||||
};
|
||||
|
||||
// Bind listener to event
|
||||
//
|
||||
// Args:
|
||||
// cmdname (str): Name of event to handle.
|
||||
// listener (function): Function taking one argument,
|
||||
// to listen to cmdname events.
|
||||
//
|
||||
var on = function (cmdname, listener) {
|
||||
if (typeof(listener === 'function')) {
|
||||
listeners[cmdname] = listener;
|
||||
};
|
||||
};
|
||||
|
||||
// remove handling of this cmdname
|
||||
//
|
||||
// Args:
|
||||
// cmdname (str): Name of event to handle
|
||||
//
|
||||
var off = function (cmdname) {
|
||||
delete listeners[cmdname]
|
||||
};
|
||||
return {emit:emit, on:on, off:off};
|
||||
};
|
||||
|
||||
// Websocket Connector
|
||||
//
|
||||
var WebsocketConnection = function () {
|
||||
log("Trying websocket ...");
|
||||
var open = false;
|
||||
var ever_open = false;
|
||||
var websocket = null;
|
||||
var wsurl = window.wsurl;
|
||||
var csessid = window.csessid;
|
||||
|
||||
var connect = function() {
|
||||
if (websocket && websocket.readyState != websocket.CLOSED) {
|
||||
// No-op if a connection is already open.
|
||||
return;
|
||||
}
|
||||
// Important - we pass csessid tacked on the url
|
||||
websocket = new WebSocket(wsurl + '?' + csessid);
|
||||
|
||||
// Handle Websocket open event
|
||||
websocket.onopen = function (event) {
|
||||
open = true;
|
||||
ever_open = true;
|
||||
Evennia.emit('connection_open', ["websocket"], event);
|
||||
};
|
||||
// Handle Websocket close event
|
||||
websocket.onclose = function (event) {
|
||||
if (ever_open) {
|
||||
// only emit if websocket was ever open at all
|
||||
Evennia.emit('connection_close', ["websocket"], event);
|
||||
}
|
||||
open = false;
|
||||
};
|
||||
// Handle websocket errors
|
||||
websocket.onerror = function (event) {
|
||||
if (websocket.readyState === websocket.CLOSED) {
|
||||
if (ever_open) {
|
||||
// only emit if websocket was ever open at all.
|
||||
log("Websocket failed.")
|
||||
Evennia.emit('connection_error', ["websocket"], event);
|
||||
}
|
||||
else {
|
||||
// only fall back to AJAX if we never got an open socket.
|
||||
log("Websocket failed. Falling back to Ajax...");
|
||||
Evennia.connection = AjaxCometConnection();
|
||||
}
|
||||
open = false;
|
||||
}
|
||||
};
|
||||
// Handle incoming websocket data [cmdname, args, kwargs]
|
||||
websocket.onmessage = function (event) {
|
||||
var data = event.data;
|
||||
if (typeof data !== 'string' && data.length < 0) {
|
||||
return;
|
||||
}
|
||||
// Parse the incoming data, send to emitter
|
||||
// Incoming data is on the form [cmdname, args, kwargs]
|
||||
data = JSON.parse(data);
|
||||
// console.log(" server->client:", data)
|
||||
Evennia.emit(data[0], data[1], data[2]);
|
||||
};
|
||||
}
|
||||
|
||||
var msg = function(data) {
|
||||
// send data across the wire. Make sure to json it.
|
||||
// console.log("client->server:", data)
|
||||
websocket.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
var close = function() {
|
||||
// tell the server this connection is closing (usually
|
||||
// tied to when the client window is closed). This
|
||||
// Makes use of a websocket-protocol specific instruction.
|
||||
websocket.send(JSON.stringify(["websocket_close", [], {}]));
|
||||
open = false;
|
||||
}
|
||||
|
||||
var isOpen = function() {
|
||||
return open;
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
return {connect: connect, msg: msg, close: close, isOpen: isOpen};
|
||||
};
|
||||
|
||||
// AJAX/COMET Connector
|
||||
//
|
||||
var AjaxCometConnection = function() {
|
||||
log("Trying ajax ...");
|
||||
var open = false;
|
||||
var stop_polling = false;
|
||||
var is_closing = false;
|
||||
var csessid = window.csessid;
|
||||
|
||||
// initialize connection, send csessid
|
||||
var init = function() {
|
||||
$.ajax({type: "POST", url: "/webclientdata",
|
||||
async: true, cache: false, timeout: 50000,
|
||||
datatype: "json",
|
||||
data: {mode: "init", csessid: csessid},
|
||||
|
||||
success: function(data) {
|
||||
open = true;
|
||||
data = JSON.parse(data);
|
||||
log ("connection_open", ["AJAX/COMET"], data);
|
||||
stop_polling = false;
|
||||
poll();
|
||||
},
|
||||
error: function(req, stat, err) {
|
||||
Evennia.emit("connection_error", ["AJAX/COMET init error"], err);
|
||||
// Also emit a close event so that the COMET API mirrors the websocket API.
|
||||
Evennia.emit("connection_close", ["AJAX/COMET init close"], err);
|
||||
log("AJAX/COMET: Connection error: " + err);
|
||||
stop_polling = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Send Client -> Evennia. Called by Evennia.msg
|
||||
var msg = function(data, inmode) {
|
||||
// log("ajax.msg:", data, JSON.stringify(data));
|
||||
$.ajax({type: "POST", url: "/webclientdata",
|
||||
async: true, cache: false, timeout: 30000,
|
||||
dataType: "json",
|
||||
data: {mode: inmode == null ? 'input' : inmode,
|
||||
data: JSON.stringify(data), 'csessid': csessid},
|
||||
success: function(req, stat, err) {
|
||||
stop_polling = false;
|
||||
},
|
||||
error: function(req, stat, err) {
|
||||
Evennia.emit("connection_error", ["AJAX/COMET send error"], err);
|
||||
log("AJAX/COMET: Server returned error.",req,stat,err);
|
||||
stop_polling = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Receive Evennia -> Client. This will start an asynchronous
|
||||
// Long-polling request. It will either timeout or receive data
|
||||
// from the 'webclientdata' url. Either way a new polling request
|
||||
// will immediately be started.
|
||||
var poll = function() {
|
||||
$.ajax({type: "POST", url: "/webclientdata",
|
||||
async: true, cache: false, timeout: 60000,
|
||||
dataType: "json",
|
||||
data: {mode: 'receive', 'csessid': csessid},
|
||||
success: function(data) {
|
||||
// log("ajax data received:", data);
|
||||
if (data[0] === "ajax_keepalive") {
|
||||
// special ajax keepalive check - return immediately
|
||||
msg("", "keepalive");
|
||||
} else {
|
||||
// not a keepalive
|
||||
Evennia.emit(data[0], data[1], data[2]);
|
||||
}
|
||||
stop_polling = false;
|
||||
poll(); // immiately start a new request
|
||||
},
|
||||
error: function(req, stat, err) {
|
||||
if (stat !== "timeout") {
|
||||
// Any other error besides a timeout is abnormal
|
||||
Evennia.emit("connection_error", ["AJAX/COMET receive error"], err);
|
||||
log("AJAX/COMET: Server returned error on receive.",req,stat,err);
|
||||
stop_polling = true;
|
||||
}
|
||||
else {
|
||||
// We'd expect to see a keepalive message rather than
|
||||
// a timeout. Ping the server to see if it's still there.
|
||||
msg(["text", ["idle"], {}], "input");
|
||||
}
|
||||
|
||||
if (stop_polling) {
|
||||
// An error of some kind occurred.
|
||||
// Close the connection, if possible.
|
||||
close();
|
||||
}
|
||||
else {
|
||||
poll(); // timeout; immediately re-poll
|
||||
// don't trigger an emit event here,
|
||||
// this is normal for ajax/comet
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Kill the connection and do house cleaning on the server.
|
||||
var close = function webclient_close(){
|
||||
if (is_closing || !(open)) {
|
||||
// Already closed or trying to close.
|
||||
return;
|
||||
}
|
||||
stop_polling = true;
|
||||
is_closing = true;
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/webclientdata",
|
||||
async: true,
|
||||
cache: false,
|
||||
timeout: 50000,
|
||||
dataType: "json",
|
||||
data: {mode: 'close', 'csessid': csessid},
|
||||
|
||||
success: function(data){
|
||||
is_closing = false;
|
||||
open = false;
|
||||
Evennia.emit("connection_close", ["AJAX/COMET"], {});
|
||||
log("AJAX/COMET connection closed cleanly.")
|
||||
},
|
||||
error: function(req, stat, err){
|
||||
is_closing = false;
|
||||
Evennia.emit("connection_error", ["AJAX/COMET close error"], err);
|
||||
// Also emit a close event so that the COMET API mirrors the websocket API.
|
||||
Evennia.emit("connection_close", ["AJAX/COMET close unclean"], err);
|
||||
open = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var isOpen = function () {
|
||||
return !(is_closing || !(open));
|
||||
}
|
||||
|
||||
// init
|
||||
init();
|
||||
|
||||
return {connect: init, msg: msg, close: close, isOpen: isOpen};
|
||||
};
|
||||
|
||||
window.Evennia = Evennia;
|
||||
|
||||
})(); // end of auto-calling Evennia object defintion
|
||||
|
||||
// helper logging function (requires a js dev-console in the browser)
|
||||
function log() {
|
||||
if (Evennia.debug) {
|
||||
console.log(JSON.stringify(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
// Called when page has finished loading (kicks the client into gear)
|
||||
$(document).ready(function() {
|
||||
setTimeout( function () {
|
||||
// the short timeout supposedly causes the load indicator
|
||||
// in Chrome to stop spinning
|
||||
Evennia.init()
|
||||
},
|
||||
500
|
||||
);
|
||||
});
|
||||
25
evennia/web/static/webclient/js/plugins/clienthelp.js
Normal file
25
evennia/web/static/webclient/js/plugins/clienthelp.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
*
|
||||
* Evennia Webclient help plugin
|
||||
*
|
||||
*/
|
||||
let clienthelp_plugin = (function () {
|
||||
//
|
||||
//
|
||||
//
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
var help_text = $( [
|
||||
"<div style='font-weight: bold;'>",
|
||||
"<a href='http://evennia.com'>Evennia</a>",
|
||||
" Webclient Settings:",
|
||||
"</div>"
|
||||
].join(""));
|
||||
parentdiv.append(help_text);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {},
|
||||
onOptionsUI: onOptionsUI,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("clienthelp", clienthelp_plugin);
|
||||
98
evennia/web/static/webclient/js/plugins/default_in.js
Normal file
98
evennia/web/static/webclient/js/plugins/default_in.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
*
|
||||
* Evennia Webclient default "send-text-on-enter-key" IO plugin
|
||||
*
|
||||
*/
|
||||
let defaultInPlugin = (function () {
|
||||
|
||||
var focusOnKeydown = true;
|
||||
|
||||
//
|
||||
// handle the default <enter> key triggering onSend()
|
||||
var onKeydown = function (event) {
|
||||
// find where the key comes from
|
||||
var inputfield = $(".inputfield:focus");
|
||||
|
||||
if( inputfield.length < 1 ) { // non-goldenlayout backwards compatibility
|
||||
inputfield = $("#inputfield:focus");
|
||||
}
|
||||
|
||||
// check for important keys
|
||||
switch (event.which) {
|
||||
case 9: // ignore tab key -- allows normal focus control
|
||||
case 16: // ignore shift
|
||||
case 17: // ignore alt
|
||||
case 18: // ignore control
|
||||
case 20: // ignore caps lock
|
||||
case 144: // ignore num lock
|
||||
break;
|
||||
|
||||
case 13: // Enter key
|
||||
var outtext = inputfield.val() || ""; // Grab the text from which-ever inputfield is focused
|
||||
if ( !event.shiftKey ) { // Enter Key without shift --> send Mesg
|
||||
var lines = outtext.replace(/[\r]+/,"\n").replace(/[\n]+/, "\n").split("\n");
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
plugin_handler.onSend( lines[i] );
|
||||
}
|
||||
inputfield.val(""); // Clear this inputfield
|
||||
event.preventDefault();
|
||||
|
||||
// enter key by itself should toggle focus
|
||||
if( inputfield.length < 1 ) {
|
||||
inputfield = $(".inputfield:last");
|
||||
inputfield.focus();
|
||||
if( inputfield.length < 1 ) { // non-goldenlayout backwards compatibility
|
||||
$("#inputfield").focus();
|
||||
}
|
||||
} else {
|
||||
inputfield.blur();
|
||||
}
|
||||
} // else allow building a multi-line input command
|
||||
break;
|
||||
|
||||
// Anything else, focus() a textarea if needed, and allow the default event
|
||||
default:
|
||||
// has some other UI element turned off this behavior temporarily?
|
||||
if( focusOnKeydown ) {
|
||||
// is an inputfield actually focused?
|
||||
if( inputfield.length < 1 ) {
|
||||
// Nope, focus the last .inputfield found in the DOM (or #inputfield)
|
||||
// :last only matters if multi-input plugins are in use
|
||||
inputfield = $(".inputfield:last");
|
||||
inputfield.focus();
|
||||
if( inputfield.length < 1 ) { // non-goldenlayout backwards compatibility
|
||||
$("#inputfield").focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// allow other UI elements to toggle this focus behavior on/off
|
||||
var setKeydownFocus = function (bool) {
|
||||
focusOnKeydown = bool;
|
||||
}
|
||||
|
||||
//
|
||||
// Mandatory plugin init function
|
||||
var init = function () {
|
||||
// Handle pressing the send button, this only applies to non-goldenlayout setups
|
||||
$("#inputsend")
|
||||
.bind("click", function (evnt) {
|
||||
// simulate a carriage return
|
||||
var e = $.Event( "keydown" );
|
||||
e.which = 13;
|
||||
$("#inputfield").focus().trigger(e);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onKeydown: onKeydown,
|
||||
setKeydownFocus: setKeydownFocus,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("default_in", defaultInPlugin);
|
||||
69
evennia/web/static/webclient/js/plugins/default_out.js
Normal file
69
evennia/web/static/webclient/js/plugins/default_out.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
*
|
||||
* 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 on every input pane
|
||||
var prompts = $('.prompt');
|
||||
|
||||
for( var x=0; x < prompts.length; x++ ) {
|
||||
var prmpt = $( prompts[x] );
|
||||
var sibling = prmpt.siblings().first();
|
||||
|
||||
prmpt.addClass("out")
|
||||
.html(args[0])
|
||||
.css({'height':'1.5em'});
|
||||
|
||||
sibling.css({'height':'calc(100% - 1.5em)'});
|
||||
}
|
||||
|
||||
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);
|
||||
17
evennia/web/static/webclient/js/plugins/default_unload.js
Normal file
17
evennia/web/static/webclient/js/plugins/default_unload.js
Normal 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);
|
||||
124
evennia/web/static/webclient/js/plugins/font.js
Normal file
124
evennia/web/static/webclient/js/plugins/font.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
*
|
||||
* Evennia Webclient default "send-text-on-enter-key" IO plugin
|
||||
*
|
||||
*/
|
||||
let font_plugin = (function () {
|
||||
|
||||
const font_urls = {
|
||||
'B612 Mono': 'https://fonts.googleapis.com/css?family=B612+Mono&display=swap',
|
||||
'Consolas': 'https://fonts.googleapis.com/css?family=Consolas&display=swap',
|
||||
'DejaVu Sans Mono': '/static/webclient/fonts/DejaVuSansMono.css',
|
||||
'Fira Mono': 'https://fonts.googleapis.com/css?family=Fira+Mono&display=swap',
|
||||
'Inconsolata': 'https://fonts.googleapis.com/css?family=Inconsolata&display=swap',
|
||||
'Monospace': '',
|
||||
'Roboto Mono': 'https://fonts.googleapis.com/css?family=Roboto+Mono&display=swap',
|
||||
'Source Code Pro': 'https://fonts.googleapis.com/css?family=Source+Code+Pro&display=swap',
|
||||
'Ubuntu Mono': 'https://fonts.googleapis.com/css?family=Ubuntu+Mono&display=swap',
|
||||
};
|
||||
|
||||
//
|
||||
//
|
||||
var setStartingFont = function () {
|
||||
var fontfamily = localStorage.getItem("evenniaFontFamily");
|
||||
if( !fontfamily ) {
|
||||
$(document.body).css("font-family", fontfamily);
|
||||
}
|
||||
|
||||
var fontsize = localStorage.getItem("evenniaFontSize");
|
||||
if( !fontsize ) {
|
||||
$(document.body).css("font-size", fontsize+"em");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var getActiveFontFamily = function () {
|
||||
var family = "DejaVu Sans Mono";
|
||||
var fontfamily = localStorage.getItem("evenniaFontFamily");
|
||||
if( fontfamily != null ) {
|
||||
family = fontfamily;
|
||||
}
|
||||
return family;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var getActiveFontSize = function () {
|
||||
var size = "0.9";
|
||||
var fontsize = localStorage.getItem("evenniaFontSize");
|
||||
if( fontsize != null ) {
|
||||
size = fontsize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var onFontFamily = function (evnt) {
|
||||
var family = $(evnt.target).val();
|
||||
$(document.body).css("font-family", family);
|
||||
localStorage.setItem("evenniaFontFamily", family);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var onFontSize = function (evnt) {
|
||||
var size = $(evnt.target).val();
|
||||
$(document.body).css("font-size", size+"em");
|
||||
localStorage.setItem("evenniaFontSize", size);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
var fontselect = $("<select>");
|
||||
var sizeselect = $("<select>");
|
||||
|
||||
var fonts = Object.keys(font_urls);
|
||||
for( const font of fonts ) {
|
||||
fontselect.append( $("<option value='"+font+"'>"+font+"</option>") );
|
||||
}
|
||||
|
||||
for (var x = 4; x < 21; x++) {
|
||||
var val = (x/10.0);
|
||||
sizeselect.append( $("<option value='"+val+"'>"+x+"</option>") );
|
||||
}
|
||||
|
||||
fontselect.val( getActiveFontFamily() );
|
||||
sizeselect.val( getActiveFontSize() );
|
||||
|
||||
// font change callbacks
|
||||
fontselect.on("change", onFontFamily);
|
||||
sizeselect.on("change", onFontSize);
|
||||
|
||||
// add the font selection dialog control to our parentdiv
|
||||
parentdiv.append("<div style='font-weight: bold'>Font Selection:</div>");
|
||||
parentdiv.append(fontselect);
|
||||
parentdiv.append(sizeselect);
|
||||
}
|
||||
|
||||
//
|
||||
// Font plugin init function (adds the urls for the webfonts to the page)
|
||||
//
|
||||
var init = function () {
|
||||
var head = $(document.head);
|
||||
|
||||
var fonts = Object.keys(font_urls);
|
||||
for (var x = 0; x < fonts.length; x++) {
|
||||
if ( fonts[x] != "Monospace" ) {
|
||||
var url = font_urls[ fonts[x] ];
|
||||
var link = $("<link href='"+url+"' rel='stylesheet'>");
|
||||
head.append( link );
|
||||
}
|
||||
}
|
||||
|
||||
setStartingFont();
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onOptionsUI: onOptionsUI,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("font", font_plugin);
|
||||
821
evennia/web/static/webclient/js/plugins/goldenlayout.js
Normal file
821
evennia/web/static/webclient/js/plugins/goldenlayout.js
Normal file
|
|
@ -0,0 +1,821 @@
|
|||
/*
|
||||
*
|
||||
* Golden Layout plugin
|
||||
*
|
||||
*/
|
||||
let goldenlayout = (function () {
|
||||
|
||||
var myLayout; // The actively used GoldenLayout API object.
|
||||
|
||||
var evenniaGoldenLayouts = new Map(); // key/value Map for each selectable layout.
|
||||
var activeLayoutName = "default"; // The object key of the active evenniaGoldenLayout
|
||||
var activeLayoutModified = false; // Has the active layout been modified by the user, without being saved?
|
||||
|
||||
var knownTypes = ["all", "untagged", "testing"];
|
||||
var untagged = [];
|
||||
|
||||
var newTabConfig = {
|
||||
title: "Untitled",
|
||||
type: "component",
|
||||
componentName: "evennia",
|
||||
tooltip: "Click and drag tabs to make new panes",
|
||||
componentState: {
|
||||
types: "all",
|
||||
updateMethod: "newlines",
|
||||
},
|
||||
};
|
||||
|
||||
var newInputConfig = {
|
||||
title: "input",
|
||||
type: "component",
|
||||
componentName: "input",
|
||||
id: "inputComponent",
|
||||
};
|
||||
|
||||
// helper function: filter vals out of array
|
||||
function filter (vals, array) {
|
||||
if( Array.isArray( vals ) && Array.isArray( array ) ) {
|
||||
let tmp = array.slice();
|
||||
vals.forEach( function (val) {
|
||||
while( tmp.indexOf(val) > -1 ) {
|
||||
tmp.splice( tmp.indexOf(val), 1 );
|
||||
}
|
||||
});
|
||||
return tmp;
|
||||
}
|
||||
// pass along whatever we got, since our arguments aren't right.
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Calculate all knownTypes minus the "all" type,
|
||||
// then filter out all types that have been mapped to a pane.
|
||||
var calculateUntaggedTypes = function () {
|
||||
// set initial untagged list
|
||||
untagged = filter( ["all", "untagged"], knownTypes);
|
||||
// for each .content pane
|
||||
$(".content").each( function () {
|
||||
let types = $(this).attr("types");
|
||||
if ( typeof types !== "undefined" ) {
|
||||
let typesArray = types.split(" ");
|
||||
// add our types to known types so that the onText function don't add them to untagged later
|
||||
knownTypes = Array.from(new Set([...knownTypes, ...typesArray]));
|
||||
// remove our types from the untagged array
|
||||
untagged = filter( typesArray, untagged );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var closeRenameDropdown = function () {
|
||||
let content = $("#renamebox").parent().parent().parent().parent()[0];
|
||||
let title = $("#renameboxin").val();
|
||||
|
||||
let components = myLayout.root.getItemsByType("component");
|
||||
|
||||
components.forEach( function (component) {
|
||||
let element = component.tab.header.parent.element[0];
|
||||
if( (element === content) && (component.tab.isActive) ) {
|
||||
component.setTitle( title );
|
||||
}
|
||||
});
|
||||
|
||||
myLayout.emit("stateChanged");
|
||||
$("#renamebox").remove();
|
||||
window.plugins["default_in"].setKeydownFocus(true);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var closeTypelistDropdown = function () {
|
||||
let content = $("#typelist").parent().find(".content");
|
||||
let checkboxes = $("#typelist :input");
|
||||
|
||||
let types = [];
|
||||
checkboxes.each( function (idx) {
|
||||
if( $(checkboxes[idx]).prop("checked") ) {
|
||||
types.push( $(checkboxes[idx]).val() );
|
||||
}
|
||||
});
|
||||
|
||||
content.attr("types", types.join(" "));
|
||||
myLayout.emit("stateChanged");
|
||||
|
||||
calculateUntaggedTypes();
|
||||
$("#typelist").remove();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var closeUpdatelistDropdown = function () {
|
||||
let content = $("#updatelist").parent().find(".content");
|
||||
let value = $("input[name=upmethod]:checked").val();
|
||||
|
||||
content.attr("updateMethod", value );
|
||||
myLayout.emit("stateChanged");
|
||||
$("#updatelist").remove();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle the renameDropdown
|
||||
var renameDropdown = function (evnt) {
|
||||
let element = $(evnt.data.contentItem.element);
|
||||
let content = element.find(".content");
|
||||
let title = evnt.data.contentItem.config.title;
|
||||
let renamebox = document.getElementById("renamebox");
|
||||
|
||||
// check that no other dropdown is open
|
||||
if( document.getElementById("typelist") ) {
|
||||
closeTypelistDropdown();
|
||||
}
|
||||
|
||||
if( document.getElementById("updatelist") ) {
|
||||
closeUpdatelistDropdown();
|
||||
}
|
||||
|
||||
if( !renamebox ) {
|
||||
renamebox = $("<div id='renamebox'>");
|
||||
renamebox.append("<input type='textbox' id='renameboxin' value='"+title+"'>");
|
||||
renamebox.insertBefore( content );
|
||||
window.plugins["default_in"].setKeydownFocus(false);
|
||||
} else {
|
||||
closeRenameDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onSelectTypesClicked = function (evnt) {
|
||||
let element = $(evnt.data.contentItem.element);
|
||||
let content = element.find(".content");
|
||||
let selectedTypes = content.attr("types");
|
||||
let menu = $("<div id='typelist'>");
|
||||
let div = $("<div class='typelistsub'>");
|
||||
|
||||
if( selectedTypes ) {
|
||||
selectedTypes = selectedTypes.split(" ");
|
||||
}
|
||||
knownTypes.forEach( function (itype) {
|
||||
let choice;
|
||||
if( selectedTypes && selectedTypes.includes(itype) ) {
|
||||
choice = $("<label><input type='checkbox' value='"+itype+"' checked='checked'/>"+itype+"</label>");
|
||||
} else {
|
||||
choice = $("<label><input type='checkbox' value='"+itype+"'/>"+itype+"</label>");
|
||||
}
|
||||
choice.appendTo(div);
|
||||
});
|
||||
div.appendTo(menu);
|
||||
|
||||
element.prepend(menu);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle the typeDropdown
|
||||
var typeDropdown = function (evnt) {
|
||||
let typelist = document.getElementById("typelist");
|
||||
|
||||
// check that no other dropdown is open
|
||||
if( document.getElementById("renamebox") ) {
|
||||
closeRenameDropdown();
|
||||
}
|
||||
|
||||
if( document.getElementById("updatelist") ) {
|
||||
closeUpdatelistDropdown();
|
||||
}
|
||||
|
||||
if( !typelist ) {
|
||||
onSelectTypesClicked(evnt);
|
||||
} else {
|
||||
closeTypelistDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onUpdateMethodClicked = function (evnt) {
|
||||
let element = $(evnt.data.contentItem.element);
|
||||
let content = element.find(".content");
|
||||
let updateMethod = content.attr("updateMethod");
|
||||
let nlchecked = (updateMethod === "newlines") ? "checked='checked'" : "";
|
||||
let apchecked = (updateMethod === "append") ? "checked='checked'" : "";
|
||||
let rpchecked = (updateMethod === "replace") ? "checked='checked'" : "";
|
||||
|
||||
let menu = $("<div id='updatelist'>");
|
||||
let div = $("<div class='updatelistsub'>");
|
||||
|
||||
let newlines = $("<label><input type='radio' name='upmethod' value='newlines' "+nlchecked+"/>Newlines</label>");
|
||||
let append = $("<label><input type='radio' name='upmethod' value='append' "+apchecked+"/>Append</label>");
|
||||
let replace = $("<label><input type='radio' name='upmethod' value='replace' "+rpchecked+"/>Replace</label>");
|
||||
|
||||
newlines.appendTo(div);
|
||||
append.appendTo(div);
|
||||
replace.appendTo(div);
|
||||
|
||||
div.appendTo(menu);
|
||||
|
||||
element.prepend(menu);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle the updateDropdown
|
||||
var updateDropdown = function (evnt) {
|
||||
let updatelist = document.getElementById("updatelist");
|
||||
|
||||
// check that no other dropdown is open
|
||||
if( document.getElementById("renamebox") ) {
|
||||
closeRenameDropdown();
|
||||
}
|
||||
|
||||
if( document.getElementById("typelist") ) {
|
||||
closeTypelistDropdown();
|
||||
}
|
||||
|
||||
if( !updatelist ) {
|
||||
onUpdateMethodClicked(evnt);
|
||||
} else {
|
||||
closeUpdatelistDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onActiveTabChange = function (tab) {
|
||||
let renamebox = document.getElementById("renamebox");
|
||||
let typelist = document.getElementById("typelist");
|
||||
let updatelist = document.getElementById("updatelist");
|
||||
|
||||
if( renamebox ) {
|
||||
closeRenameDropdown();
|
||||
}
|
||||
|
||||
if( typelist ) {
|
||||
closeTypelistDropdown();
|
||||
}
|
||||
|
||||
if( updatelist ) {
|
||||
closeUpdatelistDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Save the GoldenLayout state to localstorage whenever it changes.
|
||||
var onStateChanged = function () {
|
||||
let components = myLayout.root.getItemsByType("component");
|
||||
components.forEach( function (component) {
|
||||
if( component.hasId("inputComponent") ) { return; } // ignore input components
|
||||
|
||||
let textDiv = component.container.getElement().children(".content");
|
||||
let types = textDiv.attr("types");
|
||||
let updateMethod = textDiv.attr("updateMethod");
|
||||
component.container.extendState({ "types": types, "updateMethod": updateMethod });
|
||||
});
|
||||
|
||||
// update localstorage
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedState", JSON.stringify(myLayout.toConfig()) );
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedStateName", activeLayoutName );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onClearLocalstorage = function (evnt) {
|
||||
myLayout.off( "stateChanged", onStateChanged );
|
||||
localStorage.removeItem( "evenniaGoldenLayoutSavedState" );
|
||||
localStorage.removeItem( "evenniaGoldenLayoutSavedStateName" );
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var scrollAll = function () {
|
||||
let components = myLayout.root.getItemsByType("component");
|
||||
components.forEach( function (component) {
|
||||
if( component.hasId("inputComponent") ) { return; } // ignore input components
|
||||
|
||||
let textDiv = component.container.getElement().children(".content");
|
||||
let scrollHeight = textDiv.prop("scrollHeight");
|
||||
let clientHeight = textDiv.prop("clientHeight");
|
||||
textDiv.scrollTop( scrollHeight - clientHeight );
|
||||
});
|
||||
myLayout.updateSize();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onTabCreate = function (tab) {
|
||||
//HTML for the typeDropdown
|
||||
let renameDropdownControl = $("<span class='lm_title' style='font-size: 1.5em;width: 0.5em;'>🢒</span>");
|
||||
let typeDropdownControl = $("<span class='lm_title' style='font-size: 1.0em;width: 1em;'>⯁</span>");
|
||||
let updateDropdownControl = $("<span class='lm_title' style='font-size: 1.0em;width: 1em;'>⯈</span>");
|
||||
let splitControl = $("<span class='lm_title' style='font-size: 1.5em;width: 1em;'>+</span>");
|
||||
// track dropdowns when the associated control is clicked
|
||||
renameDropdownControl.click( tab, renameDropdown );
|
||||
|
||||
typeDropdownControl.click( tab, typeDropdown );
|
||||
|
||||
updateDropdownControl.click( tab, updateDropdown );
|
||||
|
||||
// track adding a new tab
|
||||
splitControl.click( tab, function (evnt) {
|
||||
evnt.data.header.parent.addChild( newTabConfig );
|
||||
});
|
||||
|
||||
// Add the typeDropdown to the header
|
||||
tab.element.prepend( renameDropdownControl );
|
||||
tab.element.append( typeDropdownControl );
|
||||
tab.element.append( updateDropdownControl );
|
||||
tab.element.append( splitControl );
|
||||
|
||||
if( tab.contentItem.config.componentName === "Main" ) {
|
||||
tab.element.prepend( $("#optionsbutton").clone(true).addClass("lm_title") );
|
||||
}
|
||||
|
||||
tab.header.parent.on( "activeContentItemChanged", onActiveTabChange );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onInputCreate = function (tab) {
|
||||
//HTML for the typeDropdown
|
||||
let splitControl = $("<span class='lm_title' style='font-size: 1.5em;width: 1em;'>+</span>");
|
||||
|
||||
// track adding a new tab
|
||||
splitControl.click( tab, function (evnt) {
|
||||
evnt.data.header.parent.addChild( newInputConfig );
|
||||
});
|
||||
|
||||
// Add the typeDropdown to the header
|
||||
tab.element.append( splitControl );
|
||||
|
||||
tab.header.parent.on( "activeContentItemChanged", onActiveTabChange );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var initComponent = function (div, container, state, defaultTypes, updateMethod) {
|
||||
// set this container"s content div types attribute
|
||||
if( state ) {
|
||||
div.attr("types", state.types);
|
||||
div.attr("updateMethod", state.updateMethod);
|
||||
} else {
|
||||
div.attr("types", defaultTypes);
|
||||
div.attr("updateMethod", updateMethod);
|
||||
}
|
||||
div.appendTo( container.getElement() );
|
||||
container.on("tab", onTabCreate);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var registerComponents = function (myLayout) {
|
||||
|
||||
// register our component and replace the default messagewindow with the Main component
|
||||
myLayout.registerComponent( "Main", function (container, componentState) {
|
||||
let main = $("#messagewindow").addClass("content");
|
||||
initComponent(main, container, componentState, "untagged", "newlines" );
|
||||
});
|
||||
|
||||
// register our input component
|
||||
myLayout.registerComponent( "input", function (container, componentState) {
|
||||
var promptfield = $("<div class='prompt'></div>");
|
||||
var formcontrol = $("<textarea type='text' class='inputfield form-control'></textarea>");
|
||||
var button = $("<button type='button' class='inputsend'>></button>");
|
||||
|
||||
var inputfield = $("<div class='inputfieldwrapper'>")
|
||||
.append( button )
|
||||
.append( formcontrol );
|
||||
|
||||
$("<div class='inputwrap'>")
|
||||
.append( promptfield )
|
||||
.append( inputfield )
|
||||
.appendTo( container.getElement() );
|
||||
|
||||
button.bind("click", function (evnt) {
|
||||
// focus our textarea
|
||||
$( $(evnt.target).siblings(".inputfield")[0] ).focus();
|
||||
// fake a carriage return event
|
||||
var e = $.Event("keydown");
|
||||
e.which = 13;
|
||||
$( $(evnt.target).siblings(".inputfield")[0] ).trigger(e);
|
||||
});
|
||||
|
||||
container.on("tab", onInputCreate);
|
||||
});
|
||||
|
||||
// register the generic "evennia" component
|
||||
myLayout.registerComponent( "evennia", function (container, componentState) {
|
||||
let div = $("<div class='content'></div>");
|
||||
initComponent(div, container, componentState, "all", "newlines");
|
||||
container.on("destroy", calculateUntaggedTypes);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var resetUI = function (newLayout) {
|
||||
var mainsub = document.getElementById("main-sub");
|
||||
|
||||
// rebuild the original HTML stacking
|
||||
var messageDiv = $("#messagewindow").detach();
|
||||
messageDiv.prependTo( mainsub );
|
||||
|
||||
// out with the old
|
||||
myLayout.destroy();
|
||||
|
||||
// in with the new
|
||||
myLayout = new window.GoldenLayout( newLayout, mainsub );
|
||||
|
||||
// re-register our main, input and generic evennia components.
|
||||
registerComponents( myLayout );
|
||||
|
||||
// call all other plugins to give them a chance to registerComponents.
|
||||
for( let plugin in window.plugins ) {
|
||||
if( "onLayoutChanged" in window.plugins[plugin] ) {
|
||||
window.plugins[plugin].onLayoutChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// finish the setup and actually start GoldenLayout
|
||||
myLayout.init();
|
||||
|
||||
// work out which types are untagged based on our pre-configured layout
|
||||
calculateUntaggedTypes();
|
||||
|
||||
// Set the Event handler for when the client window changes size
|
||||
$(window).bind("resize", scrollAll);
|
||||
|
||||
// Set Save State callback
|
||||
myLayout.on( "stateChanged", onStateChanged );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onSwitchLayout = function (evnt) {
|
||||
// get the new layout name from the select box
|
||||
var name = $(evnt.target).val();
|
||||
var saveButton = $(".savelayout");
|
||||
|
||||
// check to see if the layout is in the list of known layouts
|
||||
if( evenniaGoldenLayouts.has(name) ) {
|
||||
var newLayout = evenniaGoldenLayouts.get(name);
|
||||
|
||||
// reset the activeLayout
|
||||
activeLayoutName = name;
|
||||
activeLayoutModified = false;
|
||||
|
||||
if( activeLayoutName === "default" ) {
|
||||
saveButton.prop( "disabled", true );
|
||||
} else {
|
||||
saveButton.prop( "disabled", false );
|
||||
}
|
||||
|
||||
// store the newly requested layout into localStorage.
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedState", JSON.stringify(newLayout) );
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedStateName", activeLayoutName );
|
||||
|
||||
// pull the trigger
|
||||
resetUI( newLayout );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// upload the named layout to the Evennia server as an option
|
||||
var uploadLayouts = function () {
|
||||
if( window.Evennia.isConnected() && myLayout.isInitialised ) {
|
||||
var obj = {};
|
||||
|
||||
// iterate over each layout, storing the json for each into our temp obj
|
||||
for( const key of evenniaGoldenLayouts.keys() ) {
|
||||
if( key !== "default" ) {
|
||||
obj[key] = JSON.stringify( evenniaGoldenLayouts.get(key) );
|
||||
}
|
||||
}
|
||||
|
||||
// store our temp object as json out to window.options.webclientLayouts
|
||||
window.options["webclientActiveLayout"] = activeLayoutName;
|
||||
window.options["webclientLayouts"] = JSON.stringify( obj );
|
||||
window.Evennia.msg("webclient_options", [], window.options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onRemoveLayout = function (evnt) {
|
||||
var name = $(evnt.target).parent().attr("id");
|
||||
var layout = $("#"+name);
|
||||
|
||||
evenniaGoldenLayouts.delete(name);
|
||||
layout.remove();
|
||||
|
||||
uploadLayouts();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// This is a helper function for when adding items from the OptionsUI's layout listing
|
||||
var addLayoutUI = function (layoutDiv, name) {
|
||||
var div = $("<div id='"+name+"' >");
|
||||
|
||||
var option = $("<input type='button' class='goldenlayout' value='"+name+"'>");
|
||||
option.on("click", onSwitchLayout);
|
||||
div.append(option);
|
||||
|
||||
if( name !== "default" && name !== activeLayoutName ) {
|
||||
var remove = $("<input type='button' class='removelayout' value='X'>");
|
||||
remove.on("click", onRemoveLayout);
|
||||
div.append(remove);
|
||||
}
|
||||
|
||||
layoutDiv.append(div);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onSaveLayout = function () {
|
||||
// get the name from the select box
|
||||
var name = $("#layoutName").val();
|
||||
var layouts = $("#goldenlayouts");
|
||||
|
||||
// make sure we have a valid name
|
||||
if( name !== "" ) {
|
||||
// Is this name new or pre-existing?
|
||||
if( !evenniaGoldenLayouts.has(name) ) {
|
||||
// this is a new name, so add a new UI item for it.
|
||||
addLayoutUI( layouts, name );
|
||||
}
|
||||
|
||||
// Force Close the Options Menu so that it isn't part of the saved layout.
|
||||
window.plugins["options2"].onOpenCloseOptions();
|
||||
|
||||
// store the current layout to the local list of layouts
|
||||
evenniaGoldenLayouts.set( name, myLayout.toConfig() );
|
||||
activeLayoutName = name;
|
||||
activeLayoutModified = false;
|
||||
|
||||
// store the newly requested layout into localStorage.
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedState", JSON.stringify( evenniaGoldenLayouts.get(name) ) );
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedStateName", activeLayoutName );
|
||||
|
||||
uploadLayouts();
|
||||
|
||||
resetUI( evenniaGoldenLayouts.get(name) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public
|
||||
//
|
||||
|
||||
//
|
||||
// helper accessor for other plugins to add new known-message types
|
||||
var addKnownType = function (newtype) {
|
||||
if( knownTypes.includes(newtype) == false ) {
|
||||
knownTypes.push(newtype);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Add new HTML message to an existing Div pane, while
|
||||
// honoring the pane's updateMethod and scroll state, etc.
|
||||
//
|
||||
var addMessageToPaneDiv = function (textDiv, message) {
|
||||
let atBottom = false;
|
||||
let updateMethod = textDiv.attr("updateMethod");
|
||||
|
||||
if ( updateMethod === "replace" ) {
|
||||
textDiv.html(message);
|
||||
} else if ( updateMethod === "append" ) {
|
||||
textDiv.append(message);
|
||||
} else { // line feed
|
||||
textDiv.append("<div class='out'>" + message + "</div>");
|
||||
}
|
||||
|
||||
// Calculate the scrollback state.
|
||||
//
|
||||
// This check helps us avoid scrolling to the bottom when someone is
|
||||
// manually scrolled back, trying to read their backlog.
|
||||
// Auto-scrolling would force them to re-scroll to their previous scroll position.
|
||||
// Which, on fast updating games, destroys the utility of scrolling entirely.
|
||||
//
|
||||
//if( textDiv.scrollTop === (textDiv.scrollHeight - textDiv.offsetHeight) ) {
|
||||
atBottom = true;
|
||||
//}
|
||||
|
||||
// if we are at the bottom of the window already, scroll to display the new content
|
||||
if( atBottom ) {
|
||||
let scrollHeight = textDiv.prop("scrollHeight");
|
||||
let clientHeight = textDiv.prop("clientHeight");
|
||||
textDiv.scrollTop( scrollHeight - clientHeight );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// returns an array of pane divs that the given message should be sent to
|
||||
//
|
||||
var routeMessage = function (args, kwargs) {
|
||||
// If the message is not itself tagged, we"ll assume it
|
||||
// should go into any panes with "all" and "untagged" set
|
||||
var divArray = [];
|
||||
var msgtype = "untagged";
|
||||
|
||||
if ( kwargs && "type" in kwargs ) {
|
||||
msgtype = kwargs["type"];
|
||||
if ( ! knownTypes.includes(msgtype) ) {
|
||||
// this is a new output type that can be mapped to panes
|
||||
knownTypes.push(msgtype);
|
||||
untagged.push(msgtype);
|
||||
}
|
||||
}
|
||||
|
||||
let components = myLayout.root.getItemsByType("component");
|
||||
components.forEach( function (component) {
|
||||
if( component.hasId("inputComponent") ) { return; } // ignore input components
|
||||
|
||||
let destDiv = component.container.getElement().children(".content");
|
||||
let attrTypes = destDiv.attr("types");
|
||||
let paneTypes = attrTypes ? attrTypes.split(" ") : [];
|
||||
|
||||
// is this message type listed in this pane"s types (or is this pane catching "all")
|
||||
if( paneTypes.includes(msgtype) || paneTypes.includes("all") ) {
|
||||
divArray.push(destDiv);
|
||||
}
|
||||
|
||||
// is this pane catching "upmapped" messages?
|
||||
// And is this message type listed in the untagged types array?
|
||||
if( paneTypes.includes("untagged") && untagged.includes(msgtype) ) {
|
||||
divArray.push(destDiv);
|
||||
}
|
||||
});
|
||||
|
||||
return divArray;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onGotOptions = function (args, kwargs) {
|
||||
// Reset the UI if the JSON layout sent from the server doesn't match the client's current JSON
|
||||
if( "webclientLayouts" in kwargs ) {
|
||||
var layouts = JSON.parse( kwargs["webclientLayouts"] );
|
||||
|
||||
// deserialize key/layout pairs into evenniaGoldenLayouts
|
||||
for( var key in layouts ) {
|
||||
if( key !== "default" && layouts.hasOwnProperty(key) ) { // codacy.com guard-rail
|
||||
evenniaGoldenLayouts.set( key, JSON.parse(layouts[key]) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
var layoutName = $("<input id='layoutName' type='text' class='layoutName'>");
|
||||
var saveButton = $("<input type='button' class='savelayout' value='Close Options and Save'>");
|
||||
var layoutDiv = $("<div id='goldenlayouts'>");
|
||||
|
||||
if( activeLayoutName === "default" ) {
|
||||
saveButton.prop( "disabled", true );
|
||||
}
|
||||
|
||||
for (const name of evenniaGoldenLayouts.keys() ) {
|
||||
addLayoutUI(layoutDiv, name);
|
||||
}
|
||||
|
||||
// currently active layout
|
||||
layoutName.val( activeLayoutName );
|
||||
layoutName.on("keydown", function (evnt) {
|
||||
var name = $(evnt.target).val();
|
||||
if( name === "default" || name === "" ) {
|
||||
saveButton.prop( "disabled", true );
|
||||
} else {
|
||||
saveButton.prop( "disabled", false );
|
||||
}
|
||||
});
|
||||
|
||||
// Layout selection on-change callback
|
||||
saveButton.on("click", onSaveLayout);
|
||||
|
||||
var saveDiv = $("<div class='goldenlayout-save-ui'>");
|
||||
saveDiv.append(layoutName);
|
||||
saveDiv.append(saveButton);
|
||||
|
||||
// add the selection dialog control to our parentdiv
|
||||
parentdiv.addClass("goldenlayout-options-ui");
|
||||
parentdiv.append("<div>GoldenLayout Options:</div>");
|
||||
parentdiv.append("<div>Activate a new layout:</div>");
|
||||
parentdiv.append(layoutDiv);
|
||||
parentdiv.append("<div>Save current layout as (best if used when logged in):</div>");
|
||||
parentdiv.append(saveDiv);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var onText = function (args, kwargs) {
|
||||
// are any panes set to receive this text message?
|
||||
var divs = routeMessage(args, kwargs);
|
||||
|
||||
var msgHandled = false;
|
||||
divs.forEach( function (div) {
|
||||
let txt = args[0];
|
||||
// yes, so add this text message to the target div
|
||||
addMessageToPaneDiv( div, txt );
|
||||
msgHandled = true;
|
||||
});
|
||||
|
||||
return msgHandled;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
var postInit = function () {
|
||||
// finish the setup and actually start GoldenLayout
|
||||
myLayout.init();
|
||||
|
||||
// work out which types are untagged based on our pre-configured layout
|
||||
calculateUntaggedTypes();
|
||||
|
||||
// Set the Event handler for when the client window changes size
|
||||
$(window).bind("resize", scrollAll);
|
||||
|
||||
// Set Save State callback
|
||||
myLayout.on( "stateChanged", onStateChanged );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// required Init
|
||||
var init = function (options) {
|
||||
// Set up our GoldenLayout instance built off of the default main-sub div
|
||||
var savedState = localStorage.getItem( "evenniaGoldenLayoutSavedState" );
|
||||
var activeName = localStorage.getItem( "evenniaGoldenLayoutSavedStateName" );
|
||||
var mainsub = document.getElementById("main-sub");
|
||||
|
||||
// pre-load the evenniaGoldenLayouts with the hard-coded default
|
||||
evenniaGoldenLayouts.set( "default", window.goldenlayout_config );
|
||||
|
||||
if( activeName !== null ) {
|
||||
activeLayoutName = activeName;
|
||||
}
|
||||
|
||||
if( savedState !== null ) {
|
||||
// Overwrite the global-variable configuration from
|
||||
// webclient/js/plugins/goldenlayout_default_config.js
|
||||
// with the version from localstorage
|
||||
evenniaGoldenLayouts.set( activeLayoutName, JSON.parse(savedState) );
|
||||
} else {
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedState", JSON.stringify( window.goldenlayout_config ) );
|
||||
localStorage.setItem( "evenniaGoldenLayoutSavedStateName", "default" );
|
||||
}
|
||||
|
||||
myLayout = new window.GoldenLayout( evenniaGoldenLayouts.get(activeLayoutName), mainsub );
|
||||
|
||||
$("#prompt").remove(); // remove the HTML-defined prompt div
|
||||
$("#inputcontrol").remove(); // remove the cluttered, HTML-defined input divs
|
||||
|
||||
registerComponents( myLayout );
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
postInit: postInit,
|
||||
onGotOptions: onGotOptions,
|
||||
onOptionsUI: onOptionsUI,
|
||||
onText: onText,
|
||||
getGL: function () { return myLayout; },
|
||||
addKnownType: addKnownType,
|
||||
onTabCreate: onTabCreate,
|
||||
routeMessage: routeMessage,
|
||||
addMessageToPaneDiv: addMessageToPaneDiv,
|
||||
}
|
||||
}());
|
||||
window.plugin_handler.add("goldenlayout", goldenlayout);
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Define the default GoldenLayout-based config
|
||||
*
|
||||
* The layout defined here will need to be customized based on which plugins
|
||||
* you are using and what layout you want players to see by default.
|
||||
*
|
||||
* This needs to be loaded in the HTML before the goldenlayout.js plugin
|
||||
*
|
||||
* The contents of the global variable will be overwritten by what is in the
|
||||
* browser's localstorage after visiting this site.
|
||||
*
|
||||
* For full documentation on all of the keywords see:
|
||||
* http://golden-layout.com/docs/Config.html
|
||||
*
|
||||
*/
|
||||
var goldenlayout_config = { // Global Variable used in goldenlayout.js init()
|
||||
content: [{
|
||||
type: "column",
|
||||
content: [{
|
||||
type: "row",
|
||||
content: [{
|
||||
type: "column",
|
||||
content: [{
|
||||
type: "component",
|
||||
componentName: "Main",
|
||||
isClosable: false, // remove the 'x' control to close this
|
||||
tooltip: "Main - drag to desired position.",
|
||||
componentState: {
|
||||
types: "untagged",
|
||||
updateMethod: "newlines",
|
||||
},
|
||||
}]
|
||||
}],
|
||||
// }, { // Uncomment the following to add a default hotbuttons component
|
||||
// type: "component",
|
||||
// componentName: "hotbuttons",
|
||||
// id: "inputComponent", // mark 'ignore this component during output message processing'
|
||||
// height: 6,
|
||||
// isClosable: false,
|
||||
// }, {
|
||||
// type: "component",
|
||||
// componentName: "input",
|
||||
// id: "inputComponent", // mark for ignore
|
||||
// height: 12, // percentage
|
||||
// tooltip: "Input - The last input in the layout is always the default.",
|
||||
}, {
|
||||
type: "component",
|
||||
componentName: "input",
|
||||
id: "inputComponent", // mark for ignore
|
||||
height: 20, // percentage
|
||||
isClosable: false, // remove the 'x' control to close this
|
||||
tooltip: "Input - The last input in the layout is always the default.",
|
||||
}]
|
||||
}]
|
||||
};
|
||||
102
evennia/web/static/webclient/js/plugins/history.js
Normal file
102
evennia/web/static/webclient/js/plugins/history.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
*
|
||||
* Evennia Webclient Command History plugin
|
||||
*
|
||||
*/
|
||||
let history = (function () {
|
||||
|
||||
// Manage history for input line
|
||||
var historyMax = 21;
|
||||
var history = new Array();
|
||||
var historyPos = 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
|
||||
historyPos = Math.min(++historyPos, history.length - 1);
|
||||
return history[history.length - 1 - historyPos];
|
||||
}
|
||||
|
||||
//
|
||||
// move forward in the history
|
||||
var fwd = function () {
|
||||
// step forwards in history stack
|
||||
historyPos = Math.max(--historyPos, 0);
|
||||
return history[history.length - 1 - historyPos];
|
||||
}
|
||||
|
||||
//
|
||||
// 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 >= historyMax) {
|
||||
history.shift(); // kill oldest entry
|
||||
}
|
||||
history[history.length-1] = input;
|
||||
history[history.length] = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
//
|
||||
// Handle up arrow and down arrow events.
|
||||
var onKeydown = function(event) {
|
||||
var code = event.which;
|
||||
var historyEntry = null;
|
||||
var startingPos = historyPos;
|
||||
|
||||
// Only process up/down arrow if cursor is at the end of the line.
|
||||
if (code === 38 && event.shiftKey) { // Shift + Arrow up
|
||||
historyEntry = back();
|
||||
}
|
||||
else if (code === 40 && event.shiftKey) { // Shift + Arrow down
|
||||
historyEntry = fwd();
|
||||
}
|
||||
|
||||
// are we processing an up or down history event?
|
||||
if (historyEntry !== null) {
|
||||
// Doing a history navigation; replace the text in the input and
|
||||
// move the cursor to the end of the new value
|
||||
var inputfield = $(".inputfield:focus");
|
||||
if( inputfield.length < 1 ) { // focus the default (last), if nothing focused
|
||||
inputfield = $(".inputfield:last");
|
||||
}
|
||||
if( inputfield.length < 1 ) { // pre-goldenlayout backwards compatibility
|
||||
inputfield = $("#inputfield");
|
||||
}
|
||||
|
||||
// store any partially typed line as a new history item before replacement
|
||||
var line = inputfield.val();
|
||||
if( line !== "" && startingPos === 0 ) {
|
||||
add(line);
|
||||
}
|
||||
|
||||
inputfield.val("");
|
||||
inputfield.blur().focus().val(historyEntry);
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Listen for onSend lines to add to history
|
||||
var onSend = function (line) {
|
||||
add(line);
|
||||
historyPos = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {},
|
||||
onKeydown: onKeydown,
|
||||
onSend: onSend,
|
||||
}
|
||||
}());
|
||||
window.plugin_handler.add("history", history);
|
||||
153
evennia/web/static/webclient/js/plugins/hotbuttons.js
Normal file
153
evennia/web/static/webclient/js/plugins/hotbuttons.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
*
|
||||
* Assignable "hot-buttons" Plugin
|
||||
*
|
||||
* This adds a bar of 9 buttons that can be shift-click assigned,
|
||||
* whatever text is in the bottom textinput buffer will be copied.
|
||||
* Once assigned, clicking the button again and have it execute those commands.
|
||||
*
|
||||
* 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 and before the goldenlayout.js plugin, add:
|
||||
* <script src={% static "webclient/js/plugins/hotbuttons.js" %} type="text/javascript"></script>
|
||||
*
|
||||
* Then uncomment the hotbuttons component in goldenlayout_default_config.js
|
||||
*
|
||||
* Run: evennia collectstatic (say "yes" to the overwrite prompt)
|
||||
*
|
||||
* Start Evennia
|
||||
*
|
||||
* REQUIRES: goldenlayout.js
|
||||
*/
|
||||
let hotbuttons = (function () {
|
||||
var dependenciesMet = false;
|
||||
|
||||
var numButtons = 9;
|
||||
var commandCache = new Array;
|
||||
|
||||
//
|
||||
// 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
|
||||
commandCache[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
|
||||
commandCache[n] = "unassigned";
|
||||
}
|
||||
|
||||
//
|
||||
// actually send the command associated with the button that is clicked
|
||||
var sendImmediate = function(n) {
|
||||
var text = commandCache[n];
|
||||
if( text.length ) {
|
||||
Evennia.msg("text", [text], {});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// send, assign, or clear the button
|
||||
var hotButtonClicked = function(e) {
|
||||
var button = $("#assign_button"+e.data);
|
||||
if( button.text() == "unassigned" ) {
|
||||
// Assign the button and send the full button state to the server using a Webclient_Options event
|
||||
var input = $(".inputfield:last");
|
||||
if( input.length < 1 ) {
|
||||
input = $("#inputfield");
|
||||
}
|
||||
assignButton( e.data, input.val() );
|
||||
Evennia.msg("webclient_options", [], { "HotButtons": commandCache });
|
||||
} 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": commandCache });
|
||||
} else {
|
||||
sendImmediate(e.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
//
|
||||
// Handle the HotButtons part of a Webclient_Options event
|
||||
var onGotOptions = function(args, kwargs) {
|
||||
if( dependenciesMet && kwargs["HotButtons"] ) {
|
||||
var buttonOptions = kwargs["HotButtons"];
|
||||
$.each( buttonOptions, function( key, value ) {
|
||||
assignButton(key, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Create and register the hotbuttons golden-layout component
|
||||
var onLayoutChanged = function () {
|
||||
var myLayout = window.plugins["goldenlayout"].getGL();
|
||||
|
||||
myLayout.registerComponent( "hotbuttons", function (container, componentState) {
|
||||
// build the buttons
|
||||
var div = $("<div class='input-group'>");
|
||||
|
||||
var len = commandCache.length;
|
||||
for( var x=len; x < len + numButtons; x++ ) {
|
||||
commandCache.push("unassigned");
|
||||
|
||||
// initialize button command cache and onClick handler
|
||||
var button = $("<button class='btn' id='assign_button"+x+"' type='button' value='button"+x+"'>");
|
||||
button.html("unassigned");
|
||||
button.click( x, hotButtonClicked );
|
||||
|
||||
button.appendTo( div );
|
||||
}
|
||||
|
||||
div.appendTo( container.getElement() );
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var postInit = function() {
|
||||
// Are we using GoldenLayout?
|
||||
if( window.plugins["goldenlayout"] ) {
|
||||
onLayoutChanged();
|
||||
dependenciesMet = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {},
|
||||
postInit: postInit,
|
||||
onGotOptions: onGotOptions,
|
||||
onLayoutChanged: onLayoutChanged,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("hotbuttons", hotbuttons);
|
||||
68
evennia/web/static/webclient/js/plugins/iframe.js
Normal file
68
evennia/web/static/webclient/js/plugins/iframe.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* IFrame plugin
|
||||
* REQUIRES: goldenlayout.js
|
||||
*/
|
||||
let iframe = (function () {
|
||||
|
||||
var url = window.location.origin;
|
||||
|
||||
//
|
||||
// Create iframe component
|
||||
var createIframeComponent = function () {
|
||||
var myLayout = window.plugins["goldenlayout"].getGL();
|
||||
|
||||
myLayout.registerComponent( "iframe", function (container, componentState) {
|
||||
// build the iframe
|
||||
var div = $('<iframe src="' + url + '">');
|
||||
div.css("width", "100%");
|
||||
div.css("height", "inherit");
|
||||
div.appendTo( container.getElement() );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// handler for the "iframe" button
|
||||
var onOpenIframe = function () {
|
||||
var iframeComponent = {
|
||||
title: url,
|
||||
type: "component",
|
||||
componentName: "iframe",
|
||||
componentState: {
|
||||
},
|
||||
};
|
||||
|
||||
// Create a new GoldenLayout tab filled with the iframeComponent above
|
||||
var myLayout = window.plugins["goldenlayout"].getGL();
|
||||
var main = myLayout.root.getItemsByType("stack")[0].getActiveContentItem();
|
||||
main.parent.addChild( iframeComponent );
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
var iframebutton = $('<input type="button" value="Open Game Website" />');
|
||||
iframebutton.on('click', onOpenIframe);
|
||||
|
||||
parentdiv.append( '<div style="font-weight: bold">Restricted Browser-in-Browser:</div>' );
|
||||
parentdiv.append( iframebutton );
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var postInit = function() {
|
||||
// Are we using GoldenLayout?
|
||||
if( window.plugins["goldenlayout"] ) {
|
||||
createIframeComponent();
|
||||
|
||||
$("#iframebutton").bind("click", onOpenIframe);
|
||||
}
|
||||
console.log('IFrame plugin Loaded');
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {},
|
||||
postInit: postInit,
|
||||
onOptionsUI: onOptionsUI,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("iframe", iframe);
|
||||
143
evennia/web/static/webclient/js/plugins/message_routing.js
Normal file
143
evennia/web/static/webclient/js/plugins/message_routing.js
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Spawns plugin
|
||||
* REQUIRES: goldenlayout.js
|
||||
*/
|
||||
let spawns = (function () {
|
||||
|
||||
var spawnmap = {}; // { id1: { r:regex, t:tag } } pseudo-array of regex-tag pairs
|
||||
|
||||
//
|
||||
// changes the spawnmap row's contents to the new regex/tag provided,
|
||||
// this avoids leaving stale regex/tag definitions in the spawnmap
|
||||
var onAlterTag = function (evnt) {
|
||||
var adult = $(evnt.target).parent();
|
||||
var children = adult.children();
|
||||
var id = $(adult).data('id');
|
||||
var regex = $(children[0]).val();// spaces before/after are valid regex syntax, unfortunately
|
||||
var mytag = $(children[1]).val().trim();
|
||||
|
||||
if( mytag != "" && regex != "" ) {
|
||||
if( !(id in spawnmap) ) {
|
||||
spawnmap[id] = {};
|
||||
}
|
||||
spawnmap[id]["r"] = regex;
|
||||
spawnmap[id]["t"] = mytag;
|
||||
localStorage.setItem( "evenniaMessageRoutingSavedState", JSON.stringify(spawnmap) );
|
||||
window.plugins["goldenlayout"].addKnownType( mytag );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// deletes the entire regex/tag/delete button row.
|
||||
var onDeleteTag = function (evnt) {
|
||||
var adult = $(evnt.target).parent();
|
||||
var children = adult.children();
|
||||
var id = $(adult).data('id');
|
||||
delete spawnmap[id];
|
||||
localStorage.setItem( "evenniaMessageRoutingSavedState", JSON.stringify(spawnmap) );
|
||||
adult.remove(); // remove this set of input boxes/etc from the DOM
|
||||
}
|
||||
|
||||
//
|
||||
var onFocusIn = function (evnt) {
|
||||
window.plugins["default_in"].setKeydownFocus(false);
|
||||
}
|
||||
|
||||
//
|
||||
var onFocusOut = function (evnt) {
|
||||
window.plugins["default_in"].setKeydownFocus(true);
|
||||
onAlterTag(evnt); // percolate event so closing the pane, etc saves any last changes.
|
||||
}
|
||||
|
||||
//
|
||||
// display a row with proper editting hooks
|
||||
var displayRow = function (formdiv, div, regexstring, tagstring) {
|
||||
var regex = $('<input class="regex" type=text value="'+regexstring+'"/>');
|
||||
var tag = $('<input class="tag" type=text value="'+tagstring+'"/>');
|
||||
var del = $('<input class="delete-regex" type=button value="X"/>');
|
||||
regex.on('change', onAlterTag );
|
||||
regex.on('focusin', onFocusIn );
|
||||
regex.on('focusout', onFocusOut );
|
||||
tag.on('change', onAlterTag );
|
||||
tag.on('focusin', onFocusIn );
|
||||
tag.on('focusout', onFocusOut );
|
||||
del.on('click', onDeleteTag );
|
||||
div.append(regex);
|
||||
div.append(tag);
|
||||
div.append(del);
|
||||
formdiv.append(div);
|
||||
}
|
||||
|
||||
//
|
||||
// generate a whole new regex/tag/delete button row
|
||||
var onNewRegexRow = function (formdiv) {
|
||||
var nextid = 1;
|
||||
while( nextid in spawnmap ) { // pseudo-index spawnmap with id reuse
|
||||
nextid++;
|
||||
}
|
||||
var div = $("<div data-id='"+nextid+"'>");
|
||||
displayRow(formdiv, div, "", "");
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
//
|
||||
// onOptionsUI -- display the existing spawnmap and a button to create more entries.
|
||||
//
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
var formdiv = $('<div>');
|
||||
var button= $('<input type="button" value="New Regex/Tag Pair" />');
|
||||
button.on('click', function () { onNewRegexRow(formdiv) });
|
||||
formdiv.append(button);
|
||||
|
||||
// display the existing spawnmap
|
||||
for( var id in spawnmap ) {
|
||||
var div = $("<div data-id='"+id+"'>");
|
||||
displayRow(formdiv, div, spawnmap[id]["r"], spawnmap[id]["t"] );
|
||||
}
|
||||
|
||||
parentdiv.append('<div style="font-weight: bold">Message Routing:</div>');
|
||||
parentdiv.append(formdiv);
|
||||
}
|
||||
|
||||
//
|
||||
// onText -- catch Text before it is routed by the goldenlayout router
|
||||
// then test our list of regexes on the given text to see if it matches.
|
||||
// If it does, rewrite the Text Type to be our tag value instead.
|
||||
//
|
||||
var onText = function (args, kwargs) {
|
||||
var div = $("<div>" + args[0] + "</div>");
|
||||
var txt = div.text();
|
||||
for( var id in spawnmap ) {
|
||||
var regex = spawnmap[id]["r"];
|
||||
if ( txt.match(regex) != null ) {
|
||||
kwargs['type'] = spawnmap[id]["t"];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// init
|
||||
//
|
||||
var init = function () {
|
||||
var ls_spawnmap = localStorage.getItem( "evenniaMessageRoutingSavedState" );
|
||||
if( ls_spawnmap ) {
|
||||
spawnmap = JSON.parse(ls_spawnmap);
|
||||
for( var id in spawnmap ) {
|
||||
window.plugins["goldenlayout"].addKnownType( spawnmap[id]["t"] );
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Client-Side Message Routing plugin initialized');
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onOptionsUI: onOptionsUI,
|
||||
onText: onText,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("spawns", spawns);
|
||||
119
evennia/web/static/webclient/js/plugins/multimedia.js
Normal file
119
evennia/web/static/webclient/js/plugins/multimedia.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Evennia example Webclient multimedia outputs plugin
|
||||
*
|
||||
* PLUGIN ORDER PREREQS:
|
||||
* loaded after:
|
||||
* webclient_gui.js
|
||||
* option2.js
|
||||
* loaded before:
|
||||
*
|
||||
*
|
||||
* To use, in evennia python code:
|
||||
* target.msg( image="URL" )
|
||||
* target.msg( audio="URL" )
|
||||
* target.msg( video="URL" )
|
||||
* or, if you prefer tagged routing:
|
||||
* target.msg( image=("URL",{'type':'tag'}) )
|
||||
*
|
||||
*
|
||||
* Note: users probably don't _want_ more than one pane to end up with multimedia tags...
|
||||
* But to allow proper tagged message routing, this plugin doesn't explicitly deny it.
|
||||
*/
|
||||
let multimedia_plugin = (function () {
|
||||
//
|
||||
var image = function (args, kwargs) {
|
||||
let options = window.options;
|
||||
if( !("mm_image" in options) || options["mm_image"] === false ) { return; }
|
||||
|
||||
var mwins = window.plugins["goldenlayout"].routeMessage(args, kwargs);
|
||||
mwins.forEach( function (mwin) {
|
||||
mwin.append("<img src='"+ args[0] +"'/>");
|
||||
mwin.scrollTop(mwin[0].scrollHeight);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
var audio = function (args, kwargs) {
|
||||
let options = window.options;
|
||||
if( !("mm_audio" in options) || options["mm_audio"] === false ) { return; }
|
||||
|
||||
// create an HTML5 audio control (only .mp3 is fully compatible with all major browsers)
|
||||
var mwins = window.plugins["goldenlayout"].routeMessage(args, kwargs);
|
||||
mwins.forEach( function (mwin) {
|
||||
mwin.append("<audio controls='' autoplay='' style='height:17px;width:175px'>" +
|
||||
"<source src='"+ args[0] +"'/>" +
|
||||
"</audio>");
|
||||
mwin.scrollTop(mwin[0].scrollHeight);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
var video = function (args, kwargs) {
|
||||
let options = window.options;
|
||||
if( !("mm_video" in options) || options["mm_video"] === false ) { return; }
|
||||
|
||||
// create an HTML5 video element (only h264 .mp4 is compatible with all major browsers)
|
||||
var mwins = window.plugins["goldenlayout"].routeMessage(args, kwargs);
|
||||
mwins.forEach( function (mwin) {
|
||||
mwin.append("<video controls='' autoplay=''>" +
|
||||
"<source src='"+ args[0] +"'/>" +
|
||||
"</video>");
|
||||
mwin.scrollTop(mwin[0].scrollHeight);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
let options = window.options;
|
||||
var checked;
|
||||
|
||||
checked = options["mm_image"] ? "checked='checked'" : "";
|
||||
var mmImage = $( [ "<label>",
|
||||
"<input type='checkbox' data-setting='mm_image' " + checked + "'>",
|
||||
" Enable multimedia image (png/gif/etc) messages",
|
||||
"</label>"
|
||||
].join("") );
|
||||
|
||||
checked = options["mm_audio"] ? "checked='checked'" : "";
|
||||
var mmAudio = $( [ "<label>",
|
||||
"<input type='checkbox' data-setting='mm_audio' " + checked + "'>",
|
||||
" Enable multimedia audio (mp3) messages",
|
||||
"</label>"
|
||||
].join("") );
|
||||
|
||||
checked = options["mm_video"] ? "checked='checked'" : "";
|
||||
var mmVideo = $( [ "<label>",
|
||||
"<input type='checkbox' data-setting='mm_video' " + checked + "'>",
|
||||
" Enable multimedia video (h264 .mp4) messages",
|
||||
"</label>"
|
||||
].join("") );
|
||||
mmImage.on("change", window.plugins["options2"].onOptionCheckboxChanged);
|
||||
mmAudio.on("change", window.plugins["options2"].onOptionCheckboxChanged);
|
||||
mmVideo.on("change", window.plugins["options2"].onOptionCheckboxChanged);
|
||||
|
||||
parentdiv.append(mmImage);
|
||||
parentdiv.append(mmAudio);
|
||||
parentdiv.append(mmVideo);
|
||||
}
|
||||
|
||||
//
|
||||
// Mandatory plugin init function
|
||||
var init = function () {
|
||||
let options = window.options;
|
||||
options["mm_image"] = true;
|
||||
options["mm_audio"] = true;
|
||||
options["mm_video"] = true;
|
||||
|
||||
let Evennia = window.Evennia;
|
||||
Evennia.emitter.on("image", image); // capture "image" commands
|
||||
Evennia.emitter.on("audio", audio); // capture "audio" commands
|
||||
Evennia.emitter.on("video", video); // capture "video" commands
|
||||
console.log('Multimedia plugin initialized');
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onOptionsUI: onOptionsUI,
|
||||
}
|
||||
})();
|
||||
plugin_handler.add("multimedia_plugin", multimedia_plugin);
|
||||
88
evennia/web/static/webclient/js/plugins/notifications.js
Normal file
88
evennia/web/static/webclient/js/plugins/notifications.js
Normal 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 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/static/webclient/js/plugins/oob.js
Normal file
35
evennia/web/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);
|
||||
189
evennia/web/static/webclient/js/plugins/options2.js
Normal file
189
evennia/web/static/webclient/js/plugins/options2.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Options 2.0
|
||||
* REQUIRES: goldenlayout.js
|
||||
*/
|
||||
let options2 = (function () {
|
||||
|
||||
var optionsContainer = null ;
|
||||
|
||||
//
|
||||
// When the user changes a setting from the interface
|
||||
var onOptionCheckboxChanged = function (evnt) {
|
||||
var name = $(evnt.target).data("setting");
|
||||
var value = $(evnt.target).is(":checked");
|
||||
options[name] = value;
|
||||
Evennia.msg("webclient_options", [], options);
|
||||
}
|
||||
|
||||
//
|
||||
// Callback to display our basic OptionsUI
|
||||
var onOptionsUI = function (parentdiv) {
|
||||
var checked;
|
||||
|
||||
checked = options["gagprompt"] ? "checked='checked'" : "";
|
||||
var gagprompt = $( [ "<label>",
|
||||
"<input type='checkbox' data-setting='gagprompt' " + checked + "'>",
|
||||
" Don't echo prompts to the main text area",
|
||||
"</label>"
|
||||
].join("") );
|
||||
|
||||
checked = options["notification_popup"] ? "checked='checked'" : "";
|
||||
var notifypopup = $( [ "<label>",
|
||||
"<input type='checkbox' data-setting='notification_popup' " + checked + "'>",
|
||||
" Popup notification",
|
||||
"</label>"
|
||||
].join("") );
|
||||
|
||||
checked = options["notification_sound"] ? "checked='checked'" : "";
|
||||
var notifysound = $( [ "<label>",
|
||||
"<input type='checkbox' data-setting='notification_sound' " + checked + "'>",
|
||||
" Play a sound",
|
||||
"</label>"
|
||||
].join("") );
|
||||
|
||||
gagprompt.on("change", onOptionCheckboxChanged);
|
||||
notifypopup.on("change", onOptionCheckboxChanged);
|
||||
notifysound.on("change", onOptionCheckboxChanged);
|
||||
|
||||
parentdiv.append(gagprompt);
|
||||
parentdiv.append(notifypopup);
|
||||
parentdiv.append(notifysound);
|
||||
}
|
||||
|
||||
|
||||
// handler for the "Options" button
|
||||
var onOpenCloseOptions = function () {
|
||||
var optionsComponent = {
|
||||
title: "Options",
|
||||
type: "component",
|
||||
componentName: "options",
|
||||
componentState: {
|
||||
},
|
||||
};
|
||||
|
||||
// Create a new GoldenLayout tab filled with the optionsComponent above
|
||||
var myLayout = window.plugins["goldenlayout"].getGL();
|
||||
if( ! optionsContainer ) {
|
||||
// open new optionsComponent
|
||||
var main = myLayout.root.getItemsByType("stack")[0].getActiveContentItem();
|
||||
|
||||
myLayout.on( "tabCreated", function( tab ) {
|
||||
if( tab.contentItem.componentName == "options" ) {
|
||||
tab
|
||||
.closeElement
|
||||
.off("click")
|
||||
.click( function () {
|
||||
optionsContainer = null;
|
||||
tab.contentItem.remove();
|
||||
window.plugins["default_in"].setKeydownFocus(true);
|
||||
});
|
||||
optionsContainer = tab.contentItem;
|
||||
}
|
||||
});
|
||||
main.parent.addChild( optionsComponent );
|
||||
|
||||
window.plugins["default_in"].setKeydownFocus(false);
|
||||
} else {
|
||||
optionsContainer.remove();
|
||||
optionsContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
//
|
||||
// Called when options settings are sent from server
|
||||
var onGotOptions = function (args, kwargs) {
|
||||
var addKnownType = window.plugins["goldenlayout"].addKnownType;
|
||||
|
||||
$.each(kwargs, function(key, value) {
|
||||
options[key] = value;
|
||||
|
||||
// for "available_server_tags", addKnownType for each value ["tag1", "tag2", ... ]
|
||||
if( (key === "available_server_tags") && addKnownType ) {
|
||||
$.each( value, addKnownType );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Create and register the "options" golden-layout component
|
||||
var onLayoutChanged = function () {
|
||||
var myLayout = window.plugins["goldenlayout"].getGL();
|
||||
|
||||
myLayout.registerComponent( "options", function (container, componentState) {
|
||||
var plugins = window.plugins;
|
||||
optionsContainer = container.getElement();
|
||||
|
||||
// build the buttons
|
||||
var div = $("<div class='accordion' style='overflow-y:scroll; height:inherit;'>");
|
||||
|
||||
for( let plugin in plugins ) {
|
||||
if( "onOptionsUI" in plugins[plugin] ) {
|
||||
var card = $("<div class='card'>");
|
||||
var body = $("<div>");
|
||||
|
||||
plugins[plugin].onOptionsUI( body );
|
||||
|
||||
card.append(body);
|
||||
card.appendTo( div );
|
||||
}
|
||||
}
|
||||
|
||||
div.appendTo( optionsContainer );
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Called when the user logged in
|
||||
var onLoggedIn = function (args, kwargs) {
|
||||
Evennia.msg("webclient_options", [], {});
|
||||
}
|
||||
|
||||
//
|
||||
// Display a "prompt" command from the server
|
||||
var onPrompt = function (args, kwargs) {
|
||||
// display the prompt in the output window if gagging is disabled
|
||||
if( options["gagprompt"] == false ) {
|
||||
plugin_handler.onText(args, kwargs);
|
||||
}
|
||||
|
||||
// don't claim this Prompt as completed.
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var init = function() {
|
||||
var optionsbutton = $("<button id='optionsbutton'>⚙</button>");
|
||||
$("#toolbar").append( optionsbutton );
|
||||
options["gagprompt"] = true;
|
||||
options["notification_popup"] = true;
|
||||
options["notification_sound"] = true;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
var postInit = function() {
|
||||
// Are we using GoldenLayout?
|
||||
if( window.plugins["goldenlayout"] ) {
|
||||
onLayoutChanged();
|
||||
|
||||
$("#optionsbutton").bind("click", onOpenCloseOptions);
|
||||
}
|
||||
console.log("Options 2.0 Loaded");
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
postInit: postInit,
|
||||
onGotOptions: onGotOptions,
|
||||
onLayoutChanged: onLayoutChanged,
|
||||
onLoggedIn: onLoggedIn,
|
||||
onOptionsUI: onOptionsUI,
|
||||
onPrompt: onPrompt,
|
||||
onOptionCheckboxChanged: onOptionCheckboxChanged,
|
||||
onOpenCloseOptions: onOpenCloseOptions,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("options2", options2);
|
||||
101
evennia/web/static/webclient/js/plugins/popups.js
Normal file
101
evennia/web/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);
|
||||
306
evennia/web/static/webclient/js/webclient_gui.js
Normal file
306
evennia/web/static/webclient/js/webclient_gui.js
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
*
|
||||
* Evennia Webclient GUI component
|
||||
*
|
||||
* 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 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.
|
||||
*
|
||||
*/
|
||||
|
||||
//
|
||||
// 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, ... }, ... }
|
||||
|
||||
var plugins = {}; // Global plugin objects by name.
|
||||
// Each must have an init() function.
|
||||
|
||||
//
|
||||
// Global plugin_handler
|
||||
//
|
||||
var plugin_handler = (function () {
|
||||
"use strict"
|
||||
|
||||
var ordered_plugins = new Array; // plugins in <html> loaded order
|
||||
|
||||
//
|
||||
// Plugin Support Functions
|
||||
//
|
||||
|
||||
// Add a new plugin
|
||||
var add = function (name, plugin) {
|
||||
plugins[name] = plugin;
|
||||
ordered_plugins.push( plugin );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// GUI Event Handlers
|
||||
//
|
||||
|
||||
// catch all keyboard input, handle special chars
|
||||
var onKeydown = function (event) {
|
||||
// cycle through each plugin's keydown
|
||||
for( let n=0; n < ordered_plugins.length; n++ ) {
|
||||
let plugin = ordered_plugins[n];
|
||||
// does this plugin handle keydown events?
|
||||
if( 'onKeydown' in plugin ) {
|
||||
// yes, does this plugin claim this event exclusively?
|
||||
if( plugin.onKeydown(event) ) {
|
||||
// 'true' claims this event has been handled
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('NO plugin handled this Keydown');
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Evennia Public Event Handlers
|
||||
//
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// normally init() is all that is needed, but some cases may require a second
|
||||
// pass to avoid chicken/egg dependencies between two plugins.
|
||||
var postInit = function () {
|
||||
// does this plugin need postInit() to be called?
|
||||
for( let n=0; n < ordered_plugins.length; n++ ) {
|
||||
let plugin = ordered_plugins[n];
|
||||
if( 'postInit' in plugin ) {
|
||||
plugin.postInit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
postInit: postInit,
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
//
|
||||
// Webclient Initialization
|
||||
//
|
||||
|
||||
// Event when client finishes loading
|
||||
$(document).ready(function() {
|
||||
// This is safe to call, it will always only
|
||||
// initialize once.
|
||||
Evennia.init();
|
||||
|
||||
// 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", plugin_handler.onBeforeUnload);
|
||||
|
||||
// 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
|
||||
);
|
||||
|
||||
// Initialize all plugins
|
||||
plugin_handler.init();
|
||||
|
||||
// Finish Initializing any plugins that need a second stage
|
||||
plugin_handler.postInit();
|
||||
|
||||
console.log("Completed Webclient setup");
|
||||
});
|
||||
BIN
evennia/web/static/webclient/media/notification.wav
Normal file
BIN
evennia/web/static/webclient/media/notification.wav
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue