mirror of
https://github.com/ENSL/ensl_gathers.git
synced 2024-11-22 04:31:03 +00:00
docker+upgrade
* Updated dependencies - React update needed transformation of React.createClass to ES2015 classes - removed some deprecations/deprecated packages - added mulitple @types Dependencies to devDependencies for IDE code completion support * added Docker related files - Dockerfile with build container - docker-compose.yml with mongodb and app
This commit is contained in:
parent
11c0168609
commit
79be2b8155
29 changed files with 4494 additions and 4230 deletions
9
.dockerignore
Normal file
9
.dockerignore
Normal file
|
@ -0,0 +1,9 @@
|
|||
node_modules
|
||||
.snyk
|
||||
*.md
|
||||
db/data
|
||||
Procfile
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.env*
|
||||
.nvmrc
|
12
.env.example
Normal file
12
.env.example
Normal file
|
@ -0,0 +1,12 @@
|
|||
NODE_PORT=8080
|
||||
RAILS_SECRET=secret
|
||||
NEW_RELIC_LICENSE_KEY=secret
|
||||
GATHER_STEAM_ACCOUNT=gatherbot
|
||||
GATHER_STEAM_PASSWORD=secret
|
||||
GATHER_DISCORD_HOOK_ID=someid
|
||||
GATHER_DISCORD_HOOK_TOKEN=secret
|
||||
NODE_ENV=production
|
||||
|
||||
MONGODB_USERNAME=ensl_gather
|
||||
MONGODB_PASSWORD=supersecret
|
||||
MONGODB_DATABASE=ensl_gather
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -8,4 +8,6 @@ public/app.js.map
|
|||
public/app.css
|
||||
public/app.css.map
|
||||
public/vendor.js
|
||||
public/vendor.js.map
|
||||
public/vendor.js.map
|
||||
db/data
|
||||
.env
|
||||
|
|
28
Dockerfile
Normal file
28
Dockerfile
Normal file
|
@ -0,0 +1,28 @@
|
|||
FROM bitnami/node:12 AS builder
|
||||
|
||||
COPY package*.json /app/
|
||||
WORKDIR /app
|
||||
RUN ["npm", "install"]
|
||||
|
||||
COPY . /app
|
||||
RUN ["npm", "run", "compile_production" ]
|
||||
RUN ["npm", "prune","--production"]
|
||||
|
||||
|
||||
FROM bitnami/node:12-prod AS production
|
||||
ENV NODE_ENV="production"
|
||||
ENV PORT=8000
|
||||
|
||||
RUN ["adduser", "web", "--disabled-password"]
|
||||
|
||||
COPY --chown=web:web --from=builder /app /app
|
||||
USER web
|
||||
WORKDIR /app
|
||||
|
||||
RUN /bin/mkdir -p /home/web/tmp/public
|
||||
RUN /bin/cp -r ./public /home/web/tmp/public
|
||||
RUN /usr/bin/touch /home/web/tmp/.updatePublic
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["node", "index.js"]
|
|
@ -1,9 +1,10 @@
|
|||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
const App = require("javascripts/components/main");
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { App } from "./components/main";
|
||||
|
||||
|
||||
module.exports = function (mount) {
|
||||
ReactDOM.render(<App />, mount);
|
||||
ReactDOM.render(<App />, mount);
|
||||
};
|
||||
|
||||
toastr.options = {
|
||||
|
|
|
@ -1,114 +1,119 @@
|
|||
const React = require("react");
|
||||
import {MenubarMixin} from "javascripts/components/menubar";
|
||||
import React from "react";
|
||||
import { object } from "prop-types"
|
||||
import { MenubarMixin } from "javascripts/components/menubar";
|
||||
|
||||
const UserLogin = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired
|
||||
},
|
||||
class UserLogin extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
userId: null
|
||||
};
|
||||
},
|
||||
state = {
|
||||
userId: null
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const newId = e.target.value || null;
|
||||
this.setState({ userId: newId });
|
||||
},
|
||||
handleChange = (e) => {
|
||||
const newId = e.target.value || null;
|
||||
this.setState({ userId: newId });
|
||||
}
|
||||
|
||||
authorizeId(id) {
|
||||
this.props.socket.emit("users:authorize", {
|
||||
id: id
|
||||
});
|
||||
},
|
||||
authorizeId = (id) => {
|
||||
this.props.socket.emit("users:authorize", {
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.authorizeId(this.state.userId);
|
||||
},
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.authorizeId(this.state.userId);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form>
|
||||
<div className="input-group signin">
|
||||
<input
|
||||
id="btn-input"
|
||||
type="text"
|
||||
className="form-control"
|
||||
vaue={this.state.userId}
|
||||
onChange={this.handleChange}
|
||||
placeholder="Change user (input ID)" />
|
||||
<span className="input-group-btn">
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
onClick={this.handleSubmit}
|
||||
value="Assume ID" />
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
return (
|
||||
<form>
|
||||
<div className="input-group signin">
|
||||
<input
|
||||
id="btn-input"
|
||||
type="text"
|
||||
className="form-control"
|
||||
vaue={this.state.userId}
|
||||
onChange={this.handleChange}
|
||||
placeholder="Change user (input ID)" />
|
||||
<span className="input-group-btn">
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
onClick={this.handleSubmit}
|
||||
value="Assume ID" />
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ResetGatherButton = exports.ResetGatherButton = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
gather: React.PropTypes.object.isRequired
|
||||
},
|
||||
class ResetGatherButton extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired,
|
||||
gather: object.isRequired
|
||||
}
|
||||
|
||||
handleGatherReset() {
|
||||
this.props.socket.emit("gather:reset", {
|
||||
type: this.props.gather.type
|
||||
});
|
||||
},
|
||||
handleGatherReset = () => {
|
||||
this.props.socket.emit("gather:reset", {
|
||||
type: this.props.gather.type
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-danger max-width"
|
||||
onClick={this.handleGatherReset}>
|
||||
Reset {this.props.gather.name}</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-danger max-width"
|
||||
onClick={this.handleGatherReset}>
|
||||
Reset {this.props.gather.name}</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const AdminPanel = exports.AdminPanel = React.createClass({
|
||||
mixins: [MenubarMixin],
|
||||
class AdminPanel extends MenubarMixin(React.Component) {
|
||||
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
gatherPool: React.PropTypes.object.isRequired
|
||||
},
|
||||
static propTypes = {
|
||||
socket: object.isRequired,
|
||||
gatherPool: object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const gatherPool = this.props.gatherPool;
|
||||
const resetButtons = [];
|
||||
for (let attr in gatherPool) {
|
||||
let gather = gatherPool[attr];
|
||||
resetButtons.push(
|
||||
<ResetGatherButton socket={this.props.socket}
|
||||
gather={gather} key={gather.type} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li className={this.componentClass()}>
|
||||
<a href="#" onClick={this.toggleShow}>
|
||||
<i className="fa fa-rebel"></i>
|
||||
</a>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="header">Admin</li>
|
||||
<ul className="news-menu">
|
||||
<h5>Swap Into a Different Account (Only works for admins)</h5>
|
||||
<UserLogin socket={this.props.socket} />
|
||||
<h5>Gather Options</h5>
|
||||
<div>
|
||||
{resetButtons}
|
||||
</div>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = super.getInitialState();
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const gatherPool = this.props.gatherPool;
|
||||
const resetButtons = [];
|
||||
for (let attr in gatherPool) {
|
||||
let gather = gatherPool[attr];
|
||||
resetButtons.push(
|
||||
<ResetGatherButton socket={this.props.socket}
|
||||
gather={gather} key={gather.type} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li className={this.componentClass()}>
|
||||
<a href="#" onClick={this.toggleShow}>
|
||||
<i className="fa fa-rebel"></i>
|
||||
</a>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="header">Admin</li>
|
||||
<ul className="news-menu">
|
||||
<h5>Swap Into a Different Account (Only works for admins)</h5>
|
||||
<UserLogin socket={this.props.socket} />
|
||||
<h5>Gather Options</h5>
|
||||
<div>
|
||||
{resetButtons}
|
||||
</div>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { AdminPanel, ResetGatherButton }
|
||||
|
|
|
@ -1,71 +1,70 @@
|
|||
const React = require("react");
|
||||
import React from "react";
|
||||
|
||||
const discordDefaults = {
|
||||
url: "https://discord.gg/Bvs3KjX",
|
||||
alien: {
|
||||
channel: "https://discord.gg/UcN724q",
|
||||
},
|
||||
marine: {
|
||||
channel: "https://discord.gg/eGwfHXz",
|
||||
}
|
||||
url: "https://discord.gg/Bvs3KjX",
|
||||
alien: {
|
||||
channel: "https://discord.gg/UcN724q",
|
||||
},
|
||||
marine: {
|
||||
channel: "https://discord.gg/eGwfHXz",
|
||||
}
|
||||
};
|
||||
|
||||
const DiscordButton = exports.DiscordButton = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
open: false
|
||||
};
|
||||
},
|
||||
|
||||
toggleOpen(e) {
|
||||
e.preventDefault();
|
||||
this.setState({ open: !this.state.open });
|
||||
},
|
||||
getDefaultProps() {
|
||||
return discordDefaults
|
||||
},
|
||||
export class DiscordButton extends React.Component {
|
||||
|
||||
marineUrl() {
|
||||
return discordDefaults.marine.channel;
|
||||
},
|
||||
static defaultProps = discordDefaults
|
||||
|
||||
alienUrl() {
|
||||
return discordDefaults.alien.channel;
|
||||
},
|
||||
state = {
|
||||
open: false
|
||||
}
|
||||
|
||||
chevron() {
|
||||
if (this.state.open) {
|
||||
return <i className="fa fa-angle-down pull-right"></i>;
|
||||
} else {
|
||||
return <i className="fa fa-angle-right pull-right"></i>;
|
||||
}
|
||||
},
|
||||
toggleOpen = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ open: !this.state.open });
|
||||
}
|
||||
|
||||
render() {
|
||||
const open = this.state.open;
|
||||
let componentClass = ["treeview"];
|
||||
let dropdown;
|
||||
if (open) {
|
||||
componentClass.push("active");
|
||||
dropdown = (
|
||||
<ul className="treeview-menu menu-open" style={{display: "block"}}>
|
||||
<li><a href={this.props.url}>Join Discord channel</a></li>
|
||||
<li><a href={this.marineUrl()}>Join Marine channel</a></li>
|
||||
<li><a href={this.alienUrl()}>Join Alien channel</a></li>
|
||||
<li><p className="let-me-copy">Server: {discordDefaults.url}</p></li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={componentClass.join(" ")}>
|
||||
<a href="#" onClick={this.toggleOpen}>
|
||||
<i className="fa fa-microphone"></i><span>Discord</span>
|
||||
{this.chevron()}
|
||||
</a>
|
||||
{dropdown}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
marineUrl = () => discordDefaults.marine.channel;
|
||||
|
||||
|
||||
alienUrl() {
|
||||
return discordDefaults.alien.channel;
|
||||
}
|
||||
|
||||
chevron = () => {
|
||||
if (this.state.open) {
|
||||
return <i className="fa fa-angle-down pull-right"></i>;
|
||||
} else {
|
||||
return <i className="fa fa-angle-right pull-right"></i>;
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const open = this.state.open;
|
||||
let componentClass = ["treeview"];
|
||||
let dropdown;
|
||||
if (open) {
|
||||
componentClass.push("active");
|
||||
dropdown = (
|
||||
<ul className="treeview-menu menu-open" style={{ display: "block" }}>
|
||||
<li><a href={this.props.url}>Join Discord channel</a></li>
|
||||
<li><a href={this.marineUrl()}>Join Marine channel</a></li>
|
||||
<li><a href={this.alienUrl()}>Join Alien channel</a></li>
|
||||
<li><p className="let-me-copy">Server: {discordDefaults.url}</p></li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={componentClass.join(" ")}>
|
||||
<a href="#" onClick={this.toggleOpen}>
|
||||
<i className="fa fa-microphone"></i><span>Discord</span>
|
||||
{this.chevron()}
|
||||
</a>
|
||||
{dropdown}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
const React = require("react");
|
||||
const Events = exports.Events = React.createClass({
|
||||
propTypes: {
|
||||
events: React.PropTypes.array.isRequired
|
||||
},
|
||||
import React from "react";
|
||||
import { array } from "prop-types";
|
||||
class Events extends React.Component {
|
||||
static propTypes = {
|
||||
events: array.isRequired
|
||||
}
|
||||
|
||||
getTime(timeString) {
|
||||
return (new Date(timeString)).toTimeString().match(/^[\d:]*/)[0];
|
||||
},
|
||||
getTime(timeString) {
|
||||
return (new Date(timeString)).toTimeString().match(/^[\d:]*/)[0];
|
||||
}
|
||||
|
||||
render() {
|
||||
let events;
|
||||
if (this.props.events.length) {
|
||||
events = this.props.events.map(event => {
|
||||
return `${this.getTime(event.createdAt)} ${event.description}`;
|
||||
}).join("\n");
|
||||
return (
|
||||
<pre className="events-panel">
|
||||
{events}
|
||||
</pre>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<pre className="events-panel">
|
||||
Listening for new events...
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
let events;
|
||||
if (this.props.events.length) {
|
||||
events = this.props.events.map(event => {
|
||||
return `${this.getTime(event.createdAt)} ${event.description}`;
|
||||
}).join("\n");
|
||||
return (
|
||||
<pre className="events-panel">
|
||||
{events}
|
||||
</pre>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<pre className="events-panel">
|
||||
Listening for new events...
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Events }
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,32 +1,35 @@
|
|||
const React = require("react");
|
||||
import {CompletedGather} from "javascripts/components/gather";
|
||||
import React from "react";
|
||||
import { array } from "prop-types";
|
||||
import { CompletedGather } from "javascripts/components/gather";
|
||||
|
||||
const ArchivedGathers = exports.ArchivedGathers = React.createClass({
|
||||
propTypes: {
|
||||
archive: React.PropTypes.array.isRequired,
|
||||
maps: React.PropTypes.array.isRequired
|
||||
},
|
||||
class ArchivedGathers extends React.Component {
|
||||
static propTypes = {
|
||||
archive: array.isRequired,
|
||||
maps: array.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
let archive = this.props.archive
|
||||
.sort((a, b) => {
|
||||
return new Date(b.createdAt) - new Date(a.createdAt);
|
||||
})
|
||||
.map((archivedGather, index) => {
|
||||
return <CompletedGather
|
||||
key={archivedGather.gather.done.time}
|
||||
show={(index === 0) ? true : false}
|
||||
gather={archivedGather.gather}
|
||||
maps={this.props.maps} />
|
||||
});
|
||||
render = () => {
|
||||
let archive = this.props.archive
|
||||
.sort((a, b) => {
|
||||
return new Date(b.createdAt) - new Date(a.createdAt);
|
||||
})
|
||||
.map((archivedGather, index) => {
|
||||
return <CompletedGather
|
||||
key={archivedGather.gather.done.time}
|
||||
show={(index === 0) ? true : false}
|
||||
gather={archivedGather.gather}
|
||||
maps={this.props.maps} />
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="panel panel-primary">
|
||||
<div className="panel-heading">Archived Gathers</div>
|
||||
<div className="panel-body">
|
||||
{archive}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div className="panel panel-primary">
|
||||
<div className="panel-heading">Archived Gathers</div>
|
||||
<div className="panel-body">
|
||||
{archive}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { ArchivedGathers }
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
const React = require("react");
|
||||
import React from "react"
|
||||
|
||||
const InfoButton = exports.InfoButton = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
open: false
|
||||
};
|
||||
},
|
||||
class InfoButton extends React.Component {
|
||||
state = {
|
||||
open: false
|
||||
}
|
||||
|
||||
toggleOpen(e) {
|
||||
toggleOpen = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ open: !this.state.open });
|
||||
},
|
||||
}
|
||||
|
||||
chevron() {
|
||||
chevron = () => {
|
||||
if (this.state.open) {
|
||||
return <i className="fa fa-angle-down pull-right"></i>;
|
||||
} else {
|
||||
return <i className="fa fa-angle-right pull-right"></i>;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
render = () => {
|
||||
const open = this.state.open;
|
||||
let componentClass = ["treeview"];
|
||||
let dropdown;
|
||||
|
@ -62,5 +60,7 @@ const InfoButton = exports.InfoButton = React.createClass({
|
|||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { InfoButton }
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,20 @@
|
|||
const MenubarMixin = exports.MenubarMixin = {
|
||||
getInitialState() {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
var MenubarMixin = Base => class extends Base {
|
||||
|
||||
toggleShow() {
|
||||
this.setState({ show: !this.state.show });
|
||||
},
|
||||
getInitialState() {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
|
||||
componentClass() {
|
||||
let componentClass = ["dropdown", "messages-menu"];
|
||||
if (this.state.show) componentClass.push("open");
|
||||
return componentClass.join(" ");
|
||||
}
|
||||
toggleShow = () => {
|
||||
this.setState({ show: !this.state.show });
|
||||
}
|
||||
|
||||
componentClass = () => {
|
||||
let componentClass = ["dropdown", "messages-menu"];
|
||||
if (this.state.show) componentClass.push("open");
|
||||
return componentClass.join(" ");
|
||||
}
|
||||
};
|
||||
|
||||
export { MenubarMixin }
|
||||
|
|
|
@ -1,490 +1,484 @@
|
|||
const React = require("react");
|
||||
const moment = require("moment");
|
||||
const ReactDOM = require("react-dom");
|
||||
const Ps = require("perfect-scrollbar");
|
||||
const ReactEmoji = require("react-emoji");
|
||||
const ReactAutolink = require("react-autolink");
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { array, object } from "prop-types";
|
||||
import ReactEmoji from "react-emoji";
|
||||
import ReactAutolink from "react-autolink";
|
||||
|
||||
const MessageBrowser = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
browserState: "",
|
||||
messages: [],
|
||||
page: 0,
|
||||
limit: 250,
|
||||
search: ""
|
||||
}
|
||||
},
|
||||
|
||||
handleNextPage(e) {
|
||||
e.preventDefault();
|
||||
const page = this.state.page;
|
||||
this.setState({ page: page + 1 });
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
handlePreviousPage(e) {
|
||||
e.preventDefault();
|
||||
const page = this.state.page;
|
||||
if (page < 1) return;
|
||||
this.setState({ page: page - 1 });
|
||||
this.loadMessages();
|
||||
},
|
||||
class MessageBrowser extends React.Component {
|
||||
state = {
|
||||
browserState: "",
|
||||
messages: [],
|
||||
page: 0,
|
||||
limit: 250,
|
||||
search: ""
|
||||
}
|
||||
|
||||
pageHandlers() {
|
||||
let previous;
|
||||
if (this.state.page > 0) {
|
||||
previous = (
|
||||
<a className="btn btn-xs btn-primary add-right"
|
||||
onClick={this.handlePreviousPage}>Prev</a>
|
||||
);
|
||||
}
|
||||
let next;
|
||||
if (this.state.messages.length === this.state.limit) {
|
||||
next = (
|
||||
<a className="btn btn-xs btn-primary"
|
||||
onClick={this.handleNextPage}>Next</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{previous}
|
||||
<span className="add-right">
|
||||
{this.state.page}
|
||||
</span>
|
||||
{next}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
loadMessages() {
|
||||
const limit = this.state.limit;
|
||||
const page = this.state.page;
|
||||
let data = {
|
||||
limit: limit,
|
||||
page: page
|
||||
};
|
||||
handleNextPage = (e) => {
|
||||
e.preventDefault();
|
||||
const page = this.state.page;
|
||||
this.setState({ page: page + 1 });
|
||||
this.loadMessages();
|
||||
}
|
||||
|
||||
if (this.state.search.length) {
|
||||
data.query = this.state.search;
|
||||
}
|
||||
handlePreviousPage = (e) => {
|
||||
e.preventDefault();
|
||||
const page = this.state.page;
|
||||
if (page < 1) return;
|
||||
this.setState({ page: page - 1 });
|
||||
this.loadMessages();
|
||||
}
|
||||
|
||||
this.setState({ browserState: "Retrieving messages"});
|
||||
$.ajax({
|
||||
url: "/api/messages",
|
||||
data: data
|
||||
})
|
||||
.done(data => {
|
||||
this.setState({
|
||||
messages: data.messages,
|
||||
browserState: ""
|
||||
});
|
||||
})
|
||||
.fail(error => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
browserState: `Unable to retrieve messages.`
|
||||
});
|
||||
})
|
||||
},
|
||||
pageHandlers = () => {
|
||||
let previous;
|
||||
if (this.state.page > 0) {
|
||||
previous = (
|
||||
<a className="btn btn-xs btn-primary add-right"
|
||||
onClick={this.handlePreviousPage}>Prev</a>
|
||||
);
|
||||
}
|
||||
let next;
|
||||
if (this.state.messages.length === this.state.limit) {
|
||||
next = (
|
||||
<a className="btn btn-xs btn-primary"
|
||||
onClick={this.handleNextPage}>Next</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{previous}
|
||||
<span className="add-right">
|
||||
{this.state.page}
|
||||
</span>
|
||||
{next}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadMessages();
|
||||
},
|
||||
loadMessages = () => {
|
||||
const limit = this.state.limit;
|
||||
const page = this.state.page;
|
||||
let data = {
|
||||
limit: limit,
|
||||
page: page
|
||||
};
|
||||
|
||||
updateLimit(e) {
|
||||
let newLimit = parseInt(e.target.value, 10);
|
||||
if (isNaN(newLimit) || newLimit > 250) newLimit = 250;
|
||||
this.setState({ limit: newLimit });
|
||||
},
|
||||
if (this.state.search.length) {
|
||||
data.query = this.state.search;
|
||||
}
|
||||
|
||||
updateSearch(e) {
|
||||
this.setState({ search: e.target.value });
|
||||
},
|
||||
this.setState({ browserState: "Retrieving messages" });
|
||||
$.ajax({
|
||||
url: "/api/messages",
|
||||
data: data
|
||||
})
|
||||
.done(data => {
|
||||
this.setState({
|
||||
messages: data.messages,
|
||||
browserState: ""
|
||||
});
|
||||
})
|
||||
.fail(error => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
browserState: `Unable to retrieve messages.`
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let browserState;
|
||||
if (this.state.browserState.length) {
|
||||
browserState = (
|
||||
<div className="col-xs-7">
|
||||
<div className="well">{this.state.browserState}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const messages = this.state.messages.map(message => {
|
||||
return (
|
||||
<tr key={message._id}>
|
||||
<td className="col-xs-2">{(new Date(message.createdAt)).toString()}</td>
|
||||
<td className="col-xs-3">{message.author.username}</td>
|
||||
<td className="col-xs-5">{message.content}</td>
|
||||
<td className="col-xs-2">{message._id}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-5">
|
||||
<div className="form-horizontal">
|
||||
<div className="form-group">
|
||||
<label className="col-sm-3 control-label">Max Results</label>
|
||||
<div className="col-sm-9">
|
||||
<input type="number" className="form-control"
|
||||
onChange={this.updateLimit}
|
||||
value={this.state.limit}></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="col-sm-3 control-label">Search Filter</label>
|
||||
<div className="col-sm-9">
|
||||
<input type="text" className="form-control"
|
||||
onChange={this.updateSearch}
|
||||
value={this.state.search}></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-offset-3 col-sm-9">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={this.loadMessages}>Search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-offset-3 col-sm-9">
|
||||
<p>Page Control</p>
|
||||
{this.pageHandlers()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{browserState}
|
||||
<div className="col-xs-12">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Author</th>
|
||||
<th>Message</th>
|
||||
<th>ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{messages}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
componentDidMount = () => {
|
||||
this.loadMessages();
|
||||
}
|
||||
|
||||
const Chatroom = exports.Chatroom = React.createClass({
|
||||
propTypes: {
|
||||
messages: React.PropTypes.array.isRequired,
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
user: React.PropTypes.object.isRequired
|
||||
},
|
||||
updateLimit = (e) => {
|
||||
let newLimit = parseInt(e.target.value, 10);
|
||||
if (isNaN(newLimit) || newLimit > 250) newLimit = 250;
|
||||
this.setState({ limit: newLimit });
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
autoScroll: true
|
||||
};
|
||||
},
|
||||
updateSearch = (e) => {
|
||||
this.setState({ search: e.target.value });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let self = this;
|
||||
render = () => {
|
||||
let browserState;
|
||||
if (this.state.browserState.length) {
|
||||
browserState = (
|
||||
<div className="col-xs-7">
|
||||
<div className="well">{this.state.browserState}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const messages = this.state.messages.map(message => {
|
||||
return (
|
||||
<tr key={message._id}>
|
||||
<td className="col-xs-2">{(new Date(message.createdAt)).toString()}</td>
|
||||
<td className="col-xs-3">{message.author.username}</td>
|
||||
<td className="col-xs-5">{message.content}</td>
|
||||
<td className="col-xs-2">{message._id}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-5">
|
||||
<div className="form-horizontal">
|
||||
<div className="form-group">
|
||||
<label className="col-sm-3 control-label">Max Results</label>
|
||||
<div className="col-sm-9">
|
||||
<input type="number" className="form-control"
|
||||
onChange={this.updateLimit}
|
||||
value={this.state.limit}></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="col-sm-3 control-label">Search Filter</label>
|
||||
<div className="col-sm-9">
|
||||
<input type="text" className="form-control"
|
||||
onChange={this.updateSearch}
|
||||
value={this.state.search}></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-offset-3 col-sm-9">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={this.loadMessages}>Search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-offset-3 col-sm-9">
|
||||
<p>Page Control</p>
|
||||
{this.pageHandlers()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{browserState}
|
||||
<div className="col-xs-12">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Author</th>
|
||||
<th>Message</th>
|
||||
<th>ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{messages}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollListener = _.debounce((event) => {
|
||||
self.temporarilyDisableAutoScroll(event);
|
||||
}, 300, {
|
||||
leading: false,
|
||||
trailing: true
|
||||
});
|
||||
class Chatroom extends React.Component {
|
||||
static propTypes = {
|
||||
messages: array.isRequired,
|
||||
socket: object.isRequired,
|
||||
user: object.isRequired
|
||||
}
|
||||
state = {
|
||||
autoScroll: true
|
||||
}
|
||||
|
||||
let node = ReactDOM.findDOMNode(this.refs.messageContainer);
|
||||
node.addEventListener('scroll', this.scrollListener);
|
||||
componentDidMount = () => {
|
||||
let self = this;
|
||||
|
||||
$(window).on("load", this.scrollToBottom);
|
||||
},
|
||||
this.scrollListener = _.debounce((event) => {
|
||||
self.temporarilyDisableAutoScroll(event);
|
||||
}, 300, {
|
||||
leading: false,
|
||||
trailing: true
|
||||
});
|
||||
|
||||
componentWillUnmount() {
|
||||
node.removeEventListener('scroll', this.scrollListener);
|
||||
clearTimeout(this.disableScrollTimer);
|
||||
},
|
||||
let node = ReactDOM.findDOMNode(this.refs.messageContainer);
|
||||
node.addEventListener('scroll', this.scrollListener);
|
||||
|
||||
loadMoreMessages() {
|
||||
const earliestMessage = this.props.messages[0];
|
||||
if (earliestMessage === undefined) return;
|
||||
this.props.socket.emit("message:refresh", {
|
||||
before: earliestMessage.createdAt
|
||||
});
|
||||
},
|
||||
$(window).on("load", this.scrollToBottom);
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
this.props.socket.emit("newMessage", {message: message});
|
||||
},
|
||||
componentWillUnmount = () => {
|
||||
node.removeEventListener('scroll', this.scrollListener);
|
||||
clearTimeout(this.disableScrollTimer);
|
||||
}
|
||||
|
||||
clearAutoScrollTimeout() {
|
||||
if (this.disableScrollTimer) clearTimeout(this.disableScrollTimer);
|
||||
},
|
||||
loadMoreMessages = () => {
|
||||
const earliestMessage = this.props.messages[0];
|
||||
if (earliestMessage === undefined) return;
|
||||
this.props.socket.emit("message:refresh", {
|
||||
before: earliestMessage.createdAt
|
||||
});
|
||||
}
|
||||
|
||||
temporarilyDisableAutoScroll(event) {
|
||||
let self = this;
|
||||
let node = event.target;
|
||||
if (node) {
|
||||
if (node.scrollHeight - node.scrollTop === node.clientHeight) {
|
||||
this.setState({ autoScroll: true });
|
||||
this.clearAutoScrollTimeout();
|
||||
}
|
||||
if (node.scrollHeight - node.scrollTop - node.clientHeight < 50) return;
|
||||
}
|
||||
this.setState({ autoScroll: false });
|
||||
this.clearAutoScrollTimeout();
|
||||
this.disableScrollTimer = setTimeout(() => {
|
||||
self.setState({
|
||||
autoScroll: true
|
||||
})
|
||||
}, 10000);
|
||||
},
|
||||
sendMessage = (message) => {
|
||||
this.props.socket.emit("newMessage", { message: message });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.messages.length < this.props.messages.length) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
clearAutoScrollTimeout = () => {
|
||||
if (this.disableScrollTimer) clearTimeout(this.disableScrollTimer);
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
if (!this.state.autoScroll) return;
|
||||
let node = ReactDOM.findDOMNode(this.refs.messageContainer);
|
||||
node.scrollTop = node.scrollHeight;
|
||||
},
|
||||
temporarilyDisableAutoScroll = (event) => {
|
||||
let self = this;
|
||||
let node = event.target;
|
||||
if (node) {
|
||||
if (node.scrollHeight - node.scrollTop === node.clientHeight) {
|
||||
this.setState({ autoScroll: true });
|
||||
this.clearAutoScrollTimeout();
|
||||
}
|
||||
if (node.scrollHeight - node.scrollTop - node.clientHeight < 50) return;
|
||||
}
|
||||
this.setState({ autoScroll: false });
|
||||
this.clearAutoScrollTimeout();
|
||||
this.disableScrollTimer = setTimeout(() => {
|
||||
self.setState({
|
||||
autoScroll: true
|
||||
})
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
render() {
|
||||
const socket = this.props.socket;
|
||||
const messages = this.props.messages.map(message => {
|
||||
if (message) {
|
||||
return <ChatMessage message={message}
|
||||
key={message._id}
|
||||
socket={socket}
|
||||
user={this.props.user} />
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<ul className="chat" id="chatmessages" ref="messageContainer"
|
||||
style={{height: this.props.containerHeight - 170}}>
|
||||
<li className="text-center ">
|
||||
<a href="#"
|
||||
onClick={this.loadMoreMessages}
|
||||
className="btn btn-primary btn-xs">
|
||||
Load more messages
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (prevProps.messages.length < this.props.messages.length) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom = () => {
|
||||
if (!this.state.autoScroll) return;
|
||||
let node = ReactDOM.findDOMNode(this.refs.messageContainer);
|
||||
node.scrollTop = node.scrollHeight;
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const socket = this.props.socket;
|
||||
const messages = this.props.messages.map(message => {
|
||||
if (message) {
|
||||
return <ChatMessage message={message}
|
||||
key={message._id}
|
||||
socket={socket}
|
||||
user={this.props.user} />
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<ul className="chat" id="chatmessages" ref="messageContainer"
|
||||
style={{ height: this.props.containerHeight - 170 }}>
|
||||
<li className="text-center ">
|
||||
<a href="#"
|
||||
onClick={this.loadMoreMessages}
|
||||
className="btn btn-primary btn-xs">
|
||||
Load more messages
|
||||
</a>
|
||||
</li>
|
||||
{messages}
|
||||
</ul>
|
||||
<div>
|
||||
<MessageBar socket={socket}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
</li>
|
||||
{messages}
|
||||
</ul>
|
||||
<div>
|
||||
<MessageBar socket={socket} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const imgurRegex = /^(https?:\/\/i\.imgur\.com\/\S*\.(jpg|png))$/i;
|
||||
|
||||
const ChatMessage = React.createClass({
|
||||
propTypes: {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
message: React.PropTypes.object.isRequired
|
||||
},
|
||||
class ChatMessage extends React.Component {
|
||||
static propTypes = {
|
||||
user: object.isRequired,
|
||||
socket: object.isRequired,
|
||||
message: object.isRequired
|
||||
}
|
||||
|
||||
mixins: [
|
||||
ReactAutolink,
|
||||
ReactEmoji
|
||||
],
|
||||
state = {
|
||||
createdAt: ""
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
createdAt: ""
|
||||
}
|
||||
},
|
||||
autolink = ReactAutolink.autolink
|
||||
emojify = ReactEmoji.emojify
|
||||
|
||||
messageContent: function () {
|
||||
let self = this;
|
||||
let message = self.props.message.content
|
||||
if (message.match(imgurRegex)) {
|
||||
return (
|
||||
<div className="imgur-container">
|
||||
<a href={message} target="_blank">
|
||||
<img className="imgur-chat" src={message} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
messageContent = () => {
|
||||
let self = this;
|
||||
let message = self.props.message.content
|
||||
if (message.match(imgurRegex)) {
|
||||
return (
|
||||
<div className="imgur-container">
|
||||
<a href={message} target="_blank">
|
||||
<img className="imgur-chat" src={message} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p className="wordwrap">
|
||||
{
|
||||
self.autolink(message, {
|
||||
target: "_blank",
|
||||
rel: "nofollow"
|
||||
}).map((elem) => {
|
||||
if (_.isString(elem)) {
|
||||
return self.emojify(elem);
|
||||
} else {
|
||||
return elem;
|
||||
}
|
||||
})
|
||||
}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
return (
|
||||
<p className="wordwrap">
|
||||
{
|
||||
self.autolink(message, {
|
||||
target: "_blank",
|
||||
rel: "nofollow"
|
||||
}).map((elem) => {
|
||||
if (_.isString(elem)) {
|
||||
return self.emojify(elem);
|
||||
} else {
|
||||
return elem;
|
||||
}
|
||||
})
|
||||
}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
messageTime() {
|
||||
let self = this;
|
||||
let ts = new Date(self.props.message.createdAt);
|
||||
let t = ts.toLocaleTimeString(undefined,{hour:"2-digit", minute:"2-digit"});
|
||||
let d = ts.toLocaleDateString(undefined,{month: "2-digit", day:"2-digit"});
|
||||
let r = "";
|
||||
return r.concat(d," ", t);
|
||||
},
|
||||
messageTime = () => {
|
||||
let self = this;
|
||||
let ts = new Date(self.props.message.createdAt);
|
||||
let t = ts.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
|
||||
let d = ts.toLocaleDateString(undefined, { month: "2-digit", day: "2-digit" });
|
||||
let r = "";
|
||||
return r.concat(d, " ", t);
|
||||
}
|
||||
|
||||
render() {
|
||||
let deleteButton;
|
||||
let user = this.props.user;
|
||||
if (user && user.admin) {
|
||||
deleteButton = <DeleteMessageButton messageId={this.props.message._id}
|
||||
socket={this.props.socket}/>;
|
||||
}
|
||||
return (
|
||||
<li className="left clearfix chat-message list-unstyled">
|
||||
<span className="chat-img pull-left">
|
||||
<img
|
||||
src={this.props.message.author.avatar}
|
||||
alt="User Avatar"
|
||||
className="chat-avatar" />
|
||||
</span>
|
||||
<div className="chat-body clearfix">
|
||||
<div className="header">
|
||||
<strong className="primary-font">
|
||||
{this.props.message.author.username}
|
||||
</strong>
|
||||
<small className="pull-right text-muted">
|
||||
<span className="hidden-xs">
|
||||
{this.messageTime()}
|
||||
</span>
|
||||
{deleteButton}
|
||||
</small>
|
||||
</div>
|
||||
{this.messageContent()}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
let deleteButton;
|
||||
let user = this.props.user;
|
||||
if (user && user.admin) {
|
||||
deleteButton = <DeleteMessageButton messageId={this.props.message._id}
|
||||
socket={this.props.socket} />;
|
||||
}
|
||||
return (
|
||||
<li className="left clearfix chat-message list-unstyled">
|
||||
<span className="chat-img pull-left">
|
||||
<img
|
||||
src={this.props.message.author.avatar}
|
||||
alt="User Avatar"
|
||||
className="chat-avatar" />
|
||||
</span>
|
||||
<div className="chat-body clearfix">
|
||||
<div className="header">
|
||||
<strong className="primary-font">
|
||||
{this.props.message.author.username}
|
||||
</strong>
|
||||
<small className="pull-right text-muted">
|
||||
<span className="hidden-xs">
|
||||
{this.messageTime()}
|
||||
</span>
|
||||
{deleteButton}
|
||||
</small>
|
||||
</div>
|
||||
{this.messageContent()}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const DeleteMessageButton = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired
|
||||
},
|
||||
class DeleteMessageButton extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
e.preventDefault();
|
||||
this.props.socket.emit("message:delete", {
|
||||
id: this.props.messageId
|
||||
});
|
||||
},
|
||||
handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.socket.emit("message:delete", {
|
||||
id: this.props.messageId
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<a href="#" onClick={this.handleClick}>
|
||||
<i className="fa fa-trash-o"></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
})
|
||||
render = () => {
|
||||
return (
|
||||
<a href="#" onClick={this.handleClick}>
|
||||
<i className="fa fa-trash-o"></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MessageBar = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired
|
||||
},
|
||||
class MessageBar extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired
|
||||
}
|
||||
|
||||
sendMessage(content) {
|
||||
this.props.socket.emit("message:new", {
|
||||
content: content
|
||||
});
|
||||
},
|
||||
state = {
|
||||
statusMessage: null
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
statusMessage: null
|
||||
};
|
||||
},
|
||||
|
||||
checkInputLength() {
|
||||
const input = ReactDOM.findDOMNode(this.refs.content).value;
|
||||
const currentStatusMessage = this.state.statusMessage;
|
||||
if (input.length > 256) {
|
||||
return this.setState({
|
||||
statusMessage: "Maximum of 256 characters will be saved"
|
||||
})
|
||||
}
|
||||
if (currentStatusMessage !== null) {
|
||||
this.setState({
|
||||
statusMessage: null
|
||||
});
|
||||
}
|
||||
},
|
||||
sendMessage = (content) => {
|
||||
this.props.socket.emit("message:new", {
|
||||
content: content
|
||||
});
|
||||
}
|
||||
|
||||
handleInputChange() {
|
||||
// Noop, later assigned as debounced method in componentWillMount
|
||||
},
|
||||
checkInputLength = () => {
|
||||
const input = ReactDOM.findDOMNode(this.refs.content).value;
|
||||
const currentStatusMessage = this.state.statusMessage;
|
||||
if (input.length > 256) {
|
||||
return this.setState({
|
||||
statusMessage: "Maximum of 256 characters will be saved"
|
||||
})
|
||||
}
|
||||
if (currentStatusMessage !== null) {
|
||||
this.setState({
|
||||
statusMessage: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
let content = ReactDOM.findDOMNode(this.refs.content).value.trim();
|
||||
if (!content) return;
|
||||
ReactDOM.findDOMNode(this.refs.content).value = '';
|
||||
this.sendMessage(content);
|
||||
return;
|
||||
},
|
||||
handleInputChange() {
|
||||
// Noop, later assigned as debounced method in componentWillMount
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.handleInputChange = _.debounce(this.checkInputLength, {
|
||||
leading: false,
|
||||
trailing: true
|
||||
});
|
||||
},
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
let content = ReactDOM.findDOMNode(this.refs.content).value.trim();
|
||||
if (!content) return;
|
||||
ReactDOM.findDOMNode(this.refs.content).value = '';
|
||||
this.sendMessage(content);
|
||||
return;
|
||||
}
|
||||
|
||||
render() {
|
||||
let statusMessage;
|
||||
if (this.state.statusMessage !== null) {
|
||||
statusMessage = <div className="input-group">
|
||||
<small>{this.state.statusMessage}</small>
|
||||
</div>;
|
||||
}
|
||||
return (
|
||||
componentWillMount = () => {
|
||||
this.handleInputChange = _.debounce(this.checkInputLength, {
|
||||
leading: false,
|
||||
trailing: true
|
||||
});
|
||||
}
|
||||
|
||||
<form onSubmit={this.handleSubmit} autoComplete="off">
|
||||
<div className="input-group">
|
||||
<input
|
||||
id="message-input"
|
||||
type="text"
|
||||
className="form-control message-input"
|
||||
ref="content"
|
||||
onChange={this.handleInputChange}
|
||||
autoComplete="off"
|
||||
placeholder="Be polite please..." />
|
||||
<span className="input-group-btn">
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
id="btn-chat"
|
||||
value="Send" />
|
||||
</span>
|
||||
</div>
|
||||
{statusMessage}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
let statusMessage;
|
||||
if (this.state.statusMessage !== null) {
|
||||
statusMessage = <div className="input-group">
|
||||
<small>{this.state.statusMessage}</small>
|
||||
</div>;
|
||||
}
|
||||
return (
|
||||
|
||||
<form onSubmit={this.handleSubmit} autoComplete="off">
|
||||
<div className="input-group">
|
||||
<input
|
||||
id="message-input"
|
||||
type="text"
|
||||
className="form-control message-input"
|
||||
ref="content"
|
||||
onChange={this.handleInputChange}
|
||||
autoComplete="off"
|
||||
placeholder="Be polite please..." />
|
||||
<span className="input-group-btn">
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
id="btn-chat"
|
||||
value="Send" />
|
||||
</span>
|
||||
</div>
|
||||
{statusMessage}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Chatroom }
|
||||
|
|
|
@ -1,112 +1,119 @@
|
|||
const $ = require("jquery");
|
||||
const React = require("react");
|
||||
const helper = require("javascripts/helper");
|
||||
const storageAvailable = helper.storageAvailable;
|
||||
import {MenubarMixin} from "javascripts/components/menubar";
|
||||
import React from "react"
|
||||
import $ from "jquery";
|
||||
import { MenubarMixin } from "./menubar";
|
||||
import { storageAvailable } from "../helper";
|
||||
|
||||
const READ_ARTICLES_STORAGE = "akuh098h209ufnw";
|
||||
const HTML_ENTITY_REGEX = /&#\d+;/;
|
||||
|
||||
const News = exports.News = React.createClass({
|
||||
mixins: [MenubarMixin],
|
||||
|
||||
getInitialState() {
|
||||
let readArticles = {};
|
||||
if (storageAvailable('localStorage')) {
|
||||
const raw = localStorage.getItem(READ_ARTICLES_STORAGE) || {};
|
||||
let rawJson;
|
||||
try {
|
||||
rawJson = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
rawJson = {};
|
||||
}
|
||||
readArticles = rawJson;
|
||||
}
|
||||
class News extends MenubarMixin(React.Component) {
|
||||
|
||||
return {
|
||||
posts: [],
|
||||
readArticles: readArticles
|
||||
};
|
||||
},
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
updatePosts(data) {
|
||||
this.setState({
|
||||
posts: data.posts.slice(0,5).map(post => {
|
||||
return {
|
||||
id: post.id,
|
||||
url: post.url,
|
||||
title: post.title
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
this.state = this.getInitialState();
|
||||
}
|
||||
|
||||
renderTitle(title) {
|
||||
return title.replace(HTML_ENTITY_REGEX, match => {
|
||||
return String.fromCharCode(match.slice(2, match.length - 1))
|
||||
});
|
||||
},
|
||||
getInitialState = () => {
|
||||
let readArticles = {};
|
||||
if (storageAvailable('localStorage')) {
|
||||
const raw = localStorage.getItem(READ_ARTICLES_STORAGE) || {};
|
||||
let rawJson;
|
||||
try {
|
||||
rawJson = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
rawJson = {};
|
||||
}
|
||||
readArticles = rawJson;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
$.getJSON("http://ns2news.org/api-json/get_recent_posts?callback=?")
|
||||
.done(this.updatePosts);
|
||||
},
|
||||
return Object.assign(super.getInitialState(), {
|
||||
posts: [],
|
||||
readArticles: readArticles
|
||||
});
|
||||
}
|
||||
|
||||
markAsRead(post) {
|
||||
const self = this;
|
||||
return function (e) {
|
||||
let readArticles = self.state.readArticles;
|
||||
readArticles[post.id] = (new Date()).toJSON();
|
||||
self.setState({readArticles: readArticles});
|
||||
updatePosts = (data) => {
|
||||
this.setState({
|
||||
posts: data.posts.slice(0, 5).map(post => {
|
||||
return {
|
||||
id: post.id,
|
||||
url: post.url,
|
||||
title: post.title
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (storageAvailable('localStorage')) {
|
||||
localStorage.setItem(READ_ARTICLES_STORAGE, JSON.stringify(readArticles));
|
||||
}
|
||||
}
|
||||
},
|
||||
renderTitle = (title) => {
|
||||
return title.replace(HTML_ENTITY_REGEX, match => {
|
||||
return String.fromCharCode(match.slice(2, match.length - 1))
|
||||
});
|
||||
}
|
||||
|
||||
hasBeenRead(post) {
|
||||
return (this.state.readArticles[post.id] !== undefined);
|
||||
},
|
||||
componentDidMount = () => {
|
||||
$.getJSON("http://ns2news.org/api-json/get_recent_posts?callback=?")
|
||||
.done(this.updatePosts);
|
||||
}
|
||||
|
||||
render() {
|
||||
const articles = this.state.posts.map(post => {
|
||||
let postClass = "";
|
||||
if (!this.hasBeenRead(post)) postClass += "unread";
|
||||
return (
|
||||
<li key={post.id}>
|
||||
<a href={post.url} target="_blank" onClick={this.markAsRead(post)}
|
||||
className={postClass}>{this.renderTitle(post.title)}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
markAsRead = (post) => {
|
||||
const self = this;
|
||||
return function (e) {
|
||||
let readArticles = self.state.readArticles;
|
||||
readArticles[post.id] = (new Date()).toJSON();
|
||||
self.setState({ readArticles: readArticles });
|
||||
|
||||
const unreadArticles = this.state.posts.reduce((prev, post) => {
|
||||
if (this.hasBeenRead(post)) {
|
||||
return prev;
|
||||
} else {
|
||||
return prev + 1;
|
||||
}
|
||||
}, 0)
|
||||
if (storageAvailable('localStorage')) {
|
||||
localStorage.setItem(READ_ARTICLES_STORAGE, JSON.stringify(readArticles));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tag;
|
||||
if (unreadArticles > 0) {
|
||||
tag = <span className="label label-success">{unreadArticles}</span>;
|
||||
}
|
||||
hasBeenRead = (post) => {
|
||||
return (this.state.readArticles[post.id] !== undefined);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={this.componentClass()}>
|
||||
<a href="#" onClick={this.toggleShow}>
|
||||
<i className="fa fa-newspaper-o"></i>
|
||||
{tag}
|
||||
</a>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="header">NS2News.org</li>
|
||||
<ul className="news-menu">
|
||||
{articles}
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
const articles = this.state.posts.map(post => {
|
||||
let postClass = "";
|
||||
if (!this.hasBeenRead(post)) postClass += "unread";
|
||||
return (
|
||||
<li key={post.id}>
|
||||
<a href={post.url} target="_blank" onClick={this.markAsRead(post)}
|
||||
className={postClass}>{this.renderTitle(post.title)}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
const unreadArticles = this.state.posts.reduce((prev, post) => {
|
||||
if (this.hasBeenRead(post)) {
|
||||
return prev;
|
||||
} else {
|
||||
return prev + 1;
|
||||
}
|
||||
}, 0)
|
||||
|
||||
let tag;
|
||||
if (unreadArticles > 0) {
|
||||
tag = <span className="label label-success">{unreadArticles}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={this.componentClass()}>
|
||||
<a href="#" onClick={this.toggleShow}>
|
||||
<i className="fa fa-newspaper-o"></i>
|
||||
{tag}
|
||||
</a>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="header">NS2News.org</li>
|
||||
<ul className="news-menu">
|
||||
{articles}
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { News }
|
||||
|
|
|
@ -1,50 +1,53 @@
|
|||
const React = require("react");
|
||||
import React from "react";
|
||||
import { func, bool } from "prop-types";
|
||||
|
||||
const SettingsPanel = exports.SettingsPanel = React.createClass({
|
||||
propTypes: {
|
||||
toggleUpdateTitle: React.PropTypes.func.isRequired,
|
||||
updateTitle: React.PropTypes.bool.isRequired,
|
||||
toggleEventsPanel: React.PropTypes.func.isRequired,
|
||||
showEventsPanel: React.PropTypes.bool.isRequired
|
||||
},
|
||||
class SettingsPanel extends React.Component {
|
||||
propTypes = {
|
||||
toggleUpdateTitle: func.isRequired,
|
||||
updateTitle: bool.isRequired,
|
||||
toggleEventsPanel: func.isRequired,
|
||||
showEventsPanel: bool.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="modal fade" id="settingsmodal">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Settings</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="checkbox">
|
||||
<label className="checkbox-inline">
|
||||
<input type="checkbox"
|
||||
onChange={this.props.toggleUpdateTitle}
|
||||
checked={this.props.updateTitle}/> Update Gather Status in Title (Cabooble Mode) - May require refresh
|
||||
render = () => {
|
||||
return (
|
||||
<div className="modal fade" id="settingsmodal">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Settings</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="checkbox">
|
||||
<label className="checkbox-inline">
|
||||
<input type="checkbox"
|
||||
onChange={this.props.toggleUpdateTitle}
|
||||
checked={this.props.updateTitle} /> Update Gather Status in Title (Cabooble Mode) - May require refresh
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="checkbox">
|
||||
<label className="checkbox-inline">
|
||||
<input type="checkbox"
|
||||
onChange={this.props.toggleEventsPanel}
|
||||
checked={this.props.showEventsPanel}/> Show events panel
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="checkbox">
|
||||
<label className="checkbox-inline">
|
||||
<input type="checkbox"
|
||||
onChange={this.props.toggleEventsPanel}
|
||||
checked={this.props.showEventsPanel} /> Show events panel
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-default"
|
||||
data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-default"
|
||||
data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SettingsPanel }
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
const React = require("react");
|
||||
import React from "react";
|
||||
|
||||
var SnowMachineMenu = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
snowMachine: null
|
||||
}
|
||||
},
|
||||
class SnowMachineMenu extends React.Component {
|
||||
state = {
|
||||
snowMachine: null
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const snowMachine = new SnowMachine();
|
||||
snowMachine.start();
|
||||
this.setState({ snowMachine: snowMachine });
|
||||
},
|
||||
componentDidMount = () => {
|
||||
const snowMachine = new SnowMachine();
|
||||
snowMachine.start();
|
||||
this.setState({ snowMachine: snowMachine });
|
||||
}
|
||||
|
||||
toggle() {
|
||||
const snowMachine = this.state.snowMachine;
|
||||
if (snowMachine.timer) {
|
||||
snowMachine.stop();
|
||||
} else {
|
||||
snowMachine.start();
|
||||
}
|
||||
},
|
||||
toggle = () => {
|
||||
const snowMachine = this.state.snowMachine;
|
||||
if (snowMachine.timer) {
|
||||
snowMachine.stop();
|
||||
} else {
|
||||
snowMachine.start();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ul className="nav navbar-top-links navbar-right">
|
||||
<li>
|
||||
<a href="#" onClick={this.toggle}>
|
||||
Snow
|
||||
render = () => {
|
||||
return (
|
||||
<ul className="nav navbar-top-links navbar-right">
|
||||
<li>
|
||||
<a href="#" onClick={this.toggle}>
|
||||
Snow
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
});
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,292 +1,293 @@
|
|||
const $ = require("jquery");
|
||||
const React = require("react");
|
||||
const Howl = require("howler").Howl;
|
||||
const Howler = require("howler").Howler;
|
||||
const helper = require("javascripts/helper");
|
||||
import {MenubarMixin} from "javascripts/components/menubar";
|
||||
const storageAvailable = helper.storageAvailable;
|
||||
import $ from "jquery"
|
||||
import React from "react";
|
||||
import { Howl, Howler } from "howler";
|
||||
import { storageAvailable } from "../helper";
|
||||
import { MenubarMixin } from "./menubar";
|
||||
|
||||
class SoundController {
|
||||
constructor () {
|
||||
if (Howl === undefined) {
|
||||
throw new Error("Howl.js required to created sound controller");
|
||||
}
|
||||
constructor() {
|
||||
if (Howl === undefined) {
|
||||
throw new Error("Howl.js required to created sound controller");
|
||||
}
|
||||
|
||||
this.MINIMUM_PLAY_INTERVAL = 20000;
|
||||
this.MINIMUM_PLAY_INTERVAL = 20000;
|
||||
|
||||
this.playGatherMusic = _.throttle(() => {
|
||||
this.gather.music.play();
|
||||
}, this.MINIMUM_PLAY_INTERVAL);
|
||||
this.playGatherMusic = _.throttle(() => {
|
||||
this.gather.music.play();
|
||||
}, this.MINIMUM_PLAY_INTERVAL);
|
||||
|
||||
this.isMuted = Howler._muted;
|
||||
|
||||
let gatherMusic;
|
||||
if (storageAvailable("localStorage")) {
|
||||
let volume = localStorage.getItem("gatherVolume");
|
||||
if (volume !== undefined) Howler.volume(volume);
|
||||
gatherMusic = localStorage.getItem("gatherMusic");
|
||||
}
|
||||
this.isMuted = Howler._muted;
|
||||
|
||||
this.tunes = {
|
||||
"classic": {
|
||||
description: "Gathers Classic",
|
||||
url: 'https://www.ensl.org/files/audio/gather-1.mp3'
|
||||
},
|
||||
"nights": {
|
||||
description: "Nights",
|
||||
url: 'https://www.ensl.org/files/audio/nights.mp3'
|
||||
},
|
||||
"robby": {
|
||||
description: "Robby",
|
||||
url: 'https://www.ensl.org/files/audio/robby.mp3'
|
||||
},
|
||||
"america": {
|
||||
description: "Infamous",
|
||||
url: 'https://www.ensl.org/files/audio/america.mp3'
|
||||
},
|
||||
"prommah": {
|
||||
description: "Prommah",
|
||||
url: 'https://www.ensl.org/files/audio/prommah.mp3'
|
||||
},
|
||||
"turts": {
|
||||
description: "Gorges Rock your Ass",
|
||||
url: 'https://www.ensl.org/files/audio/turts.mp3'
|
||||
},
|
||||
"skyice": {
|
||||
description: "Skyice",
|
||||
url: 'https://www.ensl.org/files/audio/skyice.mp3'
|
||||
},
|
||||
"justwannahavefun": {
|
||||
description: "Gorges just want to have fun",
|
||||
url: 'https://www.ensl.org/files/audio/justwannahavefun.mp3'
|
||||
},
|
||||
"eyeofthegorgie": {
|
||||
description: "Eye of the Gorgie",
|
||||
url: 'https://www.ensl.org/files/audio/eyeofthegorgie.mp3'
|
||||
},
|
||||
"boondock": {
|
||||
description: "Boondock Marines",
|
||||
url: 'https://www.ensl.org/files/audio/boondock.mp3'
|
||||
},
|
||||
"preclassic": {
|
||||
description: "Old Gathers Classic",
|
||||
url: 'https://www.ensl.org/files/audio/gather-5.mp3'
|
||||
}
|
||||
}
|
||||
let gatherMusic;
|
||||
if (storageAvailable("localStorage")) {
|
||||
let volume = localStorage.getItem("gatherVolume");
|
||||
if (volume !== undefined) Howler.volume(volume);
|
||||
gatherMusic = localStorage.getItem("gatherMusic");
|
||||
}
|
||||
|
||||
this.setupGatherMusic(gatherMusic);
|
||||
}
|
||||
this.tunes = {
|
||||
"classic": {
|
||||
description: "Gathers Classic",
|
||||
url: 'https://www.ensl.org/files/audio/gather-1.mp3'
|
||||
},
|
||||
"nights": {
|
||||
description: "Nights",
|
||||
url: 'https://www.ensl.org/files/audio/nights.mp3'
|
||||
},
|
||||
"robby": {
|
||||
description: "Robby",
|
||||
url: 'https://www.ensl.org/files/audio/robby.mp3'
|
||||
},
|
||||
"america": {
|
||||
description: "Infamous",
|
||||
url: 'https://www.ensl.org/files/audio/america.mp3'
|
||||
},
|
||||
"prommah": {
|
||||
description: "Prommah",
|
||||
url: 'https://www.ensl.org/files/audio/prommah.mp3'
|
||||
},
|
||||
"turts": {
|
||||
description: "Gorges Rock your Ass",
|
||||
url: 'https://www.ensl.org/files/audio/turts.mp3'
|
||||
},
|
||||
"skyice": {
|
||||
description: "Skyice",
|
||||
url: 'https://www.ensl.org/files/audio/skyice.mp3'
|
||||
},
|
||||
"justwannahavefun": {
|
||||
description: "Gorges just want to have fun",
|
||||
url: 'https://www.ensl.org/files/audio/justwannahavefun.mp3'
|
||||
},
|
||||
"eyeofthegorgie": {
|
||||
description: "Eye of the Gorgie",
|
||||
url: 'https://www.ensl.org/files/audio/eyeofthegorgie.mp3'
|
||||
},
|
||||
"boondock": {
|
||||
description: "Boondock Marines",
|
||||
url: 'https://www.ensl.org/files/audio/boondock.mp3'
|
||||
},
|
||||
"preclassic": {
|
||||
description: "Old Gathers Classic",
|
||||
url: 'https://www.ensl.org/files/audio/gather-5.mp3'
|
||||
}
|
||||
}
|
||||
|
||||
mute() {
|
||||
this.isMuted = true;
|
||||
return Howler.mute();
|
||||
}
|
||||
this.setupGatherMusic(gatherMusic);
|
||||
}
|
||||
|
||||
unMute() {
|
||||
this.isMuted = false;
|
||||
return Howler.unmute();
|
||||
}
|
||||
mute = () => {
|
||||
this.isMuted = true;
|
||||
return Howler.mute();
|
||||
}
|
||||
|
||||
getVolume() {
|
||||
return Howler.volume();
|
||||
}
|
||||
unMute = () => {
|
||||
this.isMuted = false;
|
||||
return Howler.unmute();
|
||||
}
|
||||
|
||||
setVolume(val) {
|
||||
if (val === undefined ||
|
||||
typeof val !== 'number' ||
|
||||
Math.abs(val) > 1) return;
|
||||
if (storageAvailable("localStorage")) {
|
||||
localStorage.setItem("gatherVolume", val);
|
||||
}
|
||||
return Howler.volume(val);
|
||||
}
|
||||
getVolume = () => {
|
||||
return Howler.volume();
|
||||
}
|
||||
|
||||
play(music) {
|
||||
if (this.gather && this.gather.music) return this.gather.music.play();
|
||||
}
|
||||
setVolume = (val) => {
|
||||
if (val === undefined ||
|
||||
typeof val !== 'number' ||
|
||||
Math.abs(val) > 1) return;
|
||||
if (storageAvailable("localStorage")) {
|
||||
localStorage.setItem("gatherVolume", val);
|
||||
}
|
||||
return Howler.volume(val);
|
||||
}
|
||||
|
||||
stop(music) {
|
||||
if (this.gather && this.gather.music) return this.gather.music.stop();
|
||||
}
|
||||
play = (music) => {
|
||||
if (this.gather && this.gather.music) return this.gather.music.play();
|
||||
}
|
||||
|
||||
defaultGatherMusic() {
|
||||
return "classic";
|
||||
}
|
||||
stop = (music) => {
|
||||
if (this.gather && this.gather.music) return this.gather.music.stop();
|
||||
}
|
||||
|
||||
setupGatherMusic (musicName) {
|
||||
let self = this;
|
||||
let gatherMusic = self.tunes[musicName];
|
||||
defaultGatherMusic() {
|
||||
return "classic";
|
||||
}
|
||||
|
||||
if (!gatherMusic) {
|
||||
musicName = this.defaultGatherMusic();
|
||||
gatherMusic = self.tunes[musicName];
|
||||
}
|
||||
setupGatherMusic = (musicName) => {
|
||||
let self = this;
|
||||
let gatherMusic = self.tunes[musicName];
|
||||
|
||||
if (self.gather && self.gather.name === musicName) return;
|
||||
if (!gatherMusic) {
|
||||
musicName = this.defaultGatherMusic();
|
||||
gatherMusic = self.tunes[musicName];
|
||||
}
|
||||
|
||||
// Stop if already playing
|
||||
if (self.gather && self.gather.music) {
|
||||
self.gather.music.stop();
|
||||
}
|
||||
if (self.gather && self.gather.name === musicName) return;
|
||||
|
||||
let tune = self.tunes[musicName];
|
||||
self.gather = {
|
||||
name: musicName,
|
||||
description: tune.description,
|
||||
url: tune.url,
|
||||
music: new Howl({
|
||||
urls: [tune.url]
|
||||
})
|
||||
};
|
||||
}
|
||||
// Stop if already playing
|
||||
if (self.gather && self.gather.music) {
|
||||
self.gather.music.stop();
|
||||
}
|
||||
|
||||
let tune = self.tunes[musicName];
|
||||
self.gather = {
|
||||
name: musicName,
|
||||
description: tune.description,
|
||||
url: tune.url,
|
||||
music: new Howl({
|
||||
urls: [tune.url]
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var MusicSelector = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
music: this.selectedMusic()
|
||||
}
|
||||
},
|
||||
class MusicSelector extends React.Component {
|
||||
|
||||
selectedMusic() {
|
||||
if (storageAvailable("localStorage")) {
|
||||
return localStorage.getItem("gatherMusic")
|
||||
|| this.props.soundController.defaultGatherMusic();
|
||||
} else {
|
||||
return this.props.soundController.defaultGatherMusic();
|
||||
}
|
||||
},
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
music: this.selectedMusic()
|
||||
}
|
||||
}
|
||||
|
||||
setMusic(event) {
|
||||
let name = event.target.value;
|
||||
let soundController = this.props.soundController;
|
||||
let selectedTune = soundController.tunes[name];
|
||||
if (selectedTune === undefined) return;
|
||||
this.setState({ music: name });
|
||||
soundController.setupGatherMusic(name);
|
||||
if (storageAvailable("localStorage")) {
|
||||
localStorage.setItem("gatherMusic", name);
|
||||
}
|
||||
},
|
||||
selectedMusic = () => {
|
||||
if (storageAvailable("localStorage")) {
|
||||
return localStorage.getItem("gatherMusic")
|
||||
|| this.props.soundController.defaultGatherMusic();
|
||||
} else {
|
||||
return this.props.soundController.defaultGatherMusic();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let soundController = this.props.soundController;
|
||||
let tunes = [];
|
||||
for (var attr in soundController.tunes) {
|
||||
let o = soundController.tunes[attr];
|
||||
o.id = attr;
|
||||
tunes.push(o);
|
||||
}
|
||||
let options = tunes.map(tune => {
|
||||
return <option key={tune.id} value={tune.id}>{tune.description}</option>;
|
||||
});
|
||||
return (
|
||||
<div className="form-group music-select">
|
||||
<label>Music</label>
|
||||
<select
|
||||
className="form-control"
|
||||
defaultValue={this.state.music}
|
||||
onChange={this.setMusic}
|
||||
value={this.state.music}>
|
||||
{options}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
setMusic = (event) => {
|
||||
let name = event.target.value;
|
||||
let soundController = this.props.soundController;
|
||||
let selectedTune = soundController.tunes[name];
|
||||
if (selectedTune === undefined) return;
|
||||
this.setState({ music: name });
|
||||
soundController.setupGatherMusic(name);
|
||||
if (storageAvailable("localStorage")) {
|
||||
localStorage.setItem("gatherMusic", name);
|
||||
}
|
||||
}
|
||||
|
||||
var SoundPanel = React.createClass({
|
||||
mixins: [MenubarMixin],
|
||||
render = () => {
|
||||
let soundController = this.props.soundController;
|
||||
let tunes = [];
|
||||
for (var attr in soundController.tunes) {
|
||||
let o = soundController.tunes[attr];
|
||||
o.id = attr;
|
||||
tunes.push(o);
|
||||
}
|
||||
let options = tunes.map(tune => {
|
||||
return <option key={tune.id} value={tune.id}>{tune.description}</option>;
|
||||
});
|
||||
return (
|
||||
<div className="form-group music-select">
|
||||
<label>Music</label>
|
||||
<select
|
||||
className="form-control"
|
||||
defaultValue={this.state.music}
|
||||
onChange={this.setMusic}
|
||||
value={this.state.music}>
|
||||
{options}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let soundController = this.props.soundController;
|
||||
let scale = 10;
|
||||
class SoundPanel extends MenubarMixin(React.Component) {
|
||||
|
||||
$('a#sound-dropdown').on('click', function (event) {
|
||||
$(this).parent().toggleClass('open');
|
||||
});
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = this.getInitialState();
|
||||
}
|
||||
|
||||
$("#volume-slide").slider({
|
||||
min: 0,
|
||||
max: scale,
|
||||
step: 1
|
||||
}).on("slideStop", ({value}) => {
|
||||
soundController.setVolume(value / scale);
|
||||
}).slider('setValue', soundController.getVolume() * scale);
|
||||
},
|
||||
componentDidMount = () => {
|
||||
let soundController = this.props.soundController;
|
||||
let scale = 10;
|
||||
|
||||
mute() {
|
||||
this.props.soundController.mute();
|
||||
this.forceUpdate();
|
||||
},
|
||||
$('a#sound-dropdown').on('click', function (event) {
|
||||
$(this).parent().toggleClass('open');
|
||||
});
|
||||
|
||||
unMute() {
|
||||
this.props.soundController.unMute();
|
||||
this.forceUpdate();
|
||||
},
|
||||
$("#volume-slide").slider({
|
||||
min: 0,
|
||||
max: scale,
|
||||
step: 1
|
||||
}).on("slideStop", ({ value }) => {
|
||||
soundController.setVolume(value / scale);
|
||||
}).slider('setValue', soundController.getVolume() * scale);
|
||||
}
|
||||
|
||||
play() {
|
||||
this.props.soundController.stop();
|
||||
this.props.soundController.play();
|
||||
},
|
||||
mute = () => {
|
||||
this.props.soundController.mute();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.props.soundController.stop();
|
||||
},
|
||||
unMute = () => {
|
||||
this.props.soundController.unMute();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
let soundController = this.props.soundController;
|
||||
let mutedIcon, mutedButton;
|
||||
if (soundController.isMuted) {
|
||||
mutedIcon = <i className="fa fa-volume-off fa-fw"></i>;
|
||||
mutedButton = <li>
|
||||
<a href="#" onClick={this.unMute}>
|
||||
{mutedIcon} Muted
|
||||
play = () => {
|
||||
this.props.soundController.stop();
|
||||
this.props.soundController.play();
|
||||
}
|
||||
|
||||
stop = () => {
|
||||
this.props.soundController.stop();
|
||||
}
|
||||
|
||||
render = () => {
|
||||
let soundController = this.props.soundController;
|
||||
let mutedIcon, mutedButton;
|
||||
if (soundController.isMuted) {
|
||||
mutedIcon = <i className="fa fa-volume-off fa-fw"></i>;
|
||||
mutedButton = <li>
|
||||
<a href="#" onClick={this.unMute}>
|
||||
{mutedIcon} Muted
|
||||
</a>
|
||||
</li>;
|
||||
} else {
|
||||
mutedIcon = <i className="fa fa-volume-up fa-fw"></i>;
|
||||
mutedButton = <li>
|
||||
<a href="#" onClick={this.mute}>
|
||||
{mutedIcon} Unmuted
|
||||
</li>;
|
||||
} else {
|
||||
mutedIcon = <i className="fa fa-volume-up fa-fw"></i>;
|
||||
mutedButton = <li>
|
||||
<a href="#" onClick={this.mute}>
|
||||
{mutedIcon} Unmuted
|
||||
</a>
|
||||
</li>;
|
||||
}
|
||||
return (
|
||||
<li className={this.componentClass()}>
|
||||
<a href="#" onClick={this.toggleShow}>{mutedIcon}</a>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="header">Sound Settings</li>
|
||||
<ul className="sound-menu">
|
||||
{mutedButton}
|
||||
<li>
|
||||
<a href='#' onClick={this.play}>
|
||||
<i className="fa fa-play"></i> Play
|
||||
</li>;
|
||||
}
|
||||
return (
|
||||
<li className={this.componentClass()}>
|
||||
<a href="#" onClick={this.toggleShow}>{mutedIcon}</a>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="header">Sound Settings</li>
|
||||
<ul className="sound-menu">
|
||||
{mutedButton}
|
||||
<li>
|
||||
<a href='#' onClick={this.play}>
|
||||
<i className="fa fa-play"></i> Play
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='#' onClick={this.stop}>
|
||||
<i className="fa fa-stop"></i> Stop
|
||||
</li>
|
||||
<li>
|
||||
<a href='#' onClick={this.stop}>
|
||||
<i className="fa fa-stop"></i> Stop
|
||||
</a>
|
||||
</li>
|
||||
<hr />
|
||||
<li>
|
||||
<div className="volume-slide">
|
||||
<label>Volume</label>
|
||||
<div id="volume-slide"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<MusicSelector soundController={soundController} />
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
</li>
|
||||
<hr />
|
||||
<li>
|
||||
<div className="volume-slide">
|
||||
<label>Volume</label>
|
||||
<div id="volume-slide"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<MusicSelector soundController={soundController} />
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SoundController: SoundController,
|
||||
SoundPanel: SoundPanel
|
||||
};
|
||||
export { SoundController, SoundPanel }
|
||||
|
|
|
@ -1,389 +1,387 @@
|
|||
import { LifeformIcons } from "javascripts/components/gather";
|
||||
const React = require("react");
|
||||
const helper = require("javascripts/helper");
|
||||
const enslUrl = helper.enslUrl;
|
||||
const modalId = helper.modalId;
|
||||
const obsUrl = helper.observatoryUrl;
|
||||
const Ps = require('perfect-scrollbar');
|
||||
import React from "react";
|
||||
import { object, number, func, array } from "prop-types";
|
||||
import { enslUrl, observatoryUrl as obsUrl } from "../helper";
|
||||
|
||||
const DisconnectUserButton = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
id: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
id: null
|
||||
};
|
||||
},
|
||||
class DisconnectUserButton extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired,
|
||||
id: number.isRequired
|
||||
}
|
||||
state = {
|
||||
id: null
|
||||
}
|
||||
|
||||
disconnectUser() {
|
||||
this.props.socket.emit("users:disconnect", {
|
||||
id: this.props.id
|
||||
});
|
||||
},
|
||||
disconnectUser = () => {
|
||||
this.props.socket.emit("users:disconnect", {
|
||||
id: this.props.id
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <button
|
||||
className="btn btn-danger"
|
||||
onClick={this.disconnectUser}>
|
||||
Disconnect User</button>
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
return <button
|
||||
className="btn btn-danger"
|
||||
onClick={this.disconnectUser}>
|
||||
Disconnect User</button>
|
||||
}
|
||||
}
|
||||
|
||||
const UserModal = React.createClass({
|
||||
propTypes: {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
close: React.PropTypes.func.isRequired
|
||||
},
|
||||
class UserModal extends React.Component {
|
||||
static propTypes = {
|
||||
user: object.isRequired,
|
||||
socket: object.isRequired,
|
||||
currentUser: object.isRequired,
|
||||
close: func.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const currentUser = this.props.currentUser;
|
||||
const user = this.props.user;
|
||||
let hiveStats;
|
||||
if (user.hive.id) {
|
||||
hiveStats = [
|
||||
<tr key="stats"><td><strong>Hive Stats</strong></td><td></td></tr>,
|
||||
<tr key="elo">
|
||||
<td>ELO</td>
|
||||
<td>{user.hive.skill}</td>
|
||||
</tr>,
|
||||
<tr key="hours">
|
||||
<td>Play Time (Hours)</td>
|
||||
<td>{Math.round(user.hive.playTime / 3600)}</td>
|
||||
</tr>,
|
||||
<tr key="losses">
|
||||
<td>Marine Play Time (Hours)</td>
|
||||
<td>{_.round(user.hive.marine_playtime / 3600, 1)}</td>
|
||||
</tr>,
|
||||
<tr key="kills">
|
||||
<td>Alien Play Time (Hours)</td>
|
||||
<td>{_.round(user.hive.alien_playtime / 3600, 1)}</td>
|
||||
</tr>,
|
||||
<tr key="assists">
|
||||
<td>Commander Play Time (Hours)</td>
|
||||
<td>{_.round(user.hive.commander_time / 3600, 1)}</td>
|
||||
</tr>,
|
||||
<tr key="wins">
|
||||
<td>Player ID</td>
|
||||
<td>{user.hive.pid}</td>
|
||||
</tr>
|
||||
]
|
||||
}
|
||||
let adminOptions;
|
||||
if (currentUser.admin) {
|
||||
adminOptions = <DisconnectUserButton id={user.id} socket={this.props.socket} />;
|
||||
}
|
||||
render = () => {
|
||||
const currentUser = this.props.currentUser;
|
||||
const user = this.props.user;
|
||||
let hiveStats;
|
||||
if (user.hive.id) {
|
||||
hiveStats = [
|
||||
<tr key="stats"><td><strong>Hive Stats</strong></td><td></td></tr>,
|
||||
<tr key="elo">
|
||||
<td>ELO</td>
|
||||
<td>{user.hive.skill}</td>
|
||||
</tr>,
|
||||
<tr key="hours">
|
||||
<td>Play Time (Hours)</td>
|
||||
<td>{Math.round(user.hive.playTime / 3600)}</td>
|
||||
</tr>,
|
||||
<tr key="losses">
|
||||
<td>Marine Play Time (Hours)</td>
|
||||
<td>{_.round(user.hive.marine_playtime / 3600, 1)}</td>
|
||||
</tr>,
|
||||
<tr key="kills">
|
||||
<td>Alien Play Time (Hours)</td>
|
||||
<td>{_.round(user.hive.alien_playtime / 3600, 1)}</td>
|
||||
</tr>,
|
||||
<tr key="assists">
|
||||
<td>Commander Play Time (Hours)</td>
|
||||
<td>{_.round(user.hive.commander_time / 3600, 1)}</td>
|
||||
</tr>,
|
||||
<tr key="wins">
|
||||
<td>Player ID</td>
|
||||
<td>{user.hive.pid}</td>
|
||||
</tr>
|
||||
]
|
||||
}
|
||||
let adminOptions;
|
||||
if (currentUser.admin) {
|
||||
adminOptions = <DisconnectUserButton id={user.id} socket={this.props.socket} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" onClick={this.props.close}
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">
|
||||
<img src="blank.gif"
|
||||
className={"flag flag-" + ((user.country === null) ? "eu" :
|
||||
user.country.toLowerCase())}
|
||||
alt={user.country} />
|
||||
return (
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" onClick={this.props.close}
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">
|
||||
<img src="blank.gif"
|
||||
className={"flag flag-" + ((user.country === null) ? "eu" :
|
||||
user.country.toLowerCase())}
|
||||
alt={user.country} />
|
||||
{user.username}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="text-center">
|
||||
<img
|
||||
src={user.avatar}
|
||||
alt="User Avatar" />
|
||||
</div>
|
||||
<table className="table borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Lifeforms</td>
|
||||
<td><LifeformIcons gatherer={{ user: user }} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Links</td>
|
||||
<td>
|
||||
<a href={enslUrl(user)}
|
||||
className="btn btn-xs btn-primary"
|
||||
target="_blank">ENSL Profile</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="text-center">
|
||||
<img
|
||||
src={user.avatar}
|
||||
alt="User Avatar" />
|
||||
</div>
|
||||
<table className="table borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Lifeforms</td>
|
||||
<td><LifeformIcons gatherer={{ user: user }} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Links</td>
|
||||
<td>
|
||||
<a href={enslUrl(user)}
|
||||
className="btn btn-xs btn-primary"
|
||||
target="_blank">ENSL Profile</a>
|
||||
<a href={obsUrl({ user: user })}
|
||||
className="btn btn-xs btn-primary"
|
||||
target="_blank">Observatory Profile</a>
|
||||
</td>
|
||||
</tr>
|
||||
{hiveStats}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
{adminOptions}
|
||||
<button type="button"
|
||||
className="btn btn-default"
|
||||
onClick={this.props.close}
|
||||
>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
className="btn btn-xs btn-primary"
|
||||
target="_blank">Observatory Profile</a>
|
||||
</td>
|
||||
</tr>
|
||||
{hiveStats}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
{adminOptions}
|
||||
<button type="button"
|
||||
className="btn btn-default"
|
||||
onClick={this.props.close}
|
||||
>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const UserItem = React.createClass({
|
||||
propTypes: {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
mountModal: React.PropTypes.func.isRequired
|
||||
},
|
||||
class UserItem extends React.Component {
|
||||
static propTypes = {
|
||||
user: object.isRequired,
|
||||
socket: object.isRequired,
|
||||
currentUser: object.isRequired,
|
||||
mountModal: func.isRequired
|
||||
}
|
||||
|
||||
openModal(e) {
|
||||
e.preventDefault();
|
||||
this.props.mountModal({
|
||||
component: UserModal,
|
||||
props: {
|
||||
user: this.props.user,
|
||||
currentUser: this.props.currentUser,
|
||||
socket: this.props.socket
|
||||
}
|
||||
});
|
||||
},
|
||||
openModal = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.mountModal({
|
||||
component: UserModal,
|
||||
props: {
|
||||
user: this.props.user,
|
||||
currentUser: this.props.currentUser,
|
||||
socket: this.props.socket
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const user = this.props.user;
|
||||
const currentUser = this.props.currentUser;
|
||||
return (
|
||||
<li className="users-list-group-item">
|
||||
<a href="#" onClick={this.openModal}>{user.username}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
const user = this.props.user;
|
||||
const currentUser = this.props.currentUser;
|
||||
return (
|
||||
<li className="users-list-group-item">
|
||||
<a href="#" onClick={this.openModal}>{user.username}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const UserMenu = exports.UserMenu = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
users: React.PropTypes.array.isRequired,
|
||||
mountModal: React.PropTypes.func.isRequired
|
||||
},
|
||||
class UserMenu extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired,
|
||||
users: array.isRequired,
|
||||
mountModal: func.isRequired
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
render = () => {
|
||||
const users = this.props.users
|
||||
.sort((a, b) => (a.username.toLowerCase() > b.username.toLowerCase()) ? 1 : -1)
|
||||
.map(user => {
|
||||
return <UserItem user={user} key={user.id}
|
||||
currentUser={this.props.user} socket={this.props.socket}
|
||||
mountModal={this.props.mountModal} />
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<ul className="users-list-group" id="user-list">
|
||||
{users}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
class ProfileModal extends React.Component {
|
||||
static propTypes = {
|
||||
user: object.isRequired,
|
||||
socket: object.isRequired,
|
||||
close: func.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const users = this.props.users
|
||||
.sort((a, b) => (a.username.toLowerCase() > b.username.toLowerCase()) ? 1 : -1)
|
||||
.map(user => {
|
||||
return <UserItem user={user} key={user.id}
|
||||
currentUser={this.props.user} socket={this.props.socket}
|
||||
mountModal={this.props.mountModal} />
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<ul className="users-list-group" id="user-list">
|
||||
{users}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.getInitialState();
|
||||
}
|
||||
|
||||
const ProfileModal = exports.ProfileModal = React.createClass({
|
||||
propTypes: {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
close: React.PropTypes.func.isRequired
|
||||
},
|
||||
getInitialState = () => {
|
||||
const user = this.props.user;
|
||||
console.log(user.profile);
|
||||
return {
|
||||
abilities: {
|
||||
skulk: user.profile.abilities.skulk,
|
||||
lerk: user.profile.abilities.lerk,
|
||||
gorge: user.profile.abilities.gorge,
|
||||
fade: user.profile.abilities.fade,
|
||||
onos: user.profile.abilities.onos,
|
||||
commander: user.profile.abilities.commander
|
||||
},
|
||||
skill: user.profile.skill
|
||||
};
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
const user = this.props.user;
|
||||
console.log(user.profile);
|
||||
return {
|
||||
abilities: {
|
||||
skulk: user.profile.abilities.skulk,
|
||||
lerk: user.profile.abilities.lerk,
|
||||
gorge: user.profile.abilities.gorge,
|
||||
fade: user.profile.abilities.fade,
|
||||
onos: user.profile.abilities.onos,
|
||||
commander: user.profile.abilities.commander
|
||||
},
|
||||
skill: user.profile.skill
|
||||
};
|
||||
},
|
||||
handleUserUpdate = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.socket.emit("users:update:profile", {
|
||||
id: this.props.user.id,
|
||||
profile: {
|
||||
abilities: this.state.abilities,
|
||||
skill: this.state.skill
|
||||
}
|
||||
});
|
||||
this.props.close();
|
||||
}
|
||||
|
||||
handleUserUpdate(e) {
|
||||
e.preventDefault();
|
||||
this.props.socket.emit("users:update:profile", {
|
||||
id: this.props.user.id,
|
||||
profile: {
|
||||
abilities: this.state.abilities,
|
||||
skill: this.state.skill
|
||||
}
|
||||
});
|
||||
this.props.close();
|
||||
},
|
||||
handleAbilityChange = (e) => {
|
||||
let abilities = this.state.abilities;
|
||||
abilities[e.target.name] = e.target.checked;
|
||||
this.setState({ abilities: abilities });
|
||||
}
|
||||
|
||||
handleAbilityChange(e) {
|
||||
let abilities = this.state.abilities;
|
||||
abilities[e.target.name] = e.target.checked;
|
||||
this.setState({ abilities: abilities });
|
||||
},
|
||||
handleSkillChange = (e) => {
|
||||
this.setState({ skill: e.target.value });
|
||||
}
|
||||
|
||||
handleSkillChange(e) {
|
||||
this.setState({ skill: e.target.value });
|
||||
},
|
||||
render = () => {
|
||||
const user = this.props.user;
|
||||
if (!user) return false;
|
||||
|
||||
render() {
|
||||
const user = this.props.user;
|
||||
if (!user) return false;
|
||||
const abilities = this.state.abilities;
|
||||
|
||||
const abilities = this.state.abilities;
|
||||
let abilitiesForm = [];
|
||||
for (let lifeform in abilities) {
|
||||
abilitiesForm.push(
|
||||
<div key={lifeform} className="checkbox">
|
||||
<label className="checkbox-inline">
|
||||
<input type="checkbox" name={lifeform}
|
||||
checked={abilities[lifeform]} onChange={this.handleAbilityChange} />
|
||||
{_.capitalize(lifeform)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let abilitiesForm = [];
|
||||
for (let lifeform in abilities) {
|
||||
abilitiesForm.push(
|
||||
<div key={lifeform} className="checkbox">
|
||||
<label className="checkbox-inline">
|
||||
<input type="checkbox" name={lifeform}
|
||||
checked={abilities[lifeform]} onChange={this.handleAbilityChange} />
|
||||
{_.capitalize(lifeform)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let skillLevel = user.profile.skill;
|
||||
let skillLevels = _.uniq(["Low Skill", "Medium Skill", "High Skill", skillLevel])
|
||||
.filter(skill => { return typeof skill === 'string' })
|
||||
.map(skill => { return <option key={skill} value={skill}>{skill}</option> });
|
||||
|
||||
let skillLevel = user.profile.skill;
|
||||
let skillLevels = _.uniq(["Low Skill", "Medium Skill", "High Skill", skillLevel])
|
||||
.filter(skill => { return typeof skill === 'string' })
|
||||
.map(skill => { return <option key={skill} value={skill}>{skill}</option> });
|
||||
|
||||
return (
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" aria-label="Close"
|
||||
onClick={this.props.close}>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Profile</h4>
|
||||
</div>
|
||||
<div className="modal-body" id="profile-panel">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label>Player Skill</label><br />
|
||||
<select
|
||||
value={skillLevel}
|
||||
className="form-control"
|
||||
onChange={this.handleSkillChange}>
|
||||
{skillLevels}
|
||||
</select>
|
||||
<p className="add-top"><small>
|
||||
Try to give an accurate representation of your skill to raise
|
||||
the quality of your gathers
|
||||
return (
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" aria-label="Close"
|
||||
onClick={this.props.close}>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Profile</h4>
|
||||
</div>
|
||||
<div className="modal-body" id="profile-panel">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label>Player Skill</label><br />
|
||||
<select
|
||||
value={skillLevel}
|
||||
className="form-control"
|
||||
onChange={this.handleSkillChange}>
|
||||
{skillLevels}
|
||||
</select>
|
||||
<p className="add-top"><small>
|
||||
Try to give an accurate representation of your skill to raise
|
||||
the quality of your gathers
|
||||
</small></p>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="form-group">
|
||||
<label>Preferred Lifeforms</label><br />
|
||||
{abilitiesForm}
|
||||
<p><small>
|
||||
Specify which lifeforms you'd like to play in the gather
|
||||
</div>
|
||||
<hr />
|
||||
<div className="form-group">
|
||||
<label>Preferred Lifeforms</label><br />
|
||||
{abilitiesForm}
|
||||
<p><small>
|
||||
Specify which lifeforms you'd like to play in the gather
|
||||
</small></p>
|
||||
</div>
|
||||
<hr />
|
||||
<p className="small">
|
||||
You will need to rejoin the gather to see your updated profile
|
||||
</div>
|
||||
<hr />
|
||||
<p className="small">
|
||||
You will need to rejoin the gather to see your updated profile
|
||||
</p>
|
||||
<div className="form-group">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
onClick={this.handleUserUpdate}>
|
||||
Update & Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
<div className="form-group">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
onClick={this.handleUserUpdate}>
|
||||
Update & Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CurrentUser = exports.CurrentUser = React.createClass({
|
||||
render() {
|
||||
if (this.props.user) {
|
||||
let adminOptions;
|
||||
if (this.props.user.admin || this.props.user.moderator) {
|
||||
adminOptions = (
|
||||
<li>
|
||||
<a href="#" data-toggle="modal" data-target="#adminmodal">
|
||||
<i className="fa fa-magic fa-fw"></i> Administration
|
||||
class CurrentUser extends React.Component {
|
||||
render = () => {
|
||||
if (this.props.user) {
|
||||
let adminOptions;
|
||||
if (this.props.user.admin || this.props.user.moderator) {
|
||||
adminOptions = (
|
||||
<li>
|
||||
<a href="#" data-toggle="modal" data-target="#adminmodal">
|
||||
<i className="fa fa-magic fa-fw"></i> Administration
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li className="dropdown">
|
||||
<a className="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
{this.props.user.username} <img src={this.props.user.avatar}
|
||||
alt="User Avatar"
|
||||
height="20"
|
||||
width="20" /> <i className="fa fa-caret-down"></i>
|
||||
</a>
|
||||
<ul className="dropdown-menu dropdown-user">
|
||||
<li>
|
||||
<a data-toggle="modal"
|
||||
data-target="#profilemodal"
|
||||
href="#"><i className="fa fa-user fa-fw"></i> Profile</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="modal"
|
||||
data-target="#settingsmodal"
|
||||
href="#"><i className="fa fa-gear fa-fw"></i> Settings</a>
|
||||
</li>
|
||||
{adminOptions}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li className="dropdown">
|
||||
<a className="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
{this.props.user.username} <img src={this.props.user.avatar}
|
||||
alt="User Avatar"
|
||||
height="20"
|
||||
width="20" /> <i className="fa fa-caret-down"></i>
|
||||
</a>
|
||||
<ul className="dropdown-menu dropdown-user">
|
||||
<li>
|
||||
<a data-toggle="modal"
|
||||
data-target="#profilemodal"
|
||||
href="#"><i className="fa fa-user fa-fw"></i> Profile</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="modal"
|
||||
data-target="#settingsmodal"
|
||||
href="#"><i className="fa fa-gear fa-fw"></i> Settings</a>
|
||||
</li>
|
||||
{adminOptions}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var AssumeUserIdButton = exports.AssumeUserIdButton = React.createClass({
|
||||
propTypes: {
|
||||
socket: React.PropTypes.object.isRequired,
|
||||
gatherer: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
},
|
||||
class AssumeUserIdButton extends React.Component {
|
||||
static propTypes = {
|
||||
socket: object.isRequired,
|
||||
gatherer: object.isRequired,
|
||||
currentUser: object.isRequired,
|
||||
}
|
||||
|
||||
assumeId(e) {
|
||||
e.preventDefault();
|
||||
if (this.props.gatherer) {
|
||||
this.props.socket.emit("users:authorize", {
|
||||
id: this.props.gatherer.id
|
||||
});
|
||||
// Refresh Gather list
|
||||
setTimeout(() => {
|
||||
this.props.socket.emit("gather:refresh");
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
assumeId = (e) => {
|
||||
e.preventDefault();
|
||||
if (this.props.gatherer) {
|
||||
this.props.socket.emit("users:authorize", {
|
||||
id: this.props.gatherer.id
|
||||
});
|
||||
// Refresh Gather list
|
||||
setTimeout(() => {
|
||||
this.props.socket.emit("gather:refresh");
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let currentUser = this.props.currentUser;
|
||||
let gatherer = this.props.gatherer;
|
||||
if (currentUser && gatherer) {
|
||||
return <button
|
||||
className="btn btn-xs btn-danger"
|
||||
onClick={this.assumeId}>Assume User ID</button>
|
||||
}
|
||||
}
|
||||
});
|
||||
render = () => {
|
||||
let currentUser = this.props.currentUser;
|
||||
let gatherer = this.props.gatherer;
|
||||
if (currentUser && gatherer) {
|
||||
return <button
|
||||
className="btn btn-xs btn-danger"
|
||||
onClick={this.assumeId}>Assume User ID</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { UserMenu, ProfileModal, CurrentUser, AssumeUserIdButton }
|
||||
|
|
|
@ -4,81 +4,83 @@
|
|||
// 2. Increments ID vote tally for every vote
|
||||
// 3. Sorts
|
||||
|
||||
const rankVotes = exports.rankVotes = function (votes, candidates) {
|
||||
var initial = candidates.reduce(function (acc, candidate) {
|
||||
acc[candidate.id] = 0;
|
||||
return acc;
|
||||
}, {});
|
||||
const rankVotes = function (votes, candidates) {
|
||||
var initial = candidates.reduce(function (acc, candidate) {
|
||||
acc[candidate.id] = 0;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
var scores = votes.reduce(function (acc, id) {
|
||||
if (acc[id] !== undefined) {
|
||||
acc[id]++;
|
||||
}
|
||||
return acc;
|
||||
}, initial);
|
||||
var scores = votes.reduce(function (acc, id) {
|
||||
if (acc[id] !== undefined) {
|
||||
acc[id]++;
|
||||
}
|
||||
return acc;
|
||||
}, initial);
|
||||
|
||||
var rank = [];
|
||||
var rank = [];
|
||||
|
||||
for (var id in scores) {
|
||||
if (scores.hasOwnProperty(id)) {
|
||||
rank.push({
|
||||
id: parseInt(id, 10),
|
||||
count: scores[id]
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var id in scores) {
|
||||
if (scores.hasOwnProperty(id)) {
|
||||
rank.push({
|
||||
id: parseInt(id, 10),
|
||||
count: scores[id]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return rank.sort(function (a, b) {
|
||||
if (b.count === a.count) {
|
||||
return b.id - a.id;
|
||||
} else {
|
||||
return b.count - a.count;
|
||||
}
|
||||
}).map(function (tally) {
|
||||
return tally.id
|
||||
}).map(function (id) {
|
||||
return candidates.reduce(function (acc, candidate) {
|
||||
if (candidate.id === id) return candidate;
|
||||
return acc;
|
||||
});
|
||||
});
|
||||
return rank.sort(function (a, b) {
|
||||
if (b.count === a.count) {
|
||||
return b.id - a.id;
|
||||
} else {
|
||||
return b.count - a.count;
|
||||
}
|
||||
}).map(function (tally) {
|
||||
return tally.id
|
||||
}).map(function (id) {
|
||||
return candidates.reduce(function (acc, candidate) {
|
||||
if (candidate.id === id) return candidate;
|
||||
return acc;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const enslUrl = exports.enslUrl = (gatherer) => {
|
||||
return `https://www.ensl.org/users/${gatherer.id}`
|
||||
const enslUrl = (gatherer) => {
|
||||
return `https://www.ensl.org/users/${gatherer.id}`
|
||||
};
|
||||
|
||||
const hiveUrl = exports.hiveUrl = (gatherer) => {
|
||||
const hiveId = gatherer.user.hive.id;
|
||||
if (hiveId) {
|
||||
return `http://hive.naturalselection2.com/profile/${hiveId}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const hiveUrl = (gatherer) => {
|
||||
const hiveId = gatherer.user.hive.id;
|
||||
if (hiveId) {
|
||||
return `http://hive.naturalselection2.com/profile/${hiveId}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const modalId = exports.modalId = (user) => {
|
||||
return `user-modal-${user.id}`;
|
||||
const modalId = (user) => {
|
||||
return `user-modal-${user.id}`;
|
||||
};
|
||||
|
||||
const storageAvailable = exports.storageAvailable = (type) => {
|
||||
try {
|
||||
var storage = window[type],
|
||||
x = '__storage_test__';
|
||||
storage.setItem(x, x);
|
||||
storage.removeItem(x);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
const storageAvailable = (type) => {
|
||||
try {
|
||||
var storage = window[type],
|
||||
x = '__storage_test__';
|
||||
storage.setItem(x, x);
|
||||
storage.removeItem(x);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const observatoryUrl = exports.observatoryUrl = (gatherer) => {
|
||||
const steamId = gatherer.user.steam.id;
|
||||
if (steamId) {
|
||||
return `https://observatory.morrolan.ch/player?steam_id=STEAM_${steamId}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const observatoryUrl = (gatherer) => {
|
||||
const steamId = gatherer.user.steam.id;
|
||||
if (steamId) {
|
||||
return `https://observatory.morrolan.ch/player?steam_id=STEAM_${steamId}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export { enslUrl, hiveUrl, modalId, observatoryUrl, rankVotes, storageAvailable }
|
||||
|
|
11
bin/entry.sh
Normal file
11
bin/entry.sh
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd /app
|
||||
|
||||
if [ -f "/home/web/tmp/.updatePublic" ]; then
|
||||
cp -r /home/web/tmp/public /app/public
|
||||
rm -rf /home/web/tmp/public
|
||||
rm /home/web/tmp/.updatePublic
|
||||
fi
|
||||
|
||||
node index.js
|
|
@ -34,7 +34,12 @@ exports.config = {
|
|||
// Configure your plugins
|
||||
plugins: {
|
||||
babel: {
|
||||
presets: ["es2015", "react"],
|
||||
presets: [
|
||||
["@babel/preset-env", {
|
||||
"bugfixes": true,
|
||||
"shippedProposals": true
|
||||
}],
|
||||
"@babel/preset-react"],
|
||||
// Do not use ES6 compiler in vendor code
|
||||
ignore: [/vendor/]
|
||||
}
|
||||
|
@ -46,16 +51,16 @@ exports.config = {
|
|||
// 'app.js': ['app']
|
||||
// }
|
||||
// },
|
||||
|
||||
|
||||
npm: {
|
||||
enabled: true,
|
||||
styles: {
|
||||
"bootstrap-solarized": ["bootstrap-solarized-dark.css"],
|
||||
"toastr": ["build/toastr.min.css"]
|
||||
},
|
||||
whitelist: ["react", "react-dom", "jquery", "lodash",
|
||||
whitelist: ["react", "react-dom", "jquery", "lodash",
|
||||
"react-autolink", "react-dom", "react-emoji", "howler",
|
||||
"bootstrap", "perfect-scrollbar", "moment", "toastr",
|
||||
"bootstrap", "perfect-scrollbar", "moment", "toastr",
|
||||
"socket.io-client"],
|
||||
globals: {
|
||||
"_": "lodash",
|
||||
|
@ -63,7 +68,5 @@ exports.config = {
|
|||
"$": "jquery",
|
||||
"toastr": "toastr"
|
||||
}
|
||||
},
|
||||
|
||||
notifications: true
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ module.exports = app => {
|
|||
});
|
||||
// Enforce HTTPS in production
|
||||
if (env === 'production') {
|
||||
app.use((req,res,next) => {
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Strict-Transport-Security', 'max-age=2592000; includeSubdomains'); // Enforce usage of HTTPS; max-age = 30 days
|
||||
next();
|
||||
});
|
||||
|
@ -28,27 +28,27 @@ module.exports = app => {
|
|||
app.use(cookieParser());
|
||||
app.use(favicon(path.join(__dirname, '../public/favicon.ico')));
|
||||
|
||||
// Use winston on production
|
||||
var log;
|
||||
if (env !== 'development') {
|
||||
log = {
|
||||
stream: {
|
||||
write: (message, encoding) => {
|
||||
winston.info(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
log = 'dev';
|
||||
}
|
||||
// // Use winston on production
|
||||
// var log;
|
||||
// if (env !== 'development') {
|
||||
// log = {
|
||||
// stream: {
|
||||
// write: (message, encoding) => {
|
||||
// winston.info(message);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// } else {
|
||||
// log = 'dev';
|
||||
// }
|
||||
|
||||
// if (env !== 'test') app.use(morgan(log));
|
||||
|
||||
if (env !== 'test') app.use(morgan(log));
|
||||
|
||||
var hbs = exphbs({
|
||||
defaultLayout: 'main',
|
||||
defaultLayout: 'main',
|
||||
extname: '.hbs'
|
||||
});
|
||||
|
||||
app.engine('.hbs', hbs);
|
||||
app.set('view engine', '.hbs');
|
||||
app.engine('.hbs', hbs);
|
||||
app.set('view engine', '.hbs');
|
||||
};
|
||||
|
|
25
db/index.js
25
db/index.js
|
@ -6,25 +6,22 @@ var mongoose = require("mongoose");
|
|||
var config = require(path.join(__dirname, "../config/config.js"));
|
||||
|
||||
var connect = function () {
|
||||
mongoose.connect(config.mongo.uri, {
|
||||
server: {
|
||||
socketOptions: {
|
||||
keepAlive: 1,
|
||||
connectTimeoutMS: 30000
|
||||
}
|
||||
}
|
||||
});
|
||||
mongoose.connect(config.mongo.uri, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
}).then(
|
||||
() => winston.info("MongoDB: Connection established"),
|
||||
error => winston.error(error)
|
||||
);
|
||||
};
|
||||
|
||||
connect();
|
||||
|
||||
mongoose.connection.on("error", function (error) {
|
||||
winston.error(error);
|
||||
});
|
||||
mongoose.connection.on("error", (error) => winston.error(error));
|
||||
mongoose.connection.on("disconnected", () => winston.error("MongoDB: Was disconnected."));
|
||||
mongoose.connection.on("reconnectFailed", () => winston.error("MongoDB: Reconnect Failed!"));
|
||||
|
||||
mongoose.connection.on("disconnected", function () {
|
||||
winston.error("MongoDB: Was disconnected.");
|
||||
});
|
||||
mongoose.connection.on("reconnected", () => winston.info("MongoDB: Connection established"));
|
||||
|
||||
// Load models
|
||||
require(path.join(__dirname, "/models/event"));
|
||||
|
|
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
|
@ -0,0 +1,38 @@
|
|||
version: "3.8"
|
||||
services:
|
||||
production:
|
||||
container_name: ensl_gather_production
|
||||
build:
|
||||
context: ./
|
||||
target: production
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- mongodb
|
||||
command: ["/app/bin/entry.sh"]
|
||||
user: web:web
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=$NODE_PORT
|
||||
- "MONGOLAB_URI=mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@mongodb/${MONGODB_DATABASE}"
|
||||
- RAILS_SECRET
|
||||
- NEW_RELIC_LICENSE_KEY
|
||||
- GATHER_STEAM_ACCOUNT
|
||||
- GATHER_STEAM_PASSWORD
|
||||
- GATHER_DISCORD_HOOK_ID
|
||||
- GATHER_DISCORD_HOOK_TOKEN
|
||||
- RANDOM_USER
|
||||
- FIXED_USER
|
||||
ports:
|
||||
- "${NODE_PORT}:${NODE_PORT}"
|
||||
volumes:
|
||||
- "./public:/app/public"
|
||||
init: true
|
||||
mongodb:
|
||||
image: "bitnami/mongodb:latest"
|
||||
container_name: ensl_gather_mongodb
|
||||
volumes:
|
||||
- "./db/data:/bitnami/mongodb"
|
||||
environment:
|
||||
- MONGODB_USERNAME
|
||||
- MONGODB_PASSWORD
|
||||
- MONGODB_DATABASE
|
|
@ -7,118 +7,117 @@ var logger = require("winston");
|
|||
var querystring = require('querystring');
|
||||
var config = require(path.join(__dirname, "../../config/config"));
|
||||
const SECRET_TOKEN = config.secret_token;
|
||||
var childProcess = require("child_process").exec;
|
||||
const Marshal = require('marshal');
|
||||
|
||||
|
||||
const MAP_CATEGORY = 45;
|
||||
const SERVER_CATEGORY = 45;
|
||||
|
||||
function EnslClient (options) {
|
||||
if (!(this instanceof EnslClient)) {
|
||||
return new EnslClient(options);
|
||||
}
|
||||
function EnslClient(options) {
|
||||
if (!(this instanceof EnslClient)) {
|
||||
return new EnslClient(options);
|
||||
}
|
||||
|
||||
this.baseUrl = config.ensl_url;
|
||||
this.baseUrl = config.ensl_url;
|
||||
}
|
||||
|
||||
EnslClient.prototype.getUserById = function (options, callback) {
|
||||
var id = options.id;
|
||||
var url = this.baseUrl + "api/v1/users/" + id;
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, callback);
|
||||
var id = options.id;
|
||||
var url = this.baseUrl + "api/v1/users/" + id;
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, callback);
|
||||
};
|
||||
|
||||
EnslClient.prototype.getTeamById = function (options, callback) {
|
||||
const id = options.id;
|
||||
const url = `${this.baseUrl}api/v1/teams/${id}`;
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, callback);
|
||||
const id = options.id;
|
||||
const url = `${this.baseUrl}api/v1/teams/${id}`;
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, callback);
|
||||
};
|
||||
|
||||
EnslClient.prototype.getServers = function (callback) {
|
||||
const url = this.baseUrl + "api/v1/servers";
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, (error, response, data) => {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error("Non-200 status code received"));
|
||||
return callback(null, {
|
||||
servers: data.servers.filter(function (server) {
|
||||
return server.category_id === SERVER_CATEGORY;
|
||||
})
|
||||
});
|
||||
});
|
||||
const url = this.baseUrl + "api/v1/servers";
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, (error, response, data) => {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error("Non-200 status code received"));
|
||||
return callback(null, {
|
||||
servers: data.servers.filter(function (server) {
|
||||
return server.category_id === SERVER_CATEGORY;
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
EnslClient.prototype.getMaps = function (callback) {
|
||||
const url = this.baseUrl + "api/v1/maps";
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, (error, response, data) => {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error("Non-200 status code received"));
|
||||
return callback(null, {
|
||||
maps: data.maps.filter(map => {
|
||||
return map.category_id === MAP_CATEGORY;
|
||||
})
|
||||
});
|
||||
});
|
||||
const url = this.baseUrl + "api/v1/maps";
|
||||
return request({
|
||||
url: url,
|
||||
json: true
|
||||
}, (error, response, data) => {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error("Non-200 status code received"));
|
||||
return callback(null, {
|
||||
maps: data.maps.filter(map => {
|
||||
return map.category_id === MAP_CATEGORY;
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
EnslClient.prototype.getFullAvatarUri = function (url) {
|
||||
return this.baseUrl + url.replace(/^\//, "");
|
||||
return this.baseUrl + url.replace(/^\//, "");
|
||||
};
|
||||
|
||||
EnslClient.parseCookies = function (socket) {
|
||||
let cookieString = socket.request.headers.cookie;
|
||||
if (typeof cookieString !== 'string') return null;
|
||||
let cookies = socket.request.headers.cookie.split(";")
|
||||
.map(cookie => cookie.trim())
|
||||
.reduce((acc, cookie) => {
|
||||
let values = cookie.split("=");
|
||||
let attr = values[0];
|
||||
let val = values[1];
|
||||
if (attr && val) acc[attr] = val;
|
||||
return acc;
|
||||
}, {})
|
||||
return cookies;
|
||||
let cookieString = socket.request.headers.cookie;
|
||||
if (typeof cookieString !== 'string') return null;
|
||||
let cookies = socket.request.headers.cookie.split(";")
|
||||
.map(cookie => cookie.trim())
|
||||
.reduce((acc, cookie) => {
|
||||
let values = cookie.split("=");
|
||||
let attr = values[0];
|
||||
let val = values[1];
|
||||
if (attr && val) acc[attr] = val;
|
||||
return acc;
|
||||
}, {})
|
||||
return cookies;
|
||||
};
|
||||
|
||||
EnslClient.decodeSession = function (sessionCookie, callback) {
|
||||
if (typeof sessionCookie !== 'string') {
|
||||
return callback(new Error("Invalid cookie"), null);
|
||||
}
|
||||
if (typeof sessionCookie !== 'string') {
|
||||
return callback(new Error("Invalid cookie"), null);
|
||||
}
|
||||
|
||||
var session = sessionCookie.split("--");
|
||||
if (session.length !== 2) {
|
||||
return callback(new Error("Invalid cookie: No signature provided"), null);
|
||||
}
|
||||
var session = sessionCookie.split("--");
|
||||
if (session.length !== 2) {
|
||||
return callback(new Error("Invalid cookie: No signature provided"), null);
|
||||
}
|
||||
|
||||
// Separate text and signature
|
||||
var text = querystring.unescape(session[0]);
|
||||
var signature = session[1];
|
||||
// Separate text and signature
|
||||
var text = querystring.unescape(session[0]);
|
||||
var signature = session[1];
|
||||
|
||||
// Verify signature
|
||||
if (crypto.createHmac("sha1", SECRET_TOKEN).update(text).digest('hex') !== signature) {
|
||||
return callback(new Error("Invalid cookie signature"), null);
|
||||
}
|
||||
// Verify signature
|
||||
if (crypto.createHmac("sha1", SECRET_TOKEN).update(text).digest('hex') !== signature) {
|
||||
return callback(new Error("Invalid cookie signature"), null);
|
||||
}
|
||||
|
||||
var cb = callback;
|
||||
childProcess("ruby unmarshal.rb " + text, {
|
||||
cwd: path.join(__dirname, "../../scripts")
|
||||
}, function (err, stdout, stderr) {
|
||||
var userId = parseInt(stdout, 10);
|
||||
if (isNaN(userId)) {
|
||||
return callback(new Error("Invalid cookie: User ID not found"), null);
|
||||
} else {
|
||||
return callback(null, userId);
|
||||
}
|
||||
});
|
||||
let railsSession = new Marshal(text, 'base64').toJSON();
|
||||
|
||||
if (isNaN(railsSession.user)) {
|
||||
return callback(new Error("Invalid cookie: User ID not found"), null);
|
||||
} else {
|
||||
return callback(null, railsSession.user);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = EnslClient;
|
||||
|
|
2158
package-lock.json
generated
2158
package-lock.json
generated
File diff suppressed because it is too large
Load diff
30
package.json
30
package.json
|
@ -31,10 +31,6 @@
|
|||
"homepage": "https://github.com/cblanc/sws_gathers",
|
||||
"dependencies": {
|
||||
"async": "~1.4.0",
|
||||
"babel": "~6.0.0",
|
||||
"babel-brunch": "~7.0.0",
|
||||
"babel-preset-es2015": "~6.3.13",
|
||||
"babel-preset-react": "~6.3.13",
|
||||
"bootstrap": "~4.0.0",
|
||||
"bootstrap-solarized": "~1.0.2",
|
||||
"brunch": "~3.0.0",
|
||||
|
@ -51,31 +47,45 @@
|
|||
"javascript-state-machine": "~2.3.5",
|
||||
"jquery": "~3.5.0",
|
||||
"lodash": "~4.17.20",
|
||||
"marshal": "^0.5.2",
|
||||
"moment": "~2.11.2",
|
||||
"mongoose": "~5.7.5",
|
||||
"morgan": "~1.9.1",
|
||||
"newrelic": "~5.13.1",
|
||||
"perfect-scrollbar": "~0.6.10",
|
||||
"react": "~0.14.6",
|
||||
"react": "^16.13.1",
|
||||
"react-autolink": "~0.2.1",
|
||||
"react-dom": "~16.0.1",
|
||||
"react-emoji": "~0.4.1",
|
||||
"request": "~2.88.0",
|
||||
"serve-favicon": "~2.4.5",
|
||||
"snyk": "^1.316.1",
|
||||
"socket.io": "~2.1.1",
|
||||
"socket.io-client": "~2.1.1",
|
||||
"steam": "~1.4.0",
|
||||
"steam": "1.4.0",
|
||||
"steamidconvert": "~0.2.4",
|
||||
"toastr": "~2.1.4",
|
||||
"uglify-js-brunch": ">= 2.0.1",
|
||||
"winston": "~1.0.1",
|
||||
"snyk": "^1.316.1"
|
||||
"winston": "~1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.11.6",
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@types/jquery": "^3.5.2",
|
||||
"@types/mongoose": "^5.7.36",
|
||||
"@types/node": "^12.12.67",
|
||||
"@types/react": "^16.9.51",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/socket.io-client": "^1.4.34",
|
||||
"@types/winston": "^2.4.4",
|
||||
"babel-brunch": "^7.0.1",
|
||||
"chai": "~3.1.0",
|
||||
"mocha": "~2.2.5",
|
||||
"nodemon": "~1.4.0",
|
||||
"supertest": "~1.0.1"
|
||||
"supertest": "~1.0.1",
|
||||
"terser-brunch": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0"
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# Reads in raw Rails 3 session store, returns user id
|
||||
|
||||
require 'base64'
|
||||
|
||||
session_store = ARGV[0]
|
||||
|
||||
deserialised_store = Marshal.load(Base64.decode64(session_store))
|
||||
|
||||
user_id = deserialised_store['user']
|
||||
|
||||
STDOUT.write user_id
|
||||
|
||||
exit 0
|
Loading…
Reference in a new issue