pog
This commit is contained in:
parent
2494b2dd5f
commit
4e1b1dd2cb
@ -6,6 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<link rel="stylesheet" href="./node_modules/izitoast/dist/css/iziToast.min.css"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Jura" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css">
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js"></script>
|
||||
</head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@ -5,7 +5,7 @@ const genAvatar = (name) => {
|
||||
for (let i = 0; i < name.length; i += 1) {
|
||||
const chr = name.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash = hash & 19; // We have avatars named 0-19
|
||||
hash = hash & 10000; // We have avatars named 0-19
|
||||
}
|
||||
return `sprite${hash}`;
|
||||
};
|
||||
|
||||
9
html-client/.babelrc
Normal file
9
html-client/.babelrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
["transform-react-jsx", { "pragma":"preact.h" }]
|
||||
]
|
||||
}
|
||||
1504
html-client/.eslintrc.js
Executable file
1504
html-client/.eslintrc.js
Executable file
File diff suppressed because it is too large
Load Diff
5
html-client/.gitignore
vendored
Normal file
5
html-client/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
package-lock.json
|
||||
node_modules/
|
||||
dist/
|
||||
.cache/
|
||||
assets/molecules
|
||||
427
html-client/assets/normalize.css
vendored
Normal file
427
html-client/assets/normalize.css
vendored
Normal file
@ -0,0 +1,427 @@
|
||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
||||
* and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contain overflow in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address odd `em`-unit font size rendering in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended that you don't attempt to style these elements.
|
||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||
*
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||
* `font-size` values of the `input`, it causes the cursor style of the
|
||||
* decrement button to change from `default` to `text`.
|
||||
*/
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inherit the `font-weight` (applied by a rule above).
|
||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
418
html-client/assets/skeleton.css
vendored
Normal file
418
html-client/assets/skeleton.css
vendored
Normal file
@ -0,0 +1,418 @@
|
||||
/*
|
||||
* Skeleton V2.0.4
|
||||
* Copyright 2014, Dave Gamache
|
||||
* www.getskeleton.com
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* 12/29/2014
|
||||
*/
|
||||
|
||||
|
||||
/* Table of contents
|
||||
––––––––––––––––––––––––––––––––––––––––––––––––––
|
||||
- Grid
|
||||
- Base Styles
|
||||
- Typography
|
||||
- Links
|
||||
- Buttons
|
||||
- Forms
|
||||
- Lists
|
||||
- Code
|
||||
- Tables
|
||||
- Spacing
|
||||
- Utilities
|
||||
- Clearing
|
||||
- Media Queries
|
||||
*/
|
||||
|
||||
|
||||
/* Grid
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box; }
|
||||
.column,
|
||||
.columns {
|
||||
width: 100%;
|
||||
float: left;
|
||||
box-sizing: border-box; }
|
||||
|
||||
/* For devices larger than 400px */
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
width: 85%;
|
||||
padding: 0; }
|
||||
}
|
||||
|
||||
/* For devices larger than 550px */
|
||||
@media (min-width: 550px) {
|
||||
.container {
|
||||
width: 80%; }
|
||||
.column,
|
||||
.columns {
|
||||
margin-left: 4%; }
|
||||
.column:first-child,
|
||||
.columns:first-child {
|
||||
margin-left: 0; }
|
||||
|
||||
.one.column,
|
||||
.one.columns { width: 4.66666666667%; }
|
||||
.two.columns { width: 13.3333333333%; }
|
||||
.three.columns { width: 22%; }
|
||||
.four.columns { width: 30.6666666667%; }
|
||||
.five.columns { width: 39.3333333333%; }
|
||||
.six.columns { width: 48%; }
|
||||
.seven.columns { width: 56.6666666667%; }
|
||||
.eight.columns { width: 65.3333333333%; }
|
||||
.nine.columns { width: 74.0%; }
|
||||
.ten.columns { width: 82.6666666667%; }
|
||||
.eleven.columns { width: 91.3333333333%; }
|
||||
.twelve.columns { width: 100%; margin-left: 0; }
|
||||
|
||||
.one-third.column { width: 30.6666666667%; }
|
||||
.two-thirds.column { width: 65.3333333333%; }
|
||||
|
||||
.one-half.column { width: 48%; }
|
||||
|
||||
/* Offsets */
|
||||
.offset-by-one.column,
|
||||
.offset-by-one.columns { margin-left: 8.66666666667%; }
|
||||
.offset-by-two.column,
|
||||
.offset-by-two.columns { margin-left: 17.3333333333%; }
|
||||
.offset-by-three.column,
|
||||
.offset-by-three.columns { margin-left: 26%; }
|
||||
.offset-by-four.column,
|
||||
.offset-by-four.columns { margin-left: 34.6666666667%; }
|
||||
.offset-by-five.column,
|
||||
.offset-by-five.columns { margin-left: 43.3333333333%; }
|
||||
.offset-by-six.column,
|
||||
.offset-by-six.columns { margin-left: 52%; }
|
||||
.offset-by-seven.column,
|
||||
.offset-by-seven.columns { margin-left: 60.6666666667%; }
|
||||
.offset-by-eight.column,
|
||||
.offset-by-eight.columns { margin-left: 69.3333333333%; }
|
||||
.offset-by-nine.column,
|
||||
.offset-by-nine.columns { margin-left: 78.0%; }
|
||||
.offset-by-ten.column,
|
||||
.offset-by-ten.columns { margin-left: 86.6666666667%; }
|
||||
.offset-by-eleven.column,
|
||||
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
|
||||
|
||||
.offset-by-one-third.column,
|
||||
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
|
||||
.offset-by-two-thirds.column,
|
||||
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
|
||||
|
||||
.offset-by-one-half.column,
|
||||
.offset-by-one-half.columns { margin-left: 52%; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Base Styles
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
/* NOTE
|
||||
html is set to 62.5% so that all the REM measurements throughout Skeleton
|
||||
are based on 10px sizing. So basically 1.5rem = 15px :) */
|
||||
html {
|
||||
font-size: 62.5%; }
|
||||
body {
|
||||
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
|
||||
line-height: 1.6;
|
||||
font-weight: 400;
|
||||
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #222; }
|
||||
|
||||
|
||||
/* Typography
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 2rem;
|
||||
font-weight: 300; }
|
||||
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
|
||||
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
|
||||
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
|
||||
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
|
||||
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
|
||||
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
|
||||
|
||||
/* Larger than phablet */
|
||||
@media (min-width: 550px) {
|
||||
h1 { font-size: 5.0rem; }
|
||||
h2 { font-size: 4.2rem; }
|
||||
h3 { font-size: 3.6rem; }
|
||||
h4 { font-size: 3.0rem; }
|
||||
h5 { font-size: 2.4rem; }
|
||||
h6 { font-size: 1.5rem; }
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0; }
|
||||
|
||||
|
||||
/* Links
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
a {
|
||||
color: #1EAEDB; }
|
||||
a:hover {
|
||||
color: #0FA0CE; }
|
||||
|
||||
|
||||
/* Buttons
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.button,
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"] {
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
padding: 0 30px;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 38px;
|
||||
letter-spacing: .1rem;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #bbb;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box; }
|
||||
.button:hover,
|
||||
button:hover,
|
||||
input[type="submit"]:hover,
|
||||
input[type="reset"]:hover,
|
||||
input[type="button"]:hover,
|
||||
.button:focus,
|
||||
button:focus,
|
||||
input[type="submit"]:focus,
|
||||
input[type="reset"]:focus,
|
||||
input[type="button"]:focus {
|
||||
color: #333;
|
||||
border-color: #888;
|
||||
outline: 0; }
|
||||
.button.button-primary,
|
||||
button.button-primary,
|
||||
input[type="submit"].button-primary,
|
||||
input[type="reset"].button-primary,
|
||||
input[type="button"].button-primary {
|
||||
color: #FFF;
|
||||
background-color: #33C3F0;
|
||||
border-color: #33C3F0; }
|
||||
.button.button-primary:hover,
|
||||
button.button-primary:hover,
|
||||
input[type="submit"].button-primary:hover,
|
||||
input[type="reset"].button-primary:hover,
|
||||
input[type="button"].button-primary:hover,
|
||||
.button.button-primary:focus,
|
||||
button.button-primary:focus,
|
||||
input[type="submit"].button-primary:focus,
|
||||
input[type="reset"].button-primary:focus,
|
||||
input[type="button"].button-primary:focus {
|
||||
color: #FFF;
|
||||
background-color: #1EAEDB;
|
||||
border-color: #1EAEDB; }
|
||||
|
||||
|
||||
/* Forms
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="search"],
|
||||
input[type="text"],
|
||||
input[type="tel"],
|
||||
input[type="url"],
|
||||
input[type="password"],
|
||||
textarea,
|
||||
select {
|
||||
height: 38px;
|
||||
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
|
||||
background-color: #fff;
|
||||
border: 1px solid #D1D1D1;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box; }
|
||||
/* Removes awkward default styles on some inputs for iOS */
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="search"],
|
||||
input[type="text"],
|
||||
input[type="tel"],
|
||||
input[type="url"],
|
||||
input[type="password"],
|
||||
textarea {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none; }
|
||||
textarea {
|
||||
min-height: 65px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px; }
|
||||
input[type="email"]:focus,
|
||||
input[type="number"]:focus,
|
||||
input[type="search"]:focus,
|
||||
input[type="text"]:focus,
|
||||
input[type="tel"]:focus,
|
||||
input[type="url"]:focus,
|
||||
input[type="password"]:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border: 1px solid #33C3F0;
|
||||
outline: 0; }
|
||||
label,
|
||||
legend {
|
||||
display: block;
|
||||
margin-bottom: .5rem;
|
||||
font-weight: 600; }
|
||||
fieldset {
|
||||
padding: 0;
|
||||
border-width: 0; }
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
display: inline; }
|
||||
label > .label-body {
|
||||
display: inline-block;
|
||||
margin-left: .5rem;
|
||||
font-weight: normal; }
|
||||
|
||||
|
||||
/* Lists
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
ul {
|
||||
list-style: circle inside; }
|
||||
ol {
|
||||
list-style: decimal inside; }
|
||||
ol, ul {
|
||||
padding-left: 0;
|
||||
margin-top: 0; }
|
||||
ul ul,
|
||||
ul ol,
|
||||
ol ol,
|
||||
ol ul {
|
||||
margin: 1.5rem 0 1.5rem 3rem;
|
||||
font-size: 90%; }
|
||||
li {
|
||||
margin-bottom: 1rem; }
|
||||
|
||||
|
||||
/* Code
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
code {
|
||||
padding: .2rem .5rem;
|
||||
margin: 0 .2rem;
|
||||
font-size: 90%;
|
||||
white-space: nowrap;
|
||||
background: #F1F1F1;
|
||||
border: 1px solid #E1E1E1;
|
||||
border-radius: 4px; }
|
||||
pre > code {
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
white-space: pre; }
|
||||
|
||||
|
||||
/* Tables
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
th,
|
||||
td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #E1E1E1; }
|
||||
th:first-child,
|
||||
td:first-child {
|
||||
padding-left: 0; }
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
padding-right: 0; }
|
||||
|
||||
|
||||
/* Spacing
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
button,
|
||||
.button {
|
||||
margin-bottom: 1rem; }
|
||||
input,
|
||||
textarea,
|
||||
select,
|
||||
fieldset {
|
||||
margin-bottom: 1.5rem; }
|
||||
pre,
|
||||
blockquote,
|
||||
dl,
|
||||
figure,
|
||||
table,
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
form {
|
||||
margin-bottom: 2.5rem; }
|
||||
|
||||
|
||||
/* Utilities
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.u-full-width {
|
||||
width: 100%;
|
||||
box-sizing: border-box; }
|
||||
.u-max-full-width {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box; }
|
||||
.u-pull-right {
|
||||
float: right; }
|
||||
.u-pull-left {
|
||||
float: left; }
|
||||
|
||||
|
||||
/* Misc
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
hr {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 3.5rem;
|
||||
border-width: 0;
|
||||
border-top: 1px solid #E1E1E1; }
|
||||
|
||||
|
||||
/* Clearing
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
|
||||
/* Self Clearing Goodness */
|
||||
.container:after,
|
||||
.row:after,
|
||||
.u-cf {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both; }
|
||||
|
||||
|
||||
/* Media Queries
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
/*
|
||||
Note: The best way to structure the use of media queries is to create the queries
|
||||
near the relevant code. For example, if you wanted to change the styles for buttons
|
||||
on small devices, paste the mobile query code up in the buttons section and style it
|
||||
there.
|
||||
*/
|
||||
|
||||
|
||||
/* Larger than mobile */
|
||||
@media (min-width: 400px) {}
|
||||
|
||||
/* Larger than phablet (also point when grid becomes active) */
|
||||
@media (min-width: 550px) {}
|
||||
|
||||
/* Larger than tablet */
|
||||
@media (min-width: 750px) {}
|
||||
|
||||
/* Larger than desktop */
|
||||
@media (min-width: 1000px) {}
|
||||
|
||||
/* Larger than Desktop HD */
|
||||
@media (min-width: 1200px) {}
|
||||
119
html-client/cryps.css
Normal file
119
html-client/cryps.css
Normal file
@ -0,0 +1,119 @@
|
||||
body {
|
||||
background-color: #000000;
|
||||
font-family: 'Jura';
|
||||
color: whitesmoke;
|
||||
font-size: 16pt;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
button, input {
|
||||
font-family: 'Jura';
|
||||
height: auto;
|
||||
color: whitesmoke;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
@keyframes glowing {
|
||||
0% { box-shadow: 0 0 0px whitesmoke; }
|
||||
20% { box-shadow: 0 0 20px whitesmoke; }
|
||||
60% { box-shadow: 0 0 20px whitesmoke; }
|
||||
100% { box-shadow: 0 0 0px whitesmoke; }
|
||||
}
|
||||
|
||||
button:hover {
|
||||
color: whitesmoke;
|
||||
animation: glowing 2000ms infinite;
|
||||
border-color: whitesmoke;
|
||||
}
|
||||
|
||||
@keyframes greenglow {
|
||||
0% {
|
||||
box-shadow: 0 0 -20px forestgreen;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 -20px forestgreen;
|
||||
box-shadow: 0 0 30px forestgreen;
|
||||
color: forestgreen;
|
||||
border-color: forestgreen;
|
||||
}
|
||||
}
|
||||
|
||||
.green-btn:hover {
|
||||
animation: greenglow 2s ease 0s 1 normal forwards;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
.instance-btn {
|
||||
font-size: 150%;
|
||||
min-width: 20%;
|
||||
border-width: 2px;
|
||||
padding: 0.5em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.instance-ui-btn {
|
||||
font-size: 100%;
|
||||
min-width: 20%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.home-cryp {
|
||||
}
|
||||
|
||||
.background {
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
position: absolute;
|
||||
z-index: -1000;
|
||||
background-color: #000000
|
||||
}
|
||||
|
||||
.cryps-title {
|
||||
font-size: 200%;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.login {
|
||||
display: inline;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.header-username {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.vbox-table td {
|
||||
border: 1px solid whitesmoke;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.vbox-table th:first-child, td:first-child {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.ping-svg {
|
||||
background-color: black;
|
||||
height: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.ping-path {
|
||||
stroke: #f5f5f5;
|
||||
fill: none;
|
||||
stroke-width: 4;
|
||||
stroke-dasharray: 121, 242;
|
||||
animation: pulse 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
stroke-dashoffset: 363;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
18
html-client/index.html
Normal file
18
html-client/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>cryps.gg - mnml pvp atbs</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="description" content="cryps.gg - mnml pvp atbs">
|
||||
<meta name="author" content="ntr@smokestack.io">
|
||||
<link rel="stylesheet" href="./node_modules/izitoast/dist/css/iziToast.min.css"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Jura" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="assets/skeleton.css">
|
||||
</head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<script src="./index.js"></script>
|
||||
</html>
|
||||
4
html-client/index.js
Executable file
4
html-client/index.js
Executable file
@ -0,0 +1,4 @@
|
||||
require('./cryps.css');
|
||||
|
||||
// kick it off
|
||||
require('./src/main');
|
||||
189
html-client/lib/fizzy-noise.js
Normal file
189
html-client/lib/fizzy-noise.js
Normal file
@ -0,0 +1,189 @@
|
||||
// http://mrl.nyu.edu/~perlin/noise/
|
||||
|
||||
const ImprovedNoise = function () {
|
||||
const p = [151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10,
|
||||
23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87,
|
||||
174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211,
|
||||
133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
|
||||
89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5,
|
||||
202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119,
|
||||
248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,
|
||||
178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
|
||||
14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,
|
||||
93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180];
|
||||
|
||||
for (let i = 0; i < 256; i++) {
|
||||
p[256 + i] = p[i];
|
||||
}
|
||||
|
||||
function fade(t) {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
function lerp(t, a, b) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
function grad(hash, x, y, z) {
|
||||
const h = hash & 15;
|
||||
const u = h < 8 ? x : y; const
|
||||
v = h < 4 ? y : h == 12 || h == 14 ? x : z;
|
||||
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
noise(x, y, z) {
|
||||
const floorX = Math.floor(x); const floorY = Math.floor(y); const
|
||||
floorZ = Math.floor(z);
|
||||
|
||||
const X = floorX & 255; const Y = floorY & 255; const
|
||||
Z = floorZ & 255;
|
||||
|
||||
x -= floorX;
|
||||
y -= floorY;
|
||||
z -= floorZ;
|
||||
|
||||
const xMinus1 = x - 1; const yMinus1 = y - 1; const
|
||||
zMinus1 = z - 1;
|
||||
|
||||
const u = fade(x); const v = fade(y); const
|
||||
w = fade(z);
|
||||
|
||||
const A = p[X] + Y; const AA = p[A] + Z; const AB = p[A + 1] + Z; const B = p[X + 1] + Y; const BA = p[B] + Z; const
|
||||
BB = p[B + 1] + Z;
|
||||
|
||||
return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),
|
||||
grad(p[BA], xMinus1, y, z)),
|
||||
lerp(u, grad(p[AB], x, yMinus1, z),
|
||||
grad(p[BB], xMinus1, yMinus1, z))),
|
||||
lerp(v, lerp(u, grad(p[AA + 1], x, y, zMinus1),
|
||||
grad(p[BA + 1], xMinus1, y, z - 1)),
|
||||
lerp(u, grad(p[AB + 1], x, yMinus1, zMinus1),
|
||||
grad(p[BB + 1], xMinus1, yMinus1, zMinus1))));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const currentRandom = Math.random;
|
||||
|
||||
// Pseudo-random generator
|
||||
function Marsaglia(i1, i2) {
|
||||
// from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c
|
||||
let z = i1 || 362436069; let
|
||||
w = i2 || 521288629;
|
||||
const nextInt = function () {
|
||||
z = (36969 * (z & 65535) + (z >>> 16)) & 0xFFFFFFFF;
|
||||
w = (18000 * (w & 65535) + (w >>> 16)) & 0xFFFFFFFF;
|
||||
return (((z & 0xFFFF) << 16) | (w & 0xFFFF)) & 0xFFFFFFFF;
|
||||
};
|
||||
|
||||
this.nextDouble = function () {
|
||||
const i = nextInt() / 4294967296;
|
||||
return i < 0 ? 1 + i : i;
|
||||
};
|
||||
this.nextInt = nextInt;
|
||||
}
|
||||
Marsaglia.createRandomized = function () {
|
||||
const now = new Date();
|
||||
return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF);
|
||||
};
|
||||
|
||||
// Noise functions and helpers
|
||||
function PerlinNoise(seed) {
|
||||
const rnd = seed !== undefined ? new Marsaglia(seed) : Marsaglia.createRandomized();
|
||||
let i; let
|
||||
j;
|
||||
// http://www.noisemachine.com/talk1/17b.html
|
||||
// http://mrl.nyu.edu/~perlin/noise/
|
||||
// generate permutation
|
||||
const p = new Array(512);
|
||||
for (i = 0; i < 256; ++i) { p[i] = i; }
|
||||
for (i = 0; i < 256; ++i) { const t = p[j = rnd.nextInt() & 0xFF]; p[j] = p[i]; p[i] = t; }
|
||||
// copy to avoid taking mod in p[0];
|
||||
for (i = 0; i < 256; ++i) { p[i + 256] = p[i]; }
|
||||
|
||||
function grad3d(i, x, y, z) {
|
||||
const h = i & 15; // convert into 12 gradient directions
|
||||
const u = h < 8 ? x : y;
|
||||
|
||||
|
||||
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
|
||||
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
||||
}
|
||||
|
||||
function grad2d(i, x, y) {
|
||||
const v = (i & 1) === 0 ? x : y;
|
||||
return (i & 2) === 0 ? -v : v;
|
||||
}
|
||||
|
||||
function grad1d(i, x) {
|
||||
return (i & 1) === 0 ? -x : x;
|
||||
}
|
||||
|
||||
function lerp(t, a, b) { return a + t * (b - a); }
|
||||
|
||||
this.noise3d = function (x, y, z) {
|
||||
const X = Math.floor(x) & 255; const Y = Math.floor(y) & 255; const
|
||||
Z = Math.floor(z) & 255;
|
||||
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
|
||||
const fx = (3 - 2 * x) * x * x; const fy = (3 - 2 * y) * y * y; const
|
||||
fz = (3 - 2 * z) * z * z;
|
||||
const p0 = p[X] + Y; const p00 = p[p0] + Z; const p01 = p[p0 + 1] + Z; const p1 = p[X + 1] + Y; const p10 = p[p1] + Z; const
|
||||
p11 = p[p1 + 1] + Z;
|
||||
return lerp(fz,
|
||||
lerp(fy, lerp(fx, grad3d(p[p00], x, y, z), grad3d(p[p10], x - 1, y, z)),
|
||||
lerp(fx, grad3d(p[p01], x, y - 1, z), grad3d(p[p11], x - 1, y - 1, z))),
|
||||
lerp(fy, lerp(fx, grad3d(p[p00 + 1], x, y, z - 1), grad3d(p[p10 + 1], x - 1, y, z - 1)),
|
||||
lerp(fx, grad3d(p[p01 + 1], x, y - 1, z - 1), grad3d(p[p11 + 1], x - 1, y - 1, z - 1))));
|
||||
};
|
||||
|
||||
this.noise2d = function (x, y) {
|
||||
const X = Math.floor(x) & 255; const
|
||||
Y = Math.floor(y) & 255;
|
||||
x -= Math.floor(x); y -= Math.floor(y);
|
||||
const fx = (3 - 2 * x) * x * x; const
|
||||
fy = (3 - 2 * y) * y * y;
|
||||
const p0 = p[X] + Y; const
|
||||
p1 = p[X + 1] + Y;
|
||||
return lerp(fy,
|
||||
lerp(fx, grad2d(p[p0], x, y), grad2d(p[p1], x - 1, y)),
|
||||
lerp(fx, grad2d(p[p0 + 1], x, y - 1), grad2d(p[p1 + 1], x - 1, y - 1)));
|
||||
};
|
||||
|
||||
this.noise1d = function (x) {
|
||||
const X = Math.floor(x) & 255;
|
||||
x -= Math.floor(x);
|
||||
const fx = (3 - 2 * x) * x * x;
|
||||
return lerp(fx, grad1d(p[X], x), grad1d(p[X + 1], x - 1));
|
||||
};
|
||||
}
|
||||
|
||||
// these are lifted from Processing.js
|
||||
// processing defaults
|
||||
const noiseProfile = {
|
||||
generator: undefined, octaves: 4, fallout: 0.5, seed: undefined,
|
||||
};
|
||||
|
||||
module.exports = function noise(x, y, z) {
|
||||
if (noiseProfile.generator === undefined) {
|
||||
// caching
|
||||
noiseProfile.generator = new PerlinNoise(noiseProfile.seed);
|
||||
}
|
||||
const generator = noiseProfile.generator;
|
||||
let effect = 1; let k = 1; let
|
||||
sum = 0;
|
||||
for (let i = 0; i < noiseProfile.octaves; ++i) {
|
||||
effect *= noiseProfile.fallout;
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
sum += effect * (1 + generator.noise1d(k * x)) / 2; break;
|
||||
case 2:
|
||||
sum += effect * (1 + generator.noise2d(k * x, k * y)) / 2; break;
|
||||
case 3:
|
||||
sum += effect * (1 + generator.noise3d(k * x, k * y, k * z)) / 2; break;
|
||||
}
|
||||
k *= 2;
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
220
html-client/lib/fizzy-text.js
Normal file
220
html-client/lib/fizzy-text.js
Normal file
@ -0,0 +1,220 @@
|
||||
const noise = require('./fizzy-noise');
|
||||
|
||||
function fizzyText(message) {
|
||||
const that = this;
|
||||
|
||||
// These are the variables that we manipulate with gui-dat.
|
||||
// Notice they're all defined with "this". That makes them public.
|
||||
// Otherwise, gui-dat can't see them.
|
||||
|
||||
this.growthSpeed = 0.8; // how fast do particles change size?
|
||||
this.minSize = 1;
|
||||
this.maxSize = 4; // how big can they get?
|
||||
this.noiseStrength = 10; // how turbulent is the flow?
|
||||
this.speed = 0.4; // how fast do particles move?
|
||||
this.displayOutline = false; // should we draw the message as a stroke?
|
||||
this.framesRendered = 0;
|
||||
|
||||
// //////////////////////////////////////////////////////////////
|
||||
|
||||
const _this = this;
|
||||
|
||||
const width = 550;
|
||||
const height = 200;
|
||||
const textAscent = 101;
|
||||
const textOffsetLeft = 80;
|
||||
const noiseScale = 300;
|
||||
const frameTime = 30;
|
||||
|
||||
const colors = ['#000000', '#1A1A1A', '#163C50', '#205A79', '#2A78A2'];
|
||||
|
||||
// This is the context we use to get a bitmap of text using
|
||||
// the getImageData function.
|
||||
const r = document.createElement('canvas');
|
||||
const s = r.getContext('2d');
|
||||
|
||||
// This is the context we actually use to draw.
|
||||
const c = document.createElement('canvas');
|
||||
const g = c.getContext('2d');
|
||||
|
||||
r.setAttribute('width', width);
|
||||
c.setAttribute('width', width);
|
||||
r.setAttribute('height', height);
|
||||
c.setAttribute('height', height);
|
||||
|
||||
// Add our demo to the HTML
|
||||
document.getElementById('fizzytext').appendChild(c);
|
||||
|
||||
// Stores bitmap image
|
||||
let pixels = [];
|
||||
|
||||
// Stores a list of particles
|
||||
const particles = [];
|
||||
|
||||
// Set g.font to the same font as the bitmap canvas, incase we
|
||||
// want to draw some outlines.
|
||||
s.font = g.font = '800 82px monospace, monospace';
|
||||
|
||||
// Instantiate some particles
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
particles.push(new Particle(Math.random() * width, Math.random() * height));
|
||||
}
|
||||
|
||||
// This function creates a bitmap of pixels based on your message
|
||||
// It's called every time we change the message property.
|
||||
const createBitmap = function (msg) {
|
||||
s.fillStyle = '#fff';
|
||||
s.fillRect(0, 0, width, height);
|
||||
|
||||
s.fillStyle = '#222';
|
||||
s.fillText(msg, textOffsetLeft, textAscent);
|
||||
|
||||
// Pull reference
|
||||
const imageData = s.getImageData(0, 0, width, height);
|
||||
pixels = imageData.data;
|
||||
};
|
||||
|
||||
// Called once per frame, updates the animation.
|
||||
const render = function () {
|
||||
that.framesRendered++;
|
||||
|
||||
g.clearRect(0, 0, width, height);
|
||||
|
||||
if (_this.displayOutline) {
|
||||
g.globalCompositeOperation = 'source-over';
|
||||
g.strokeStyle = '#000';
|
||||
g.lineWidth = 0.5;
|
||||
g.strokeText(message, textOffsetLeft, textAscent);
|
||||
}
|
||||
|
||||
g.globalCompositeOperation = 'darker';
|
||||
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
g.fillStyle = colors[i % colors.length];
|
||||
particles[i].render();
|
||||
}
|
||||
};
|
||||
|
||||
// Returns x, y coordinates for a given index in the pixel array.
|
||||
const getPosition = function (i) {
|
||||
return {
|
||||
x: (i - (width * 4) * Math.floor(i / (width * 4))) / 4,
|
||||
y: Math.floor(i / (width * 4)),
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a color for a given pixel in the pixel array.
|
||||
const getColor = function (x, y) {
|
||||
const base = (Math.floor(y) * width + Math.floor(x)) * 4;
|
||||
const c = {
|
||||
r: pixels[base + 0],
|
||||
g: pixels[base + 1],
|
||||
b: pixels[base + 2],
|
||||
a: pixels[base + 3],
|
||||
};
|
||||
|
||||
return `rgb(${c.r},${c.g},${c.b})`;
|
||||
};
|
||||
|
||||
this.message = message;
|
||||
createBitmap(message);
|
||||
|
||||
var loop = function () {
|
||||
requestAnimationFrame(loop);
|
||||
render();
|
||||
};
|
||||
|
||||
// This calls the render function every 30 milliseconds.
|
||||
loop();
|
||||
|
||||
// This class is responsible for drawing and moving those little
|
||||
// colored dots.
|
||||
function Particle(x, y, c) {
|
||||
// Position
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
// Size of particle
|
||||
this.r = 1;
|
||||
|
||||
// This velocity is used by the explode function.
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
|
||||
this.constrain = function constrainFn(v, o1, o2) {
|
||||
if (v < o1) v = o1;
|
||||
else if (v > o2) v = o2;
|
||||
return v;
|
||||
};
|
||||
|
||||
// Called every frame
|
||||
this.render = function renderFrame() {
|
||||
// What color is the pixel we're sitting on top of?
|
||||
const c = getColor(this.x, this.y);
|
||||
|
||||
// Where should we move?
|
||||
const angle = noise(this.x / noiseScale, this.y / noiseScale) * _this.noiseStrength;
|
||||
// var angle = -Math.PI/2;
|
||||
|
||||
// Are we within the boundaries of the image?
|
||||
const onScreen = this.x > 0 && this.x < width && this.y > 0 && this.y < height;
|
||||
|
||||
const isBlack = c !== 'rgb(255,255,255)' && onScreen;
|
||||
|
||||
// If we're on top of a black pixel, grow.
|
||||
// If not, shrink.
|
||||
if (isBlack) {
|
||||
this.r += _this.growthSpeed;
|
||||
} else {
|
||||
this.r -= _this.growthSpeed;
|
||||
}
|
||||
|
||||
// This velocity is used by the explode function.
|
||||
this.vx *= 0.5;
|
||||
this.vy *= 0.5;
|
||||
|
||||
// Change our position based on the flow field and our
|
||||
// explode velocity.
|
||||
this.x += Math.cos(angle) * _this.speed + this.vx;
|
||||
this.y += -Math.sin(angle) * _this.speed + this.vy;
|
||||
|
||||
if (this.r > _this.maxSize) {
|
||||
this.r = _this.maxSize;
|
||||
} else if (this.r < 0) {
|
||||
this.r = 0;
|
||||
this.x = Math.random() * width;
|
||||
this.y = Math.random() * height;
|
||||
return false;
|
||||
}
|
||||
|
||||
// this.r = 3;
|
||||
// debugger
|
||||
// console.log(DAT.GUI.constrain(this.r, 0, _this.maxSize));
|
||||
// this.r = this.constrain(this.r, _this.minSize, _this.maxSize);
|
||||
|
||||
// If we're tiny, keep moving around until we find a black
|
||||
// pixel.
|
||||
if (this.r <= 0) {
|
||||
this.x = Math.random() * width;
|
||||
this.y = Math.random() * height;
|
||||
return false; // Don't draw!
|
||||
}
|
||||
|
||||
// If we're off the screen, go over to other side
|
||||
if (this.x < 0) this.x = width;
|
||||
if (this.x > width) this.x = 0;
|
||||
if (this.y < 0) this.y = height;
|
||||
if (this.y > height) this.y = 0;
|
||||
|
||||
// Draw the circle.
|
||||
g.beginPath();
|
||||
// g.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
|
||||
g.rect(this.x, this.y, this.r, this.r);
|
||||
g.fill();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fizzyText;
|
||||
39
html-client/package.json
Executable file
39
html-client/package.json
Executable file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "cryps-client",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "parcel index.html --port 40080",
|
||||
"build": "rm -rf dist && parcel build index.html",
|
||||
"lint": "eslint --fix --ext .jsx src/",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"borc": "^2.0.3",
|
||||
"bulma-toast": "^1.2.0",
|
||||
"docco": "^0.7.0",
|
||||
"izitoast": "^1.4.0",
|
||||
"jdenticon": "^2.1.0",
|
||||
"key": "^0.1.11",
|
||||
"keymaster": "^1.6.2",
|
||||
"lodash": "^4.17.11",
|
||||
"parcel": "^1.12.3",
|
||||
"phaser": "^3.15.1",
|
||||
"preact": "^8.3.1",
|
||||
"preact-redux": "^2.0.3",
|
||||
"redux": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"eslint": "^5.6.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"jest": "^18.0.0"
|
||||
}
|
||||
}
|
||||
26
html-client/src/actions.jsx
Normal file
26
html-client/src/actions.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
export const SET_ACCOUNT = 'SET_ACCOUNT';
|
||||
export const setAccount = value => ({ type: SET_ACCOUNT, value });
|
||||
|
||||
export const SET_CRYPS = 'SET_CRYPS';
|
||||
export const setCryps = value => ({ type: SET_CRYPS, value });
|
||||
|
||||
export const SET_INSTANCES = 'SET_INSTANCES';
|
||||
export const setInstances = value => ({ type: SET_INSTANCES, value });
|
||||
|
||||
export const SET_INSTANCE = 'SET_INSTANCE';
|
||||
export const setInstance = value => ({ type: SET_INSTANCE, value });
|
||||
|
||||
export const SET_GAME = 'SET_GAME';
|
||||
export const setGame = value => ({ type: SET_GAME, value });
|
||||
|
||||
export const SET_ACTIVE_ITEM = 'SET_ACTIVE_ITEM';
|
||||
export const setActiveItem = value => ({ type: SET_ACTIVE_ITEM, value });
|
||||
|
||||
export const SET_ACTIVE_INCOMING = 'SET_ACTIVE_INCOMING';
|
||||
export const setActiveIncoming = value => ({ type: SET_ACTIVE_INCOMING, value });
|
||||
|
||||
export const SET_ACTIVE_SKILL = 'SET_ACTIVE_SKILL';
|
||||
export const setActiveSkill = (crypId, skill) => ({ type: SET_ACTIVE_SKILL, value: crypId ? { crypId, skill } : null });
|
||||
|
||||
export const SET_WS = 'SET_WS';
|
||||
export const setWs = value => ({ type: SET_WS, value });
|
||||
72
html-client/src/components/body.component.jsx
Executable file
72
html-client/src/components/body.component.jsx
Executable file
@ -0,0 +1,72 @@
|
||||
// eslint-disable-next-line
|
||||
const preact = require('preact');
|
||||
const { connect } = require('preact-redux');
|
||||
const actions = require('../actions');
|
||||
|
||||
const InstanceListContainer = require('./instance.list.container');
|
||||
const CrypSpawnContainer = require('./cryp.spawn.container');
|
||||
const CrypListContainer = require('./cryp.list.container');
|
||||
const GameContainer = require('./game.container');
|
||||
const InstanceContainer = require('./instance.container');
|
||||
|
||||
const addState = connect(
|
||||
(state) => {
|
||||
const { game, instance, ws } = state;
|
||||
|
||||
if (!game) {
|
||||
console.log('clear gs interval');
|
||||
// ws.clearGameStateInterval();
|
||||
}
|
||||
|
||||
return { game, instance };
|
||||
},
|
||||
(dispatch) => {
|
||||
function setGame(game) {
|
||||
dispatch(actions.setGame(game));
|
||||
}
|
||||
return { setGame };
|
||||
},
|
||||
);
|
||||
|
||||
function renderBody(props) {
|
||||
const { game, instance, setGame } = props;
|
||||
if (game) {
|
||||
return (
|
||||
<div>
|
||||
<GameContainer />
|
||||
<button
|
||||
className="button is-dark is-fullwidth"
|
||||
type="submit"
|
||||
onClick={() => setGame(null)}
|
||||
>
|
||||
Return to Main Menu
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
return (
|
||||
<div>
|
||||
<InstanceContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="background"/>
|
||||
<CrypSpawnContainer />
|
||||
<section className="row" >
|
||||
<div className="six columns">
|
||||
<CrypListContainer />
|
||||
</div>
|
||||
<div className="six columns">
|
||||
<InstanceListContainer />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = addState(renderBody);
|
||||
27
html-client/src/components/cryp.list.container.js
Normal file
27
html-client/src/components/cryp.list.container.js
Normal file
@ -0,0 +1,27 @@
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const CrypList = require('./cryp.list');
|
||||
|
||||
const addState = connect(
|
||||
function receiveState(state) {
|
||||
const { ws, cryps, activeItem } = state;
|
||||
function sendGamePve(crypId) {
|
||||
return ws.sendGamePve(crypId);
|
||||
}
|
||||
|
||||
function sendGamePvp(crypIds) {
|
||||
return ws.sendGamePvp(crypIds);
|
||||
}
|
||||
|
||||
function sendItemUse(targetId) {
|
||||
if (activeItem) {
|
||||
return ws.sendItemUse(activeItem, targetId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return { cryps, sendGamePve, sendGamePvp, activeItem, sendItemUse };
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = addState(CrypList);
|
||||
29
html-client/src/components/cryp.list.jsx
Executable file
29
html-client/src/components/cryp.list.jsx
Executable file
@ -0,0 +1,29 @@
|
||||
const preact = require('preact');
|
||||
|
||||
const { stringSort } = require('./../utils');
|
||||
|
||||
const idSort = stringSort('id');
|
||||
|
||||
function CrypList({ cryps, activeCryp, avatar }) {
|
||||
if (!cryps) return <div>not ready</div>;
|
||||
|
||||
const crypPanels = cryps.sort(idSort).map(cryp => (
|
||||
<div key={cryp.id} className="home-cryp">
|
||||
<h2>{cryp.name}</h2>
|
||||
<div>{cryp.hp.value} HP</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={cryp.hp.value === 0}
|
||||
onClick={() => activeCryp(cryp.id)}>
|
||||
activate
|
||||
</button>
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
{crypPanels}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = CrypList;
|
||||
32
html-client/src/components/cryp.spawn.button.jsx
Normal file
32
html-client/src/components/cryp.spawn.button.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
const preact = require('preact');
|
||||
|
||||
function renderSpawnButton({ account, sendCrypSpawn }) {
|
||||
let name = '';
|
||||
|
||||
if (!account) return <div>...</div>;
|
||||
|
||||
return false;
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="two columns">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="cryp name"
|
||||
onChange={e => (name = e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="two columns">
|
||||
<button
|
||||
className="button"
|
||||
type="submit"
|
||||
onClick={() => sendCrypSpawn(name)}>
|
||||
Spawn 👾
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = renderSpawnButton;
|
||||
16
html-client/src/components/cryp.spawn.container.js
Normal file
16
html-client/src/components/cryp.spawn.container.js
Normal file
@ -0,0 +1,16 @@
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const CrypSpawnButton = require('./cryp.spawn.button');
|
||||
|
||||
const addState = connect(
|
||||
function receiveState(state) {
|
||||
const { ws } = state;
|
||||
function sendCrypSpawn(name) {
|
||||
return ws.sendCrypSpawn(name);
|
||||
}
|
||||
|
||||
return { account: state.account, sendCrypSpawn };
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = addState(CrypSpawnButton);
|
||||
48
html-client/src/components/game.container.js
Normal file
48
html-client/src/components/game.container.js
Normal file
@ -0,0 +1,48 @@
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const actions = require('../actions');
|
||||
|
||||
const Game = require('./game');
|
||||
|
||||
const addState = connect(
|
||||
function receiveState(state) {
|
||||
const { ws, game, account, activeSkill, activeIncoming } = state;
|
||||
|
||||
function selectSkillTarget(targetTeamId) {
|
||||
if (activeSkill) {
|
||||
return ws.sendGameSkill(game.id, activeSkill.crypId, targetTeamId, activeSkill.skill.skill);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// intercept self casting skills
|
||||
if (activeSkill && activeSkill.skill.self_targeting) {
|
||||
ws.sendGameSkill(game.id, activeSkill.crypId, null, activeSkill.skill.skill);
|
||||
}
|
||||
|
||||
function selectIncomingTarget(crypId) {
|
||||
if (activeIncoming) {
|
||||
return ws.sendGameTarget(game.id, crypId, activeIncoming);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return { game, account, activeSkill, activeIncoming, selectSkillTarget, selectIncomingTarget };
|
||||
},
|
||||
|
||||
function receiveDispatch(dispatch) {
|
||||
function setActiveSkill(crypId, skill) {
|
||||
dispatch(actions.setActiveSkill(crypId, skill));
|
||||
}
|
||||
|
||||
function setActiveIncoming(skillId) {
|
||||
dispatch(actions.setActiveIncoming(skillId));
|
||||
}
|
||||
|
||||
|
||||
return { setActiveSkill, setActiveIncoming };
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
module.exports = addState(Game);
|
||||
42
html-client/src/components/game.join.button.jsx
Normal file
42
html-client/src/components/game.join.button.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
const preact = require('preact');
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const addState = connect(
|
||||
(state) => {
|
||||
const { ws, cryps } = state;
|
||||
function sendGameJoin(gameId) {
|
||||
return ws.sendGameJoin(gameId, [cryps[0].id]);
|
||||
}
|
||||
|
||||
return { account: state.account, sendGameJoin };
|
||||
},
|
||||
);
|
||||
|
||||
function GameJoinButton({ account, sendGameJoin }) {
|
||||
let gameId = '';
|
||||
|
||||
if (!account) return <div>...</div>;
|
||||
|
||||
return (
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="gameId"
|
||||
onChange={e => (gameId = e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<button
|
||||
className="button is-dark is-fullwidth"
|
||||
type="submit"
|
||||
onClick={() => sendGameJoin(gameId)}>
|
||||
Join Game
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = addState(GameJoinButton);
|
||||
184
html-client/src/components/game.jsx
Executable file
184
html-client/src/components/game.jsx
Executable file
@ -0,0 +1,184 @@
|
||||
const preact = require('preact');
|
||||
const key = require('keymaster');
|
||||
|
||||
const SKILL_HOT_KEYS = ['Q', 'W', 'E', 'R'];
|
||||
|
||||
function GamePanel(props) {
|
||||
const {
|
||||
game,
|
||||
activeSkill,
|
||||
activeIncoming,
|
||||
setActiveSkill,
|
||||
setActiveIncoming,
|
||||
selectSkillTarget,
|
||||
selectIncomingTarget,
|
||||
account,
|
||||
} = props;
|
||||
|
||||
if (!game) return <div>...</div>;
|
||||
|
||||
const otherTeams = game.teams.filter(t => t.id !== account.id);
|
||||
|
||||
const playerTeam = game.teams.find(t => t.id === account.id);
|
||||
|
||||
const incoming = game.stack.filter(s => s.target_team_id === playerTeam.id).map((inc) => {
|
||||
key.unbind('1');
|
||||
key('1', () => setActiveIncoming(inc.id));
|
||||
return (
|
||||
<div className="tile is-child" key={inc.id}>
|
||||
<div>{JSON.stringify(inc)}</div>
|
||||
<button
|
||||
className="button is-dark is-fullwidth"
|
||||
type="submit"
|
||||
onClick={() => setActiveIncoming(inc.id)}>
|
||||
(1) Block skill: {inc.skill}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
function PlayerCrypCard(cryp) {
|
||||
const skills = cryp.skills.map((skill, i) => {
|
||||
const hotkey = SKILL_HOT_KEYS[i];
|
||||
key.unbind(hotkey);
|
||||
key(hotkey, () => setActiveSkill(cryp.id, skill));
|
||||
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
className="button is-dark"
|
||||
type="submit"
|
||||
onClick={() => setActiveSkill(cryp.id, skill)}
|
||||
>
|
||||
({hotkey}) {skill.skill} {skill.cd && `(${skill.cd}T)`}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
const effects = cryp.effects.map((effect, i) => (
|
||||
<div key={i}>{effect} for {effect.turns}T</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div
|
||||
key={cryp.id}
|
||||
style={ activeIncoming ? { cursor: 'pointer' } : {}}
|
||||
onClick={() => selectIncomingTarget(cryp.id)}
|
||||
className="tile is-vertical">
|
||||
<div className="tile is-child">
|
||||
<div className="columns" >
|
||||
<div className="column is-10">
|
||||
<p className="title">{cryp.name}</p>
|
||||
<p className="subtitle">Level {cryp.lvl}</p>
|
||||
</div>
|
||||
<div className="column">
|
||||
<figure className="image">
|
||||
<svg width="40" height="40" data-jdenticon-value={cryp.name} />
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="has-text-centered">{cryp.hp.value} / {cryp.stam.value} HP </div>
|
||||
<progress className="progress is-dark" value={cryp.hp.value} max={cryp.stam.value}></progress>
|
||||
|
||||
<div className="has-text-centered">{cryp.xp} / {Math.pow(2, cryp.lvl + 1)} XP </div>
|
||||
<progress className="progress is-dark" value={cryp.xp} max={Math.pow(2, cryp.lvl + 1)}></progress>
|
||||
</div>
|
||||
{effects}
|
||||
{skills}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PlayerTeam(team) {
|
||||
const cryps = team.cryps.map(c => PlayerCrypCard(c, setActiveSkill));
|
||||
|
||||
return (
|
||||
<div className="tile">
|
||||
{cryps}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpponentCrypCard(cryp) {
|
||||
const effects = cryp.effects.map((effect, i) => (
|
||||
<div key={i}>{effect.effect} for {effect.turns}T</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div key={cryp.id} className="tile is-vertical">
|
||||
<div className="tile is-child">
|
||||
<div className="columns" >
|
||||
<div className="column is-10">
|
||||
<p className="title">{cryp.name}</p>
|
||||
<p className="subtitle">Level {cryp.lvl}</p>
|
||||
</div>
|
||||
<div className="column">
|
||||
<figure className="image">
|
||||
<svg width="40" height="40" data-jdenticon-value={cryp.name} />
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="has-text-centered">{cryp.hp.value} / {cryp.stam.value} HP </div>
|
||||
<progress className="progress is-dark" value={cryp.hp.value} max={cryp.stam.value}></progress>
|
||||
|
||||
<div className="has-text-centered">{cryp.xp} / {Math.pow(2, cryp.lvl + 1)} XP </div>
|
||||
<progress className="progress is-dark" value={cryp.xp} max={Math.pow(2, cryp.lvl + 1)}></progress>
|
||||
|
||||
</div>
|
||||
{effects}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpponentTeam(team) {
|
||||
const cryps = team.cryps.map(OpponentCrypCard);
|
||||
return (
|
||||
<div
|
||||
className="tile"
|
||||
style={activeSkill ? { cursor: 'pointer' } : {}}
|
||||
onClick={() => selectSkillTarget(team.id)} >
|
||||
{cryps}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// style={{ "min-height": "100%" }}
|
||||
function phaseText(phase) {
|
||||
switch (phase) {
|
||||
case 'Skill':
|
||||
return 'Choose abilities';
|
||||
case 'Target':
|
||||
return 'Block abilities';
|
||||
case 'Finish':
|
||||
return 'Game over';
|
||||
}
|
||||
}
|
||||
|
||||
const logs = game.log.reverse().map((l, i) => (<div key={i}>{l}</div>));
|
||||
|
||||
return (
|
||||
<section className="columns">
|
||||
<div className="column is-2 title is-1">
|
||||
{phaseText(game.phase)}
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
{PlayerTeam(playerTeam, setActiveSkill)}
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<div>
|
||||
{otherTeams.map(OpponentTeam)}
|
||||
</div>
|
||||
<div>
|
||||
{incoming}
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-2">
|
||||
<div className="title is-4">{logs}</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = GamePanel;
|
||||
17
html-client/src/components/header.component.jsx
Normal file
17
html-client/src/components/header.component.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
// eslint-disable-next-line
|
||||
const preact = require('preact');
|
||||
|
||||
const LoginContainer = require('./login.container');
|
||||
|
||||
function renderHeader() {
|
||||
return (
|
||||
<header className="row header">
|
||||
<h1 className="cryps-title six columns">
|
||||
cryps.gg
|
||||
</h1>
|
||||
<LoginContainer />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = renderHeader;
|
||||
78
html-client/src/components/instance.component.jsx
Normal file
78
html-client/src/components/instance.component.jsx
Normal file
@ -0,0 +1,78 @@
|
||||
const preact = require('preact');
|
||||
const key = require('keymaster');
|
||||
|
||||
function convertVar(v) {
|
||||
return v || '';
|
||||
}
|
||||
|
||||
function Vbox(vbox) {
|
||||
if (!vbox) return false;
|
||||
|
||||
const free = [];
|
||||
for (let i = 0 ; i < vbox.free[0].length; i++) {
|
||||
free.push([vbox.free[0][i], vbox.free[1][i], vbox.free[2][i]]);
|
||||
}
|
||||
|
||||
const rows = free.map((row, i) => (
|
||||
<tr key={i}>
|
||||
<td>{convertVar(row[0])}</td>
|
||||
<td>{convertVar(row[1])}</td>
|
||||
<td>{convertVar(row[2])}</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="four columns">
|
||||
<span>vBox</span>
|
||||
<table className="vbox-table">
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
{JSON.stringify(vbox)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InstanceComponent(props) {
|
||||
const {
|
||||
instance,
|
||||
account,
|
||||
sendInstanceReady,
|
||||
quit,
|
||||
} = props;
|
||||
|
||||
if (!instance) return <div>...</div>;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="row">
|
||||
<div className="six columns">
|
||||
<button
|
||||
className="instance-btn instance-ui-btn glow-btn"
|
||||
onClick={quit}>
|
||||
Menu
|
||||
</button>
|
||||
</div>
|
||||
<div className="six columns">
|
||||
<button
|
||||
className="instance-btn instance-ui-btn green-btn u-pull-right"
|
||||
onClick={() => sendInstanceReady()}>
|
||||
Ready
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
{Vbox(instance.vbox)}
|
||||
<div className="four columns">
|
||||
{JSON.stringify(instance.cryps)}
|
||||
</div>
|
||||
<div className="four columns">
|
||||
ready btn
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = InstanceComponent;
|
||||
27
html-client/src/components/instance.container.jsx
Normal file
27
html-client/src/components/instance.container.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const actions = require('../actions');
|
||||
|
||||
const Instance = require('./instance.component');
|
||||
|
||||
const addState = connect(
|
||||
function receiveState(state) {
|
||||
const { ws, instance, account } = state;
|
||||
|
||||
function sendInstanceReady() {
|
||||
return ws.sendInstanceReady(instance.id);
|
||||
}
|
||||
|
||||
return { instance, account, sendInstanceReady };
|
||||
},
|
||||
|
||||
function receiveDispatch(dispatch, { instance }) {
|
||||
function quit() {
|
||||
dispatch(actions.setInstance(null));
|
||||
}
|
||||
return { quit };
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
module.exports = addState(Instance);
|
||||
27
html-client/src/components/instance.list.container.jsx
Normal file
27
html-client/src/components/instance.list.container.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const actions = require('../actions');
|
||||
|
||||
const InstanceList = require('./instance.list');
|
||||
|
||||
const addState = connect(
|
||||
function receiveState(state) {
|
||||
const { ws, events, instances } = state;
|
||||
function setCrypsSet() {
|
||||
console.log('set crypos');
|
||||
// return ws.sendGamePvp(crypIds);
|
||||
}
|
||||
|
||||
return { instances, setCrypsSet };
|
||||
},
|
||||
|
||||
function receiveDispatch(dispatch) {
|
||||
function setActiveInstance(instance) {
|
||||
dispatch(actions.setInstance(instance));
|
||||
}
|
||||
|
||||
return { setActiveInstance };
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = addState(InstanceList);
|
||||
31
html-client/src/components/instance.list.jsx
Normal file
31
html-client/src/components/instance.list.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
// eslint-disable-next-line
|
||||
const preact = require('preact');
|
||||
|
||||
const { NULL_UUID } = require('./../utils');
|
||||
|
||||
function instanceList({ instances, setActiveInstance, sendCrypsSet }) {
|
||||
if (!instances) return <div>...</div>;
|
||||
const instancePanels = instances.map((instance) => {
|
||||
const name = instance.instance === NULL_UUID
|
||||
? 'Normal Mode'
|
||||
: `${instance.instance.substring(0, 5)}`;
|
||||
|
||||
return (
|
||||
<button
|
||||
className="instance-btn glow-btn"
|
||||
key={instance.id}
|
||||
onClick={() => setActiveInstance(instance)}>
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h2>Instances</h2>
|
||||
{instancePanels}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = instanceList;
|
||||
20
html-client/src/components/item.list.container.js
Normal file
20
html-client/src/components/item.list.container.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { connect } = require('preact-redux');
|
||||
const actions = require('../actions');
|
||||
|
||||
const ItemList = require('./item.list');
|
||||
|
||||
const addState = connect(
|
||||
function receiveState(state) {
|
||||
const { items } = state;
|
||||
return { items };
|
||||
},
|
||||
function receiveDispatch(dispatch) {
|
||||
function setActiveItem(id) {
|
||||
dispatch(actions.setActiveItem(id))
|
||||
}
|
||||
|
||||
return { setActiveItem };
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = addState(ItemList);
|
||||
37
html-client/src/components/item.list.jsx
Normal file
37
html-client/src/components/item.list.jsx
Normal file
@ -0,0 +1,37 @@
|
||||
// eslint-disable-next-line
|
||||
const preact = require('preact');
|
||||
|
||||
function ItemList({ items, setActiveItem }) {
|
||||
if (!items) return <div>...</div>;
|
||||
const itemPanels = items.map(item => (
|
||||
|
||||
<div key={item.id} className="tile is-parent is-vertical">
|
||||
<div className="tile is-vertical is-child">
|
||||
<div className="columns" >
|
||||
<div className="column is-8">
|
||||
<p className="title">{item.action}</p>
|
||||
<p className="subtitle">∞</p>
|
||||
</div>
|
||||
<div className="column">
|
||||
<figure className="image">
|
||||
<svg width="40" height="40" data-jdenticon-value={item.action} />
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="button is-dark"
|
||||
type="submit"
|
||||
onClick={() => setActiveItem(item.id)}>
|
||||
Use
|
||||
</button>
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
{itemPanels}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = ItemList;
|
||||
77
html-client/src/components/login.component.jsx
Executable file
77
html-client/src/components/login.component.jsx
Executable file
@ -0,0 +1,77 @@
|
||||
// eslint-disable-next-line
|
||||
const preact = require('preact');
|
||||
|
||||
function renderLogin({ account, submitLogin, submitRegister }) {
|
||||
if (account) return (
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" className="ping-svg">
|
||||
<path d="M0,50l50,-50v100l50,-50" className="ping-path"/>
|
||||
<path d="M0,50l50,-50v100l50,-50" className="ping-path"/>
|
||||
</svg>
|
||||
<h3 className="header-username">{account.name}</h3>
|
||||
</div>
|
||||
);
|
||||
|
||||
const details = {
|
||||
name: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login" >
|
||||
<div className="field">
|
||||
<p className="control has-icons-left has-icons-right">
|
||||
<input
|
||||
className="input"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
onChange={e => (details.name = e.target.value)}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-user" />
|
||||
</span>
|
||||
<span className="icon is-small is-right">
|
||||
<i className="fas fa-check" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="field">
|
||||
<p className="control has-icons-left">
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
onChange={e => (details.password = e.target.value)}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-lock" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="field">
|
||||
<p className="control">
|
||||
<button
|
||||
className="button inverted"
|
||||
type="submit"
|
||||
onClick={() => submitLogin('ntr', 'grepgrepgrep')}>
|
||||
Default
|
||||
</button>
|
||||
<button
|
||||
className="button inverted"
|
||||
type="submit"
|
||||
onClick={() => submitLogin(details.name, details.password)}>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
className="button inverted"
|
||||
type="submit"
|
||||
onClick={() => submitRegister(details.name, details.password)}>
|
||||
Register
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = renderLogin;
|
||||
18
html-client/src/components/login.container.jsx
Executable file
18
html-client/src/components/login.container.jsx
Executable file
@ -0,0 +1,18 @@
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const Login = require('./login.component');
|
||||
|
||||
const addState = connect(
|
||||
(state) => {
|
||||
const { ws } = state;
|
||||
function submitLogin(name, password) {
|
||||
return ws.sendAccountLogin(name, password);
|
||||
}
|
||||
function submitRegister(name, password) {
|
||||
return ws.sendAccountRegister(name, password);
|
||||
}
|
||||
return { account: state.account, submitLogin, submitRegister };
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = addState(Login);
|
||||
47
html-client/src/components/navbar.jsx
Normal file
47
html-client/src/components/navbar.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
const preact = require('preact');
|
||||
|
||||
// components all the way down
|
||||
const Icon = name => (
|
||||
<span>
|
||||
{name}
|
||||
<svg width="80" height="80" data-jdenticon-value={name} />
|
||||
</span>
|
||||
);
|
||||
|
||||
// the css attribute name `class` is reserved in js
|
||||
// so in react you have to call it `className`
|
||||
function Navbar() {
|
||||
const NAMES = ['Mashy', 'ntr'];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav className="navbar">
|
||||
<div className="navbar-end">
|
||||
<a href="/somewhere" className="navbar-item is-active">
|
||||
Home
|
||||
</a>
|
||||
<a href="/somewhere" className="navbar-item">
|
||||
Store
|
||||
</a>
|
||||
<a href="/somewhere" className="navbar-item">
|
||||
FAQ
|
||||
</a>
|
||||
<span className="navbar-item">
|
||||
<a href="/somewhere" className="button is-info is-inverted">
|
||||
<span className="icon">
|
||||
<svg width="80" height="80" data-jdenticon-value="Blog" />
|
||||
</span>
|
||||
<span>Blog</span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
{NAMES.map(Icon)}
|
||||
</div>
|
||||
);
|
||||
// map is a function that is called on every element of an array
|
||||
// so in this ^^ case it calls Icon('Mashy') which returns some jsx
|
||||
// that gets put into the dom
|
||||
}
|
||||
|
||||
module.exports = Navbar;
|
||||
205
html-client/src/events.jsx
Normal file
205
html-client/src/events.jsx
Normal file
@ -0,0 +1,205 @@
|
||||
const toast = require('izitoast');
|
||||
|
||||
const actions = require('./actions');
|
||||
|
||||
function registerEvents(store) {
|
||||
function setCryps(cryps) {
|
||||
console.log('EVENT ->', 'cryps', cryps);
|
||||
}
|
||||
|
||||
function setCrypList(cryps) {
|
||||
store.dispatch(actions.setCryps(cryps));
|
||||
}
|
||||
|
||||
function setWs(ws) {
|
||||
console.log('EVENT ->', 'ws', ws);
|
||||
}
|
||||
|
||||
function setGame(game) {
|
||||
return console.log('EVENT ->', 'game', game);
|
||||
}
|
||||
|
||||
function setAccount(account) {
|
||||
store.dispatch(actions.setAccount(account));
|
||||
}
|
||||
|
||||
function setActiveSkill(skill) {
|
||||
console.log('EVENT ->', 'activeSkill', skill);
|
||||
}
|
||||
|
||||
function setMenu() {
|
||||
console.log('EVENT ->', 'menu', true);
|
||||
}
|
||||
|
||||
function setVbox(items) {
|
||||
console.log('EVENT ->', 'vbox', items);
|
||||
}
|
||||
|
||||
function setScores(scores) {
|
||||
console.log('EVENT ->', 'scores', scores);
|
||||
}
|
||||
|
||||
function setInstanceList(v) {
|
||||
return store.dispatch(actions.setInstances(v));
|
||||
}
|
||||
|
||||
function setPlayer(player) {
|
||||
console.log('EVENT ->', 'player', player);
|
||||
}
|
||||
|
||||
function setZone(zone) {
|
||||
console.log('EVENT ->', 'zone', zone);
|
||||
}
|
||||
|
||||
function setGameList(gameList) {
|
||||
console.log('EVENT ->', 'gameList', gameList);
|
||||
}
|
||||
|
||||
function setCrypStatusUpdate(id, skill, target) {
|
||||
console.log('EVENT ->', 'crypStatusUpdate', { id, skill, target });
|
||||
}
|
||||
|
||||
// events.on('SET_PLAYER', setPlayer);
|
||||
|
||||
// events.on('SEND_SKILL', function skillActive(gameId, crypId, targetCrypId, skill) {
|
||||
// ws.sendGameSkill(gameId, crypId, targetCrypId, skill);
|
||||
// setCrypStatusUpdate(crypId, skill, targetCrypId);
|
||||
// });
|
||||
|
||||
// events.on('CRYP_ACTIVE', function crypActiveCb(cryp) {
|
||||
// for (let i = 0; i < cryps.length; i += 1) {
|
||||
// if (cryps[i].id === cryp.id) cryps[i].active = !cryps[i].active;
|
||||
// }
|
||||
// return setCryps(cryps);
|
||||
// });
|
||||
|
||||
const errMessages = {
|
||||
select_cryps: 'Select your cryps before battle using the numbered buttons next to the cryp avatar',
|
||||
complete_nodes: 'You need to complete the previously connected nodes first',
|
||||
max_skills: 'Your cryp can only learn a maximum of 4 skills',
|
||||
|
||||
};
|
||||
|
||||
function errorPrompt(type) {
|
||||
const message = errMessages[type];
|
||||
const OK_BUTTON = '<button type="submit">OK</button>';
|
||||
toast.info({
|
||||
theme: 'dark',
|
||||
color: 'black',
|
||||
timeout: false,
|
||||
drag: false,
|
||||
position: 'center',
|
||||
maxWidth: window.innerWidth / 2,
|
||||
close: false,
|
||||
buttons: [
|
||||
[OK_BUTTON, (instance, thisToast) => instance.hide({ transitionOut: 'fadeOut' }, thisToast)],
|
||||
],
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
// function loginPrompt() {
|
||||
// const USER_INPUT = '<input className="input" type="email" placeholder="username" />';
|
||||
// const PASSWORD_INPUT = '<input className="input" type="password" placeholder="password" />';
|
||||
// const LOGIN_BUTTON = '<button type="submit">Login</button>';
|
||||
// const REGISTER_BUTTON = '<button type="submit">Register</button>';
|
||||
// const DEMO_BUTTON = '<button type="submit">Demo</button>';
|
||||
|
||||
// const ws = registry.get('ws');
|
||||
|
||||
// function submitLogin(instance, thisToast, button, e, inputs) {
|
||||
// const USERNAME = inputs[0].value;
|
||||
// const PASSWORD = inputs[1].value;
|
||||
// ws.sendAccountLogin(USERNAME, PASSWORD);
|
||||
// }
|
||||
|
||||
// function submitRegister(instance, thisToast, button, e, inputs) {
|
||||
// const USERNAME = inputs[0].value;
|
||||
// const PASSWORD = inputs[1].value;
|
||||
// ws.sendAccountCreate(USERNAME, PASSWORD);
|
||||
// }
|
||||
|
||||
// function submitDemo() {
|
||||
// ws.sendAccountDemo();
|
||||
// }
|
||||
|
||||
// const existing = document.querySelector('#login'); // Selector of your toast
|
||||
// if (existing) toast.hide({}, existing, 'reconnect');
|
||||
|
||||
// toast.question({
|
||||
// id: 'login',
|
||||
// theme: 'dark',
|
||||
// color: 'black',
|
||||
// timeout: false,
|
||||
// // overlay: true,
|
||||
// drag: false,
|
||||
// close: false,
|
||||
// title: 'LOGIN',
|
||||
// position: 'center',
|
||||
// inputs: [
|
||||
// [USER_INPUT, 'change', () => true, true], // true to focus
|
||||
// [PASSWORD_INPUT, 'change', () => true],
|
||||
// ],
|
||||
// buttons: [
|
||||
// [LOGIN_BUTTON, submitLogin], // true to focus
|
||||
// [REGISTER_BUTTON, submitRegister], // true to focus
|
||||
// [DEMO_BUTTON, submitDemo], // true to focus
|
||||
// ],
|
||||
// });
|
||||
|
||||
// console.log('ACCOUNT', function closeLoginCb() {
|
||||
// const prompt = document.querySelector('#login'); // Selector of your toast
|
||||
// if (prompt) toast.hide({ transitionOut: 'fadeOut' }, prompt, 'EVENT ->');
|
||||
// });
|
||||
// }
|
||||
|
||||
// events.on('CRYP_SPAWN', function spawnPrompt() {
|
||||
// const NAME_INPUT = '<input className="input" type="email" placeholder="name" />';
|
||||
// const SPAWN_BUTTON = '<button type="submit">SPAWN</button>';
|
||||
|
||||
// const ws = registry.get('ws');
|
||||
|
||||
// function submitSpawn(instance, thisToast, button, e, inputs) {
|
||||
// const NAME = inputs[0].value;
|
||||
// ws.sendCrypSpawn(NAME);
|
||||
// instance.hide({ transitionOut: 'fadeOut' }, thisToast, 'button');
|
||||
// }
|
||||
|
||||
// toast.question({
|
||||
// theme: 'dark',
|
||||
// color: 'black',
|
||||
// timeout: false,
|
||||
// // overlay: true,
|
||||
// drag: false,
|
||||
// close: true,
|
||||
// title: 'SPAWN CRYP',
|
||||
// position: 'center',
|
||||
// inputs: [
|
||||
// [NAME_INPUT, 'change', null, true], // true to focus
|
||||
// ],
|
||||
// buttons: [
|
||||
// [SPAWN_BUTTON, submitSpawn], // true to focus
|
||||
// ],
|
||||
// });
|
||||
// });
|
||||
|
||||
return {
|
||||
errorPrompt,
|
||||
// loginPrompt,
|
||||
setAccount,
|
||||
setActiveSkill,
|
||||
setCryps,
|
||||
setCrypList,
|
||||
setGame,
|
||||
setMenu,
|
||||
setPlayer,
|
||||
setInstanceList,
|
||||
setVbox,
|
||||
setWs,
|
||||
setGameList,
|
||||
setZone,
|
||||
setScores,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = registerEvents;
|
||||
22
html-client/src/keyboard.jsx
Executable file
22
html-client/src/keyboard.jsx
Executable file
@ -0,0 +1,22 @@
|
||||
const key = require('keymaster');
|
||||
const actions = require('./actions');
|
||||
|
||||
function setupKeys(store) {
|
||||
store.subscribe(() => {
|
||||
const state = store.getState();
|
||||
|
||||
key.unbind('esc');
|
||||
|
||||
if (state.activeItem) {
|
||||
key('esc', () => store.dispatch(actions.setActiveItem(null)));
|
||||
}
|
||||
if (state.activeSkill) {
|
||||
key('esc', () => store.dispatch(actions.setActiveSkill(null)));
|
||||
}
|
||||
if (state.activeIncoming) {
|
||||
key('esc', () => store.dispatch(actions.setActiveIncoming(null)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = setupKeys;
|
||||
61
html-client/src/main.jsx
Executable file
61
html-client/src/main.jsx
Executable file
@ -0,0 +1,61 @@
|
||||
const preact = require('preact');
|
||||
const jdenticon = require('jdenticon');
|
||||
|
||||
const { Provider } = require('preact-redux');
|
||||
const { createStore, combineReducers } = require('redux');
|
||||
|
||||
const reducers = require('./reducers');
|
||||
const actions = require('./actions');
|
||||
// const setupKeys = require('./keyboard');
|
||||
// const fizzyText = require('../lib/fizzy-text');
|
||||
const createSocket = require('./socket');
|
||||
const registerEvents = require('./events');
|
||||
|
||||
const Header = require('./components/header.component');
|
||||
const Body = require('./components/body.component');
|
||||
|
||||
// Redux Store
|
||||
const store = createStore(
|
||||
combineReducers({
|
||||
account: reducers.accountReducer,
|
||||
game: reducers.gameReducer,
|
||||
cryps: reducers.crypsReducer,
|
||||
instances: reducers.instancesReducer,
|
||||
instance: reducers.instanceReducer,
|
||||
ws: reducers.wsReducer,
|
||||
})
|
||||
);
|
||||
|
||||
document.fonts.load('10pt "Jura"').then(() => {
|
||||
const events = registerEvents(store);
|
||||
store.subscribe(() => console.log(store.getState()));
|
||||
// setupKeys(store);
|
||||
|
||||
const ws = createSocket(events);
|
||||
store.dispatch(actions.setWs(ws));
|
||||
ws.connect();
|
||||
|
||||
// tells jdenticon to look for new svgs and render them
|
||||
// so we don't have to setInnerHtml or manually call update
|
||||
jdenticon.config = {
|
||||
replaceMode: 'observe',
|
||||
};
|
||||
|
||||
const Cryps = () => (
|
||||
<section>
|
||||
<Header />
|
||||
<Body />
|
||||
</section>
|
||||
);
|
||||
|
||||
const Main = () => (
|
||||
<Provider store={store}>
|
||||
<Cryps />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line
|
||||
preact.render(<Main />, document.body);
|
||||
|
||||
// fizzyText('cryps.gg');
|
||||
});
|
||||
70
html-client/src/reducers.jsx
Normal file
70
html-client/src/reducers.jsx
Normal file
@ -0,0 +1,70 @@
|
||||
const actions = require('./actions');
|
||||
|
||||
const defaultAccount = null;
|
||||
function accountReducer(state = defaultAccount, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_ACCOUNT:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultCryps = null;
|
||||
function crypsReducer(state = defaultCryps, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_CRYPS:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultInstances = null;
|
||||
function instancesReducer(state = defaultInstances, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_INSTANCES:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultInstance = null;
|
||||
function instanceReducer(state = defaultInstance, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_INSTANCE:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultGame = null;
|
||||
function gameReducer(state = defaultGame, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_GAME:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultWs = null;
|
||||
function wsReducer(state = defaultWs, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_WS:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
accountReducer,
|
||||
crypsReducer,
|
||||
gameReducer,
|
||||
instancesReducer,
|
||||
instanceReducer,
|
||||
wsReducer,
|
||||
};
|
||||
321
html-client/src/socket.jsx
Executable file
321
html-client/src/socket.jsx
Executable file
@ -0,0 +1,321 @@
|
||||
const toast = require('izitoast');
|
||||
const cbor = require('borc');
|
||||
|
||||
|
||||
const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://cryps.gg/ws' : 'ws://localhost:40000';
|
||||
|
||||
function errorToast(err) {
|
||||
console.error(err);
|
||||
return toast.error({
|
||||
title: 'BEEP BOOP',
|
||||
message: err,
|
||||
position: 'topRight',
|
||||
});
|
||||
}
|
||||
|
||||
function createSocket(events) {
|
||||
let ws;
|
||||
|
||||
// handle account auth within the socket itself
|
||||
// https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
|
||||
let account = null;
|
||||
|
||||
// -------------
|
||||
// Outgoing
|
||||
// -------------
|
||||
function send(msg) {
|
||||
console.log('outgoing msg', msg);
|
||||
msg.token = account && account.token;
|
||||
ws.send(cbor.encode(msg));
|
||||
}
|
||||
|
||||
function sendAccountLogin(name, password) {
|
||||
send({ method: 'account_login', params: { name, password } });
|
||||
}
|
||||
|
||||
function sendAccountCreate(name, password) {
|
||||
send({ method: 'account_create', params: { name, password } });
|
||||
}
|
||||
|
||||
function sendAccountDemo() {
|
||||
send({ method: 'account_demo', params: {} });
|
||||
}
|
||||
|
||||
function sendAccountCryps() {
|
||||
send({ method: 'account_cryps', params: {} });
|
||||
}
|
||||
|
||||
function sendAccountPlayers() {
|
||||
send({ method: 'account_players', params: {} });
|
||||
}
|
||||
|
||||
function sendAccountZone() {
|
||||
send({ method: 'account_zone', params: {} });
|
||||
}
|
||||
|
||||
function sendCrypSpawn(name) {
|
||||
send({ method: 'cryp_spawn', params: { name } });
|
||||
}
|
||||
|
||||
function sendCrypLearn(id, skill) {
|
||||
send({ method: 'cryp_learn', params: { id, skill } });
|
||||
}
|
||||
|
||||
function sendCrypForget(id, skill) {
|
||||
send({ method: 'cryp_forget', params: { id, skill } });
|
||||
}
|
||||
|
||||
function sendGameState(id) {
|
||||
send({ method: 'game_state', params: { id } });
|
||||
}
|
||||
|
||||
function sendGameJoin(gameId, crypIds) {
|
||||
send({ method: 'game_join', params: { game_id: gameId, cryp_ids: crypIds } });
|
||||
}
|
||||
|
||||
function sendSpecForget(id, spec) {
|
||||
send({ method: 'cryp_unspec', params: { id, spec } });
|
||||
}
|
||||
|
||||
function sendPlayerCrypsSet(instanceId, crypIds) {
|
||||
send({ method: 'player_cryps_set', params: { instance_id: instanceId, cryp_ids: crypIds } });
|
||||
}
|
||||
|
||||
function sendPlayerState(instanceId) {
|
||||
send({ method: 'player_state', params: { instance_id: instanceId } });
|
||||
}
|
||||
|
||||
function sendVboxAccept(instanceId, group, index) {
|
||||
send({ method: 'player_vbox_accept', params: { instance_id: instanceId, group, index } });
|
||||
}
|
||||
|
||||
function sendVboxApply(instanceId, crypId, index) {
|
||||
send({ method: 'player_vbox_apply', params: { instance_id: instanceId, cryp_id: crypId, index } });
|
||||
}
|
||||
|
||||
function sendVboxUnequip(instanceId, crypId, target) {
|
||||
send({ method: 'player_vbox_unequip', params: { instance_id: instanceId, cryp_id: crypId, target } });
|
||||
}
|
||||
|
||||
function sendVboxDiscard(instanceId) {
|
||||
send({ method: 'player_vbox_discard', params: { instance_id: instanceId } });
|
||||
}
|
||||
|
||||
function sendVboxCombine(instanceId, indices) {
|
||||
send({ method: 'player_vbox_combine', params: { instance_id: instanceId, indices } });
|
||||
}
|
||||
|
||||
function sendVboxReclaim(instanceId, index) {
|
||||
send({ method: 'player_vbox_reclaim', params: { instance_id: instanceId, index } });
|
||||
}
|
||||
|
||||
function sendGameSkill(gameId, crypId, targetCrypId, skill) {
|
||||
send({
|
||||
method: 'game_skill',
|
||||
params: {
|
||||
game_id: gameId, cryp_id: crypId, target_cryp_id: targetCrypId, skill,
|
||||
},
|
||||
});
|
||||
events.setActiveSkill(null);
|
||||
}
|
||||
|
||||
function sendGameTarget(gameId, crypId, skillId) {
|
||||
send({ method: 'game_target', params: { game_id: gameId, cryp_id: crypId, skill_id: skillId } });
|
||||
events.setActiveSkill(null);
|
||||
}
|
||||
|
||||
function sendZoneCreate() {
|
||||
send({ method: 'zone_create', params: {} });
|
||||
}
|
||||
|
||||
function sendZoneJoin(zoneId, nodeId, crypIds) {
|
||||
send({ method: 'zone_join', params: { zone_id: zoneId, node_id: nodeId, cryp_ids: crypIds } });
|
||||
}
|
||||
|
||||
function sendZoneClose(zoneId) {
|
||||
send({ method: 'zone_close', params: { zone_id: zoneId } });
|
||||
}
|
||||
|
||||
function sendInstanceJoin(cryps) {
|
||||
send({ method: 'instance_join', params: { cryp_ids: cryps, pve: true } });
|
||||
}
|
||||
|
||||
function sendInstanceReady(instanceId) {
|
||||
send({ method: 'instance_ready', params: { instance_id: instanceId } });
|
||||
}
|
||||
|
||||
function sendInstanceScores(instanceId) {
|
||||
send({ method: 'instance_scores', params: { instance_id: instanceId } });
|
||||
}
|
||||
|
||||
|
||||
// -------------
|
||||
// Incoming
|
||||
// -------------
|
||||
function accountLogin(res) {
|
||||
const [struct, login] = res;
|
||||
|
||||
account = login;
|
||||
events.setAccount(login);
|
||||
sendAccountCryps();
|
||||
sendAccountPlayers();
|
||||
}
|
||||
|
||||
function accountPlayerList(res) {
|
||||
const [struct, playerList] = res;
|
||||
events.setInstanceList(playerList);
|
||||
}
|
||||
|
||||
function accountCryps(response) {
|
||||
const [structName, cryps] = response;
|
||||
events.setCrypList(cryps);
|
||||
}
|
||||
|
||||
function gameState(response) {
|
||||
const [structName, game] = response;
|
||||
events.setGame(game);
|
||||
}
|
||||
|
||||
function crypSpawn(response) {
|
||||
const [structName, cryp] = response;
|
||||
}
|
||||
|
||||
function zoneState(response) {
|
||||
const [structName, zone] = response;
|
||||
events.setZone(zone);
|
||||
}
|
||||
|
||||
function playerState(response) {
|
||||
const [structName, player] = response;
|
||||
events.setPlayer(player);
|
||||
}
|
||||
|
||||
function instanceScores(response) {
|
||||
const [structName, scores] = response;
|
||||
events.setScores(scores);
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Setup
|
||||
// -------------
|
||||
|
||||
// when the server sends a reply it will have one of these message types
|
||||
// this object wraps the reply types to a function
|
||||
const handlers = {
|
||||
cryp_spawn: crypSpawn,
|
||||
cryp_forget: () => true,
|
||||
cryp_learn: () => true,
|
||||
game_state: gameState,
|
||||
account_login: accountLogin,
|
||||
account_create: accountLogin,
|
||||
account_cryps: accountCryps,
|
||||
account_players: accountPlayerList,
|
||||
instance_scores: instanceScores,
|
||||
zone_create: res => console.log(res),
|
||||
zone_state: zoneState,
|
||||
zone_close: res => console.log(res),
|
||||
player_state: playerState,
|
||||
};
|
||||
|
||||
function errHandler(error) {
|
||||
switch (error) {
|
||||
case 'no active zone': return sendZoneCreate();
|
||||
case 'no cryps selected': return events.errorPrompt('select_cryps');
|
||||
case 'node requirements not met': return events.errorPrompt('complete_nodes');
|
||||
case 'cryp at max skills (4)': return events.errorPrompt('max_skills');
|
||||
|
||||
default: return errorToast(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// decodes the cbor and
|
||||
// calls the handlers defined above based on message type
|
||||
function onMessage(event) {
|
||||
// decode binary msg from server
|
||||
const blob = new Uint8Array(event.data);
|
||||
const res = cbor.decode(blob);
|
||||
const { method, params } = res;
|
||||
|
||||
console.log(res);
|
||||
|
||||
// check for error and split into response type and data
|
||||
if (res.err) return errHandler(res.err);
|
||||
if (!handlers[method]) return errorToast(`${method} handler missing`);
|
||||
return handlers[method](params);
|
||||
}
|
||||
|
||||
function connect() {
|
||||
ws = new WebSocket(SOCKET_URL);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
// Connection opened
|
||||
ws.addEventListener('open', () => {
|
||||
toast.info({
|
||||
message: 'connected',
|
||||
position: 'topRight',
|
||||
});
|
||||
|
||||
// if (!account) events.loginPrompt();
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
send({ method: 'account_login', params: { name: 'ntr', password: 'grepgrepgrep' } });
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Listen for messages
|
||||
ws.addEventListener('message', onMessage);
|
||||
|
||||
ws.addEventListener('error', (event) => {
|
||||
console.error('WebSocket error', event);
|
||||
// account = null;
|
||||
// return setTimeout(connect, 5000);
|
||||
});
|
||||
|
||||
ws.addEventListener('close', (event) => {
|
||||
console.error('WebSocket closed', event);
|
||||
toast.warning({
|
||||
message: 'disconnected',
|
||||
position: 'topRight',
|
||||
});
|
||||
return setTimeout(connect, 5000);
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
return {
|
||||
sendAccountLogin,
|
||||
sendAccountCreate,
|
||||
sendAccountDemo,
|
||||
sendAccountCryps,
|
||||
sendAccountPlayers,
|
||||
sendAccountZone,
|
||||
sendGameState,
|
||||
sendGameJoin,
|
||||
sendGameSkill,
|
||||
sendGameTarget,
|
||||
sendCrypSpawn,
|
||||
sendCrypLearn,
|
||||
sendCrypForget,
|
||||
sendSpecForget,
|
||||
sendZoneCreate,
|
||||
sendZoneJoin,
|
||||
sendZoneClose,
|
||||
sendInstanceJoin,
|
||||
sendInstanceReady,
|
||||
sendInstanceScores,
|
||||
sendPlayerCrypsSet,
|
||||
sendPlayerState,
|
||||
sendVboxAccept,
|
||||
sendVboxApply,
|
||||
sendVboxReclaim,
|
||||
sendVboxCombine,
|
||||
sendVboxDiscard,
|
||||
sendVboxUnequip,
|
||||
connect,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createSocket;
|
||||
61
html-client/src/utils.jsx
Normal file
61
html-client/src/utils.jsx
Normal file
@ -0,0 +1,61 @@
|
||||
const get = require('lodash/get');
|
||||
|
||||
const stringSort = (k, desc) => {
|
||||
if (desc) {
|
||||
return (a, b) => {
|
||||
if (!get(a, k)) return 1;
|
||||
if (!get(b, k)) return -1;
|
||||
return get(b, k).localeCompare(get(a, k));
|
||||
};
|
||||
}
|
||||
return (a, b) => {
|
||||
if (!get(a, k)) return 1;
|
||||
if (!get(b, k)) return -1;
|
||||
return get(a, k).localeCompare(get(b, k));
|
||||
};
|
||||
};
|
||||
|
||||
const numSort = (k, desc) => {
|
||||
if (desc) {
|
||||
return (a, b) => {
|
||||
if (!get(a, k)) return 1;
|
||||
if (!get(b, k)) return -1;
|
||||
return get(b, k) - get(a, k);
|
||||
};
|
||||
}
|
||||
return (a, b) => {
|
||||
if (!get(a, k)) return 1;
|
||||
if (!get(b, k)) return -1;
|
||||
return get(a, k) - get(b, k);
|
||||
};
|
||||
};
|
||||
|
||||
const genAvatar = (name) => {
|
||||
let hash = 0;
|
||||
if (name.length === 0) return hash;
|
||||
// Probs don't need to hash using the whole string
|
||||
for (let i = 0; i < name.length; i += 1) {
|
||||
const chr = name.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash = hash & 10000; // We have avatars named 0-19
|
||||
}
|
||||
return `sprite${hash}`;
|
||||
};
|
||||
|
||||
function requestAvatar(name) {
|
||||
const id = genAvatar(name);
|
||||
const req = new Request(`/assets/molecules/${id}.svg`);
|
||||
return fetch(req)
|
||||
.then(res => res.text())
|
||||
.then(svg => svg);
|
||||
}
|
||||
|
||||
const NULL_UUID = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
module.exports = {
|
||||
stringSort,
|
||||
numSort,
|
||||
genAvatar,
|
||||
requestAvatar,
|
||||
NULL_UUID,
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user