Start restructuring web presences

This commit is contained in:
Griatch 2021-05-16 15:34:51 +02:00
parent 98a200533f
commit dac2be3074
92 changed files with 212 additions and 122 deletions

View 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;
}

View 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; }

View 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;
}

View 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
);
});

View 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);

View 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);

View 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);

View file

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

View file

@ -0,0 +1,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);

View 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;'>&#129170;</span>");
let typeDropdownControl = $("<span class='lm_title' style='font-size: 1.0em;width: 1em;'>&#11201;</span>");
let updateDropdownControl = $("<span class='lm_title' style='font-size: 1.0em;width: 1em;'>&#11208;</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'>&gt;</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);

View file

@ -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.",
}]
}]
};

View 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);

View 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);

View 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);

View 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);

View 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);

View file

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

View file

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

View file

@ -0,0 +1,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'>&#x2699;</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);

View file

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

View file

@ -0,0 +1,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");
});

Binary file not shown.