1
0
mirror of https://github.com/mgerb/go-discord-bot synced 2026-01-11 01:22:48 +00:00

UI Overhaul

This commit is contained in:
2018-08-19 18:17:40 -05:00
parent 5fedcd4b40
commit e593472c84
58 changed files with 633 additions and 685 deletions

View File

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

View File

@@ -1 +0,0 @@
export * from './Navbar';

View File

@@ -1 +0,0 @@
export * from './SoundList';

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

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

View File

@@ -0,0 +1,4 @@
export * from './header/header';
export * from './navbar/navbar';
export * from './sound-list/sound-list';
export * from './uploader/uploader';

View File

@@ -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;

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

View File

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

View File

@@ -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

View File

@@ -0,0 +1,22 @@
@import '../../scss/variables';
.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;
color: $lightGray;
background-color: $gray2;
transition: box-shadow 0.1s linear, background-color 0.1s linear;
cursor: pointer;
}
.dropzone--active {
background-color: $gray3;
box-shadow: 0px 0px 5px 1px $primaryBlue;
}

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