mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-08 08:02:49 +00:00
UI Overhaul
This commit is contained in:
13
.gitignore
vendored
13
.gitignore
vendored
@@ -4,11 +4,14 @@ node_modules
|
||||
yarn-error*
|
||||
vendor
|
||||
bot
|
||||
sounds
|
||||
clips
|
||||
debug
|
||||
youtube
|
||||
go-discord-bot
|
||||
data.db
|
||||
.wwp-cache
|
||||
data
|
||||
tmp
|
||||
|
||||
/sounds
|
||||
/clips
|
||||
/youtube
|
||||
|
||||
/data
|
||||
/data.db
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Navbar } from './components/Navbar';
|
||||
|
||||
//styling
|
||||
import './scss/index.scss';
|
||||
|
||||
export class Wrapper extends React.Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Navbar />
|
||||
<div>{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,27 @@
|
||||
import 'babel-polyfill';
|
||||
import { Provider } from 'mobx-react';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { Wrapper } from './Wrapper';
|
||||
import { Home } from './pages/Home/Home';
|
||||
import { Soundboard } from './pages/Soundboard/Soundboard';
|
||||
import { NotFound } from './pages/NotFound/NotFound';
|
||||
import { Downloader } from './pages/Downloader/Downloader';
|
||||
import { Clips } from './pages/Clips';
|
||||
import { Oauth } from './pages/oauth/oauth';
|
||||
import { Stats } from './pages/stats/stats';
|
||||
import 'babel-polyfill';
|
||||
import { Clips, Downloader, NotFound, Oauth, Soundboard, Stats } from './pages';
|
||||
import { rootStoreInstance } from './stores';
|
||||
import { Wrapper } from './wrapper';
|
||||
|
||||
const App: any = (): any => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Wrapper>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/soundboard" component={Soundboard} />
|
||||
<Route path="/downloader" component={Downloader} />
|
||||
<Route path="/clips" component={Clips} />
|
||||
<Route path="/oauth" component={Oauth} />
|
||||
<Route path="/stats" component={Stats} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</Wrapper>
|
||||
<Provider {...rootStoreInstance}>
|
||||
<Wrapper>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Soundboard} />
|
||||
<Route path="/downloader" component={Downloader} />
|
||||
<Route path="/clips" component={Clips} />
|
||||
<Route path="/oauth" component={Oauth} />
|
||||
<Route path="/stats" component={Stats} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</Wrapper>
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import { OauthService, StorageService } from '../../services';
|
||||
import './Navbar.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
token: string | null;
|
||||
email?: string;
|
||||
oauthUrl?: string;
|
||||
}
|
||||
|
||||
export class Navbar extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
token: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadOauthUrl();
|
||||
const token = StorageService.getJWT();
|
||||
|
||||
if (token) {
|
||||
const claims: any = jwt_decode(token);
|
||||
const email = claims['email'];
|
||||
this.setState({ token, email });
|
||||
}
|
||||
}
|
||||
|
||||
async loadOauthUrl() {
|
||||
try {
|
||||
const oauthUrl = await OauthService.getOauthUrl();
|
||||
this.setState({ oauthUrl });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private logout = () => {
|
||||
StorageService.clear();
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
renderLoginButton() {
|
||||
if (!this.state.oauthUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !this.state.token ? (
|
||||
<a href={this.state.oauthUrl} className="Navbar__item">
|
||||
Login
|
||||
</a>
|
||||
) : (
|
||||
<a className="Navbar__item" onClick={this.logout}>
|
||||
Logout
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="Navbar">
|
||||
<div className="Navbar__header">Sound Bot</div>
|
||||
<NavLink exact to="/" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Home
|
||||
</NavLink>
|
||||
<NavLink to="/soundboard" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Soundboard
|
||||
</NavLink>
|
||||
<NavLink to="/downloader" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Youtube Downloader
|
||||
</NavLink>
|
||||
<NavLink to="/clips" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Clips
|
||||
</NavLink>
|
||||
<NavLink to="/stats" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Stats
|
||||
</NavLink>
|
||||
|
||||
{this.renderLoginButton()}
|
||||
|
||||
{this.state.email && <div className="Navbar__email">{this.state.email}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './Navbar';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './SoundList';
|
||||
37
client/app/components/header/header.scss
Normal file
37
client/app/components/header/header.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, $primaryBlue, darken($primaryBlue, 20%));
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 20px;
|
||||
|
||||
&__title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__nav-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-right: 10px;
|
||||
background: $primaryBlue;
|
||||
border: none;
|
||||
color: $white;
|
||||
border-right: 1px solid darken($primaryBlue, 2%);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
background: darken($primaryBlue, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
client/app/components/header/header.tsx
Normal file
26
client/app/components/header/header.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import './header.scss';
|
||||
|
||||
interface IProps {
|
||||
onButtonClick: () => void;
|
||||
}
|
||||
|
||||
export class Header extends React.Component<IProps, any> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="header__title-container">
|
||||
<button className="header__nav-button" onClick={this.props.onButtonClick}>
|
||||
<i className="fa fa-lg fa-bars" />
|
||||
</button>
|
||||
<h2 style={{ margin: 0 }}>Sound Bot</h2>
|
||||
</div>
|
||||
<a href="https://github.com/mgerb/go-discord-bot" className="fa fa-lg fa-github" target="_blank" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
client/app/components/index.ts
Normal file
4
client/app/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './header/header';
|
||||
export * from './navbar/navbar';
|
||||
export * from './sound-list/sound-list';
|
||||
export * from './uploader/uploader';
|
||||
@@ -1,31 +1,25 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.Navbar {
|
||||
.navbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
left: -$navbarWidth;
|
||||
top: 50px;
|
||||
height: calc(100% - 50px);
|
||||
width: $navbarWidth;
|
||||
background-color: $gray2;
|
||||
border-right: 1px solid darken($gray2, 2%);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 10px;
|
||||
transition: 0.2s left ease-in-out;
|
||||
|
||||
&--open {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Navbar__header {
|
||||
font-size: 25px;
|
||||
min-height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background-color: $primaryBlue;
|
||||
border-bottom: 1px solid $gray3;
|
||||
}
|
||||
|
||||
.Navbar__item {
|
||||
.navbar__item {
|
||||
min-height: 50px;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
@@ -39,18 +33,18 @@
|
||||
background-color: $gray1;
|
||||
}
|
||||
|
||||
& + .Navbar__item {
|
||||
& + & {
|
||||
border-top: 1px solid $gray3;
|
||||
}
|
||||
}
|
||||
|
||||
.Navbar__item--active {
|
||||
.navbar__item--active {
|
||||
padding-left: 4px;
|
||||
border-right: 4px solid $primaryBlue;
|
||||
color: $primaryBlue !important;
|
||||
}
|
||||
|
||||
.Navbar__email {
|
||||
.navbar__email {
|
||||
padding-top: 10px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
86
client/app/components/navbar/navbar.tsx
Normal file
86
client/app/components/navbar/navbar.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { IClaims } from '../../model';
|
||||
import { OauthService, StorageService } from '../../services';
|
||||
import './navbar.scss';
|
||||
|
||||
interface Props {
|
||||
claims?: IClaims;
|
||||
open: boolean;
|
||||
onNavClick: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
oauthUrl?: string;
|
||||
}
|
||||
|
||||
export class Navbar extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadOauthUrl();
|
||||
}
|
||||
|
||||
async loadOauthUrl() {
|
||||
const oauthUrl = await OauthService.getOauthUrl();
|
||||
if (oauthUrl) {
|
||||
this.setState({ oauthUrl });
|
||||
}
|
||||
}
|
||||
|
||||
private logout = () => {
|
||||
StorageService.clear();
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
renderLoginButton() {
|
||||
const { claims } = this.props;
|
||||
|
||||
if (!this.state.oauthUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !claims ? (
|
||||
<a href={this.state.oauthUrl} className="navbar__item">
|
||||
Login
|
||||
</a>
|
||||
) : (
|
||||
<a className="navbar__item" onClick={this.logout}>
|
||||
Logout
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderNavLink = (title: string, to: string, params?: any) => {
|
||||
return (
|
||||
<NavLink
|
||||
{...params}
|
||||
to={to}
|
||||
className="navbar__item"
|
||||
activeClassName="navbar__item--active"
|
||||
onClick={this.props.onNavClick}
|
||||
>
|
||||
{title}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { claims, open } = this.props;
|
||||
const openClass = open ? 'navbar--open' : '';
|
||||
return (
|
||||
<div className={'navbar ' + openClass}>
|
||||
{this.renderNavLink('Soundboard', '/', { exact: true })}
|
||||
{this.renderNavLink('Youtube Downloader', '/downloader')}
|
||||
{this.renderNavLink('Clips', '/clips')}
|
||||
{this.renderNavLink('Stats', '/stats')}
|
||||
{this.renderLoginButton()}
|
||||
|
||||
{claims && claims.email && <div className="navbar__email">{claims.email}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.SoundList__item {
|
||||
.sound-list__item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
|
||||
& + .SoundList__item {
|
||||
& + .sound-list__item {
|
||||
border-top: 1px solid $gray3;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import './SoundList.scss';
|
||||
import './sound-list.scss';
|
||||
|
||||
interface Props {
|
||||
soundList: SoundType[];
|
||||
@@ -64,8 +63,8 @@ export class SoundList extends React.Component<Props, State> {
|
||||
{soundList.length > 0
|
||||
? soundList.map((sound: SoundType, index: number) => {
|
||||
return (
|
||||
<div key={index} className="SoundList__item">
|
||||
<div>{(sound.prefix || '') + sound.name}</div>
|
||||
<div key={index} className="sound-list__item">
|
||||
<div className="text-wrap">{(sound.prefix || '') + sound.name}</div>
|
||||
|
||||
{this.checkExtension(sound.extension) && this.state.showAudioControls[index] ? (
|
||||
<audio
|
||||
@@ -1,40 +1,22 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.Soundboard {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.Soundboard__column {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.Soundboard__input {
|
||||
display: block;
|
||||
width: 200px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.Dropzone {
|
||||
.dropzone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid $primaryBlue;
|
||||
border-radius: 1em;
|
||||
max-width: 800px;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
color: $lightGray;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background-color: $gray2;
|
||||
transition: box-shadow 0.1s linear, background-color 0.1s linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Dropzone--active {
|
||||
.dropzone--active {
|
||||
background-color: $gray3;
|
||||
box-shadow: 0px 0px 5px 1px $primaryBlue;
|
||||
}
|
||||
86
client/app/components/uploader/uploader.tsx
Normal file
86
client/app/components/uploader/uploader.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import { axios } from '../../services';
|
||||
import './uploader.scss';
|
||||
|
||||
interface IProps {
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
percentCompleted: number;
|
||||
uploaded: boolean;
|
||||
uploadError: string;
|
||||
}
|
||||
|
||||
export class Uploader extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: ' ',
|
||||
};
|
||||
}
|
||||
|
||||
private config = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
this.setState({
|
||||
percentCompleted: Math.round((progressEvent.loaded * 100) / progressEvent.total),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
onDrop = (acceptedFiles: any) => {
|
||||
if (acceptedFiles.length > 0) {
|
||||
this.uploadFile(acceptedFiles[0]);
|
||||
}
|
||||
};
|
||||
|
||||
uploadFile(file: any) {
|
||||
let formData = new FormData();
|
||||
formData.append('name', file.name);
|
||||
formData.append('file', file);
|
||||
|
||||
axios
|
||||
.post('/api/upload', formData, this.config)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
percentCompleted: 0,
|
||||
uploaded: true,
|
||||
uploadError: ' ',
|
||||
});
|
||||
|
||||
this.props.onComplete();
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: err.response.data,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dropzone
|
||||
className="dropzone"
|
||||
activeClassName="dropzone--active"
|
||||
onDrop={this.onDrop}
|
||||
multiple={false}
|
||||
disableClick={false}
|
||||
maxSize={10000000000}
|
||||
accept={'audio/*'}
|
||||
>
|
||||
<div style={{ fontSize: '20px' }}>Click or drop file to upload</div>
|
||||
{this.state.percentCompleted > 0 ? <div>Uploading: {this.state.percentCompleted}</div> : ''}
|
||||
{this.state.uploaded ? <div style={{ color: 'green' }}>File uploded!</div> : ''}
|
||||
<div style={{ color: '#f95f59' }}>{this.state.uploadError}</div>
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
}
|
||||
12
client/app/model/claims.ts
Normal file
12
client/app/model/claims.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Permissions } from './permissions';
|
||||
|
||||
// JWT claims
|
||||
export interface IClaims {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
discriminator: string;
|
||||
permissions: Permissions;
|
||||
exp: number;
|
||||
iss: string; // issuer
|
||||
}
|
||||
2
client/app/model/index.ts
Normal file
2
client/app/model/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './claims';
|
||||
export * from './permissions';
|
||||
5
client/app/model/permissions.ts
Normal file
5
client/app/model/permissions.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum Permissions {
|
||||
'Admin' = 3,
|
||||
'Mod' = 2,
|
||||
'User' = 1,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './Clips';
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import './Home.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {}
|
||||
|
||||
export class Home extends React.Component<Props, State> {
|
||||
render() {
|
||||
return (
|
||||
<div className="content">
|
||||
<div className="card">
|
||||
<div className="card__header">Go Discord Bot</div>
|
||||
|
||||
<h3>04-09-18 Update</h3>
|
||||
<ul>
|
||||
<li>pubg stats no longer updated on this site</li>
|
||||
<li>client dependencies all updated (including webpack 4 and react router 4)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Audio Clipping</h3>
|
||||
<p>
|
||||
<em>NEW:</em> Audio clipping now supported! Try it out with the <code>clip</code> command!
|
||||
</p>
|
||||
|
||||
<h3>PUBG Stats</h3>
|
||||
<p>PUBG stats are pulled from the score API.</p>
|
||||
|
||||
<h3>Youtube Downloader</h3>
|
||||
<p>Convert Youtube URL's to MP3 files.</p>
|
||||
|
||||
<h3>Soundboard Upload</h3>
|
||||
<p>Drag and drop files to upload. Sounds can be played in discord by typing the commands on the next page.</p>
|
||||
|
||||
<p>
|
||||
Check out the source code on
|
||||
<a href="https://github.com/mgerb/GoBot" target="_blank">
|
||||
{' '}
|
||||
GitHub
|
||||
<i className="fa fa-github" aria-hidden="true" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import './NotFound.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {}
|
||||
|
||||
export class NotFound extends React.Component<Props, State> {
|
||||
render() {
|
||||
return <div className="NotFound">404 Not Found</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.pubg__container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.pubg__table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
margin-top: 20px;
|
||||
|
||||
tr + tr {
|
||||
border-top: 1px solid $gray3;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.pubg__button-row {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.button {
|
||||
min-width: 100px;
|
||||
|
||||
& + .button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { axios } from '../../services';
|
||||
import * as _ from 'lodash';
|
||||
import './Pubg.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
players: Player[];
|
||||
selectedRegion: string;
|
||||
selectedMatch: string;
|
||||
statList: string[];
|
||||
}
|
||||
|
||||
interface Player {
|
||||
PlayerName: string;
|
||||
agg?: any;
|
||||
as?: any;
|
||||
na?: any;
|
||||
sa?: any;
|
||||
}
|
||||
|
||||
export class Pubg extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
players: [],
|
||||
selectedRegion: 'agg',
|
||||
selectedMatch: 'squad',
|
||||
statList: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
axios.get('/api/stats/pubg').then(res => {
|
||||
this.setState({
|
||||
players: _.map(res.data) as any,
|
||||
});
|
||||
|
||||
this.setStatList();
|
||||
});
|
||||
}
|
||||
|
||||
// get stat list
|
||||
setStatList() {
|
||||
// hacky way to find existing content -- to tired to make it pretty
|
||||
let i = 0;
|
||||
let stats;
|
||||
while (!stats) {
|
||||
if (i > this.state.players.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
stats = _.find(
|
||||
_.get(this.state, `players[${i}].Stats`),
|
||||
(s: any) => s.Match === this.state.selectedMatch.toLowerCase(),
|
||||
);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
this.setState({
|
||||
statList: _.sortBy(_.map(stats.Stats, 'field')) as any,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
insertRows(): any {
|
||||
return this.state.statList.map((val: any, index: any) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{val}</td>
|
||||
{this.state.players.map((player: any, i: number) => {
|
||||
// find player stats for field
|
||||
let playerStat = _.find(player.Stats, (p: any) => {
|
||||
return (
|
||||
p.Match === this.state.selectedMatch.toLowerCase() &&
|
||||
p.Region === this.state.selectedRegion.toLowerCase()
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<td key={i}>{_.get(_.find(_.get(playerStat, 'Stats'), (p: any) => p.field === val), 'displayValue')}</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
buttonRegion(title: string) {
|
||||
let lowerTitle = title === 'All' ? 'agg' : title.toLowerCase();
|
||||
return (
|
||||
<button
|
||||
className={`button ${lowerTitle === this.state.selectedRegion ? 'button--primary' : ''}`}
|
||||
onClick={() => {
|
||||
this.setState({ selectedRegion: lowerTitle });
|
||||
this.setStatList();
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
buttonMatch(title: string) {
|
||||
let lowerTitle = title.toLowerCase();
|
||||
return (
|
||||
<button
|
||||
className={`button ${lowerTitle === this.state.selectedMatch ? 'button--primary' : ''}`}
|
||||
onClick={() => {
|
||||
this.setState({ selectedMatch: lowerTitle });
|
||||
this.setStatList();
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="pubg__container">
|
||||
<div className="card" style={{ maxWidth: 'initial' }}>
|
||||
<div className="card__header">PUBG Stats</div>
|
||||
|
||||
<div className="pubg__button-row">
|
||||
{this.buttonMatch('Solo')}
|
||||
{this.buttonMatch('Duo')}
|
||||
{this.buttonMatch('Squad')}
|
||||
</div>
|
||||
|
||||
<div className="pubg__button-row">
|
||||
{this.buttonRegion('All')}
|
||||
{this.buttonRegion('Na')}
|
||||
{this.buttonRegion('As')}
|
||||
{this.buttonRegion('Au')}
|
||||
</div>
|
||||
|
||||
<table className="pubg__table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th />
|
||||
{this.state.players.map((val: any, index: number) => {
|
||||
return <th key={index}>{val.PlayerName}</th>;
|
||||
})}
|
||||
</tr>
|
||||
{this.insertRows()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import { axios } from '../../services';
|
||||
import { SoundList, SoundType } from '../../components/SoundList';
|
||||
import './Soundboard.scss';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
let self: any;
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
percentCompleted: number;
|
||||
uploaded: boolean;
|
||||
uploadError: string;
|
||||
soundList: SoundType[];
|
||||
}
|
||||
|
||||
export class Soundboard extends React.Component<Props, State> {
|
||||
private config: AxiosRequestConfig;
|
||||
private soundListCache: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
(this.state = {
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: ' ',
|
||||
soundList: [],
|
||||
}),
|
||||
(self = this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.config = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: progressEvent => {
|
||||
this.setState({
|
||||
percentCompleted: Math.round(progressEvent.loaded * 100 / progressEvent.total),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
this.getSoundList();
|
||||
}
|
||||
|
||||
private getSoundList() {
|
||||
if (!this.soundListCache) {
|
||||
axios
|
||||
.get('/api/soundlist')
|
||||
.then(response => {
|
||||
this.soundListCache = response.data;
|
||||
this.setState({
|
||||
soundList: response.data,
|
||||
});
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error(error.response.data);
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
soundList: this.soundListCache,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onDrop(acceptedFiles: any) {
|
||||
if (acceptedFiles.length > 0) {
|
||||
self.uploadFile(acceptedFiles[0]);
|
||||
}
|
||||
}
|
||||
|
||||
uploadFile(file: any) {
|
||||
let formData = new FormData();
|
||||
formData.append('name', file.name);
|
||||
formData.append('file', file);
|
||||
|
||||
axios
|
||||
.post('/api/upload', formData, this.config)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
percentCompleted: 0,
|
||||
uploaded: true,
|
||||
uploadError: ' ',
|
||||
});
|
||||
|
||||
this.soundListCache = undefined;
|
||||
this.getSoundList();
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: err.response.data,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { soundList } = this.state;
|
||||
return (
|
||||
<div className="Soundboard">
|
||||
<div className="column">
|
||||
<SoundList soundList={soundList} type="Sounds" />
|
||||
</div>
|
||||
|
||||
<div className="column">
|
||||
<div>
|
||||
<Dropzone
|
||||
className="Dropzone"
|
||||
activeClassName="Dropzone--active"
|
||||
onDrop={this.onDrop}
|
||||
multiple={false}
|
||||
disableClick={true}
|
||||
maxSize={10000000000}
|
||||
accept={'audio/*'}
|
||||
>
|
||||
<div style={{ fontSize: '20px' }}>Drop file here to upload.</div>
|
||||
{this.state.percentCompleted > 0 ? <div>Uploading: {this.state.percentCompleted}</div> : ''}
|
||||
{this.state.uploaded ? <div style={{ color: 'green' }}>File uploded!</div> : ''}
|
||||
<div style={{ color: '#f95f59' }}>{this.state.uploadError}</div>
|
||||
</Dropzone>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SoundList, SoundType } from '../../components';
|
||||
import { axios } from '../../services';
|
||||
|
||||
import { SoundList, SoundType } from '../../components/SoundList';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
@@ -36,7 +35,7 @@ export class Clips extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="Soundboard">
|
||||
<div className="content">
|
||||
<div className="column">
|
||||
<SoundList soundList={this.state.clipList} type="Clips" />
|
||||
</div>
|
||||
@@ -1,14 +1,10 @@
|
||||
.Downloader {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.Downloader__input {
|
||||
.downloader__input {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.Downloader__button {
|
||||
.downloader__button {
|
||||
& + & {
|
||||
margin-left: 10px;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { axios } from '../../services';
|
||||
|
||||
import './Downloader.scss';
|
||||
import './downloader.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
@@ -70,13 +69,13 @@ export class Downloader extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="Downloader">
|
||||
<div className="content">
|
||||
<div className="card">
|
||||
<div className="card__header">Youtube to MP3</div>
|
||||
|
||||
<input
|
||||
placeholder="Enter Youtube URL"
|
||||
className="input Downloader__input"
|
||||
className="input downloader__input"
|
||||
value={this.state.url}
|
||||
onChange={event => this.setState({ url: event.target.value })}
|
||||
/>
|
||||
6
client/app/pages/index.ts
Normal file
6
client/app/pages/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './clips/clips';
|
||||
export * from './downloader/downloader';
|
||||
export * from './not-found/not-found';
|
||||
export * from './oauth/oauth';
|
||||
export * from './soundboard/soundboard';
|
||||
export * from './stats/stats';
|
||||
@@ -1,8 +1,7 @@
|
||||
.NotFound {
|
||||
.not-found {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 20px;
|
||||
}
|
||||
4
client/app/pages/not-found/not-found.tsx
Normal file
4
client/app/pages/not-found/not-found.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import React from 'react';
|
||||
import './not-found.scss';
|
||||
|
||||
export const NotFound = () => <div className="not-found">404 Not Found</div>;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { axios, StorageService } from '../../services';
|
||||
import queryString from 'query-string';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { axios, StorageService } from '../../services';
|
||||
|
||||
interface Props extends RouteComponentProps<any> {}
|
||||
|
||||
|
||||
66
client/app/pages/soundboard/soundboard.tsx
Normal file
66
client/app/pages/soundboard/soundboard.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { SoundList, SoundType, Uploader } from '../../components';
|
||||
import { axios } from '../../services';
|
||||
import './soundboard.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
percentCompleted: number;
|
||||
uploaded: boolean;
|
||||
uploadError: string;
|
||||
soundList: SoundType[];
|
||||
}
|
||||
|
||||
export class Soundboard extends React.Component<Props, State> {
|
||||
private soundListCache: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: ' ',
|
||||
soundList: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getSoundList();
|
||||
}
|
||||
|
||||
private getSoundList() {
|
||||
if (!this.soundListCache) {
|
||||
axios
|
||||
.get('/api/soundlist')
|
||||
.then(response => {
|
||||
this.soundListCache = response.data;
|
||||
this.setState({
|
||||
soundList: response.data,
|
||||
});
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error(error.response.data);
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
soundList: this.soundListCache,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onUploadComplete = () => {
|
||||
delete this.soundListCache;
|
||||
this.getSoundList();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { soundList } = this.state;
|
||||
return (
|
||||
<div className="content">
|
||||
<Uploader onComplete={this.onUploadComplete} />
|
||||
<SoundList soundList={soundList} type="Sounds" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.Stats {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
20
client/app/scss/button.scss
Normal file
20
client/app/scss/button.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
.button {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
color: $white;
|
||||
background: $lightGray;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: lighten($lightGray, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
background: $primaryBlue;
|
||||
|
||||
&:hover {
|
||||
background: lighten($primaryBlue, 2%);
|
||||
}
|
||||
}
|
||||
20
client/app/scss/card.scss
Normal file
20
client/app/scss/card.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
.card {
|
||||
background-color: $gray2;
|
||||
border-radius: 5px;
|
||||
max-width: 800px;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid $gray3;
|
||||
}
|
||||
|
||||
.card__header {
|
||||
margin: -10px -10px 10px -10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 50px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
font-size: 25px;
|
||||
background-color: $gray3;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@import '~normalize.css/normalize.css';
|
||||
@import '~font-awesome/css/font-awesome.css';
|
||||
@import './variables.scss';
|
||||
@import './style.scss';
|
||||
@import './button.scss';
|
||||
@import './card.scss';
|
||||
|
||||
5
client/app/scss/mixins.scss
Normal file
5
client/app/scss/mixins.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
@mixin tinyScreen {
|
||||
@media only screen and (max-width: 520px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
@import './variables.scss';
|
||||
@import './mixins.scss';
|
||||
|
||||
html {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
@@ -26,11 +29,18 @@ i {
|
||||
body {
|
||||
background-color: $gray1;
|
||||
color: $white;
|
||||
padding-left: $navbarWidth;
|
||||
// padding-left: $navbarWidth;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 10px;
|
||||
padding: 20px;
|
||||
@include tinyScreen {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
@@ -43,48 +53,6 @@ body {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
color: $white;
|
||||
background: $lightGray;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: lighten($lightGray, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
background: $primaryBlue;
|
||||
|
||||
&:hover {
|
||||
background: lighten($primaryBlue, 2%);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: $gray2;
|
||||
border-radius: 5px;
|
||||
max-width: 800px;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid $gray3;
|
||||
}
|
||||
|
||||
.card__header {
|
||||
margin: -10px -10px 10px -10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 50px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
font-size: 25px;
|
||||
background-color: $gray3;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 1;
|
||||
|
||||
@@ -92,3 +60,8 @@ body {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import ax from 'axios';
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
export const axios = ax.create();
|
||||
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
const jwt = StorageService.getJWT();
|
||||
if (jwt) {
|
||||
config.headers['Authorization'] = `Bearer ${jwt}`;
|
||||
}
|
||||
// Do something before request is sent
|
||||
return config;
|
||||
},
|
||||
|
||||
43
client/app/stores/app.store.ts
Normal file
43
client/app/stores/app.store.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import { action, observable } from 'mobx';
|
||||
import { IClaims } from '../model';
|
||||
import { axios, StorageService } from '../services';
|
||||
import { Util } from '../util';
|
||||
|
||||
export class AppStore {
|
||||
@observable
|
||||
public navbarOpen: boolean = false;
|
||||
@observable
|
||||
public jwt?: string;
|
||||
@observable
|
||||
public claims?: IClaims;
|
||||
|
||||
constructor() {
|
||||
const jwt = StorageService.getJWT();
|
||||
this.setJWT(jwt as string);
|
||||
this.initNavbar();
|
||||
}
|
||||
|
||||
private initNavbar() {
|
||||
if (!Util.isMobileScreen()) {
|
||||
this.navbarOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private setJWT(jwt?: string) {
|
||||
if (!jwt) {
|
||||
return;
|
||||
}
|
||||
axios.defaults.headers['Authorization'] = `Bearer ${jwt}`;
|
||||
this.jwt = jwt;
|
||||
const claims = jwt_decode(jwt);
|
||||
if (claims) {
|
||||
this.claims = claims as IClaims;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
public toggleNavbar = () => {
|
||||
this.navbarOpen = !this.navbarOpen;
|
||||
};
|
||||
}
|
||||
2
client/app/stores/index.ts
Normal file
2
client/app/stores/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './app.store';
|
||||
export * from './root.store';
|
||||
7
client/app/stores/root.store.ts
Normal file
7
client/app/stores/root.store.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AppStore } from './app.store';
|
||||
|
||||
export class RootStore {
|
||||
public appStore = new AppStore();
|
||||
}
|
||||
|
||||
export const rootStoreInstance = new RootStore();
|
||||
17
client/app/util.ts
Normal file
17
client/app/util.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
const getScreenWidth = () => {
|
||||
const w = window,
|
||||
d = document,
|
||||
e = d.documentElement,
|
||||
g = d.getElementsByTagName('body')[0];
|
||||
|
||||
return w.innerWidth || e.clientWidth || g.clientWidth;
|
||||
};
|
||||
|
||||
const isMobileScreen = () => {
|
||||
return getScreenWidth() < 520;
|
||||
};
|
||||
|
||||
export const Util = {
|
||||
getScreenWidth,
|
||||
isMobileScreen,
|
||||
};
|
||||
16
client/app/wrapper.scss
Normal file
16
client/app/wrapper.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
@import './scss/variables';
|
||||
@import './scss/mixins';
|
||||
|
||||
.wrapper {
|
||||
padding-top: 50px;
|
||||
transition: 0.2s padding-left ease-in-out;
|
||||
padding-left: 0px;
|
||||
|
||||
&--open {
|
||||
padding-left: $navbarWidth;
|
||||
}
|
||||
|
||||
@include tinyScreen {
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
29
client/app/wrapper.tsx
Normal file
29
client/app/wrapper.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import { Header, Navbar } from './components';
|
||||
//styling
|
||||
import './scss/index.scss';
|
||||
import { Util } from './util';
|
||||
import './wrapper.scss';
|
||||
|
||||
export const Wrapper = inject('appStore')(
|
||||
withRouter(
|
||||
observer(({ appStore, children }) => {
|
||||
const openClass = appStore.navbarOpen ? 'wrapper--open' : '';
|
||||
const onNavClick = () => {
|
||||
if (Util.isMobileScreen()) {
|
||||
appStore.toggleNavbar();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header onButtonClick={appStore.toggleNavbar} />
|
||||
<Navbar claims={appStore.claims} open={appStore.navbarOpen} onNavClick={onNavClick} />
|
||||
<div className={'wrapper ' + openClass}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||
<meta http-equiv="expires" content="0">
|
||||
</head>
|
||||
|
||||
|
||||
21
client/package-lock.json
generated
21
client/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "go-discord-bot",
|
||||
"version": "0.5.2",
|
||||
"version": "0.6.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -5974,6 +5974,20 @@
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"mobx": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.0.3.tgz",
|
||||
"integrity": "sha1-U7l/Kg+bDdd3TJYkn4G/LVE9jhw="
|
||||
},
|
||||
"mobx-react": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.5.tgz",
|
||||
"integrity": "sha512-vSwsjGwmaqTmaEsPWET/APccjirTiIIchQA3YVasKzaxIGv62BNJUHFjrkIAbGBLeqJma+ZgSu158OqQLK0vaQ==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"react-lifecycles-compat": "3.0.4"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.22.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.0.tgz",
|
||||
@@ -8166,6 +8180,11 @@
|
||||
"prop-types": "15.6.2"
|
||||
}
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "go-discord-bot",
|
||||
"version": "0.5.2",
|
||||
"version": "0.6.0",
|
||||
"description": "Client for go-discord-bot",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=prod webpack -p --progress --colors",
|
||||
@@ -39,6 +39,8 @@
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.10",
|
||||
"mobx": "^5.0.3",
|
||||
"mobx-react": "^5.2.5",
|
||||
"node-sass": "^4.9.0",
|
||||
"normalize.css": "^8.0.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"target": "es2015",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
@@ -15,24 +16,10 @@
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": true,
|
||||
"alwaysStrict": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./@types"
|
||||
]
|
||||
"typeRoots": ["./node_modules/@types", "./@types"]
|
||||
},
|
||||
"filesGlob": [
|
||||
"typings/index.d.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"include": [
|
||||
"app"
|
||||
],
|
||||
"exclude": [
|
||||
"android",
|
||||
"ios",
|
||||
"build",
|
||||
"node_modules"
|
||||
],
|
||||
"filesGlob": ["typings/index.d.ts", "src/**/*.ts", "src/**/*.tsx"],
|
||||
"include": ["app"],
|
||||
"exclude": ["android", "ios", "build", "node_modules"],
|
||||
"compileOnSave": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version=$(git describe --tags)
|
||||
|
||||
docker build -t mgerb/go-discord-bot:$version .
|
||||
docker tag mgerb/go-discord-bot:$version mgerb/go-discord-bot:latest
|
||||
docker build -t mgerb/go-discord-bot:latest .
|
||||
docker tag mgerb/go-discord-bot:latest mgerb/go-discord-bot:$version
|
||||
|
||||
@@ -3,6 +3,7 @@ version: "2"
|
||||
services:
|
||||
go-discord-bot:
|
||||
image: mgerb/go-discord-bot:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
|
||||
1
fresh.conf
Normal file
1
fresh.conf
Normal file
@@ -0,0 +1 @@
|
||||
root: ./server
|
||||
47
readme.md
47
readme.md
@@ -2,16 +2,16 @@
|
||||
|
||||
A soundboard bot for discord with a Go back end and React front end.
|
||||
|
||||

|
||||

|
||||
|
||||
## How to use
|
||||
|
||||
* [Download latest release here](https://github.com/mgerb/go-discord-bot/releases)
|
||||
* Install [youtube-dl](https://github.com/rg3/youtube-dl/blob/master/README.md#installation)
|
||||
* Install [ffmpeg](https://www.ffmpeg.org/download.html)
|
||||
* edit your config.json file
|
||||
* run the executable
|
||||
* visit http://localhost:8080
|
||||
- [Download latest release here](https://github.com/mgerb/go-discord-bot/releases)
|
||||
- Install [youtube-dl](https://github.com/rg3/youtube-dl/blob/master/README.md#installation)
|
||||
- Install [ffmpeg](https://www.ffmpeg.org/download.html)
|
||||
- edit your config.json file
|
||||
- run the executable
|
||||
- visit http://localhost:8080
|
||||
|
||||
### With docker-compose
|
||||
|
||||
@@ -26,6 +26,7 @@ version: "2"
|
||||
services:
|
||||
go-discord-bot:
|
||||
image: mgerb/go-discord-bot:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
@@ -34,10 +35,10 @@ services:
|
||||
|
||||
### Commands
|
||||
|
||||
* `clip` - clips the past minute of audio (currently bugged if more than one user is speaking)
|
||||
* `summon` - summons the bot to your current channel
|
||||
* `dismiss` - dismisses the bot from the server
|
||||
* `<audio clip>` - play a named audio clip
|
||||
- `clip` - clips the past minute of audio (currently bugged if more than one user is speaking)
|
||||
- `summon` - summons the bot to your current channel
|
||||
- `dismiss` - dismisses the bot from the server
|
||||
- `<audio clip>` - play a named audio clip
|
||||
|
||||
### Uploading files
|
||||
|
||||
@@ -78,22 +79,22 @@ Check it out in the "Stats" page on the site.
|
||||
|
||||
### Dependencies
|
||||
|
||||
* Go
|
||||
* node/npm
|
||||
* make
|
||||
- Go
|
||||
- node/npm
|
||||
- make
|
||||
|
||||
### Compiling
|
||||
|
||||
* Make sure dependencies are installed
|
||||
* install packr - `go get -u github.com/gobuffalo/packr/...`
|
||||
* Rename the `config.template.json` to `config.json`
|
||||
* add configurations to `config.json`
|
||||
* `cd client && npm run dev`
|
||||
* `go run main.go`
|
||||
* open a browser `localhost:<config_port>`
|
||||
- Make sure dependencies are installed
|
||||
- install packr - `go get -u github.com/gobuffalo/packr/...`
|
||||
- Rename the `config.template.json` to `config.json`
|
||||
- add configurations to `config.json`
|
||||
- `cd client && npm run dev`
|
||||
- `go run main.go`
|
||||
- open a browser `localhost:<config_port>`
|
||||
|
||||
[Packr](https://github.com/gobuffalo/packr) is used to bundle the static web assets into the binary.
|
||||
Use these commands to compile the project. The client must be built first.
|
||||
|
||||
* `packr build`
|
||||
* `packr install`
|
||||
- `packr build`
|
||||
- `packr install`
|
||||
|
||||
1
run_client.sh
Executable file
1
run_client.sh
Executable file
@@ -0,0 +1 @@
|
||||
cd client && npm run dev
|
||||
1
run_server.sh
Executable file
1
run_server.sh
Executable file
@@ -0,0 +1 @@
|
||||
fresh -c fresh.conf
|
||||
BIN
screenshots/sound-bot.png
Normal file
BIN
screenshots/sound-bot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Reference in New Issue
Block a user