mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-10 09:02:49 +00:00
refactor: move upload history to its own page
This commit is contained in:
@@ -2,7 +2,7 @@ import { Provider } from 'mobx-react';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||||
import { Admin, Clips, Downloader, NotFound, Oauth, Soundboard, Stats, VideoArchive } from './pages';
|
import { Admin, Clips, Downloader, NotFound, Oauth, Soundboard, Stats, UploadHistory, VideoArchive } from './pages';
|
||||||
import './scss/index.scss';
|
import './scss/index.scss';
|
||||||
import { rootStoreInstance } from './stores';
|
import { rootStoreInstance } from './stores';
|
||||||
import { Wrapper } from './wrapper';
|
import { Wrapper } from './wrapper';
|
||||||
@@ -14,6 +14,7 @@ const App: any = (): any => {
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Soundboard} />
|
<Route exact path="/" component={Soundboard} />
|
||||||
|
<Route path="/upload-history" component={UploadHistory} />
|
||||||
<Route path="/downloader" component={Downloader} />
|
<Route path="/downloader" component={Downloader} />
|
||||||
<Route path="/clips" component={Clips} />
|
<Route path="/clips" component={Clips} />
|
||||||
<Route path="/oauth" component={Oauth} />
|
<Route path="/oauth" component={Oauth} />
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ export * from './embedded-youtube/embedded-youtube';
|
|||||||
export * from './header/header';
|
export * from './header/header';
|
||||||
export * from './navbar/navbar';
|
export * from './navbar/navbar';
|
||||||
export * from './sound-list/sound-list';
|
export * from './sound-list/sound-list';
|
||||||
export * from './upload-history/upload-history';
|
|
||||||
export * from './uploader/uploader';
|
export * from './uploader/uploader';
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { IClaims, Permissions } from '../../model';
|
|
||||||
import { OauthService, StorageService } from '../../services';
|
import { OauthService, StorageService } from '../../services';
|
||||||
|
import { AppStore } from '../../stores';
|
||||||
import './navbar.scss';
|
import './navbar.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
claims?: IClaims;
|
appStore: AppStore;
|
||||||
open: boolean;
|
|
||||||
onNavClick: () => void;
|
onNavClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ export class Navbar extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderLoginButton() {
|
renderLoginButton() {
|
||||||
const { claims } = this.props;
|
const { claims } = this.props.appStore;
|
||||||
|
|
||||||
if (!this.state.oauthUrl) {
|
if (!this.state.oauthUrl) {
|
||||||
return null;
|
return null;
|
||||||
@@ -69,19 +68,17 @@ export class Navbar extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { claims, open } = this.props;
|
const { claims, navbarOpen, hasModPermissions, hasAdminPermissions } = this.props.appStore;
|
||||||
const openClass = open ? 'navbar--open' : '';
|
const openClass = navbarOpen ? 'navbar--open' : '';
|
||||||
return (
|
return (
|
||||||
<div className={'navbar ' + openClass}>
|
<div className={'navbar ' + openClass}>
|
||||||
{this.renderNavLink('Soundboard', '/', { exact: true })}
|
{this.renderNavLink('Soundboard', '/', { exact: true })}
|
||||||
|
{hasModPermissions() && this.renderNavLink('Upload History', '/upload-history')}
|
||||||
{this.renderNavLink('Video Archive', '/video-archive')}
|
{this.renderNavLink('Video Archive', '/video-archive')}
|
||||||
{this.renderNavLink('Youtube Downloader', '/downloader')}
|
{this.renderNavLink('Youtube Downloader', '/downloader')}
|
||||||
{this.renderNavLink('Clips', '/clips')}
|
{this.renderNavLink('Clips', '/clips')}
|
||||||
{this.renderNavLink('Stats', '/stats')}
|
{this.renderNavLink('Stats', '/stats')}
|
||||||
{claims &&
|
{hasAdminPermissions() && this.renderNavLink('Admin', '/admin')}
|
||||||
claims.permissions &&
|
|
||||||
claims.permissions === Permissions.Admin &&
|
|
||||||
this.renderNavLink('Admin', '/admin')}
|
|
||||||
{this.renderLoginButton()}
|
{this.renderLoginButton()}
|
||||||
|
|
||||||
{claims && claims.email && <div className="navbar__email">{claims.email}</div>}
|
{claims && claims.email && <div className="navbar__email">{claims.email}</div>}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import './sound-list.scss';
|
|||||||
interface Props {
|
interface Props {
|
||||||
soundList: SoundType[];
|
soundList: SoundType[];
|
||||||
type: 'sounds' | 'clips';
|
type: 'sounds' | 'clips';
|
||||||
|
title: string;
|
||||||
onPlayDiscord?: (sound: SoundType) => void;
|
onPlayDiscord?: (sound: SoundType) => void;
|
||||||
showDiscordPlay?: boolean;
|
showDiscordPlay?: boolean;
|
||||||
}
|
}
|
||||||
@@ -47,13 +48,13 @@ export class SoundList extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showDiscordPlay, soundList, type } = this.props;
|
const { showDiscordPlay, soundList, title, type } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card__header" style={{ display: 'flex' }}>
|
<div className="card__header" style={{ display: 'flex' }}>
|
||||||
<div>
|
<div>
|
||||||
<span>{type}</span>
|
<span>{title}</span>
|
||||||
<i className="fa fa fa-volume-up" aria-hidden="true" />
|
<i className="fa fa fa-volume-up" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }} />
|
<div style={{ flex: 1 }} />
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import * as _ from 'lodash';
|
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import React from 'react';
|
|
||||||
import { ISound } from '../../model';
|
|
||||||
import { ClipPlayerControl } from '../clip-player-control/clip-player-control';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
sounds: ISound[];
|
|
||||||
showDiscordPlay?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UploadHistory = ({ sounds, showDiscordPlay }: IProps) => {
|
|
||||||
const sortedSounds = _.orderBy(sounds, 'created_at', 'desc');
|
|
||||||
return (
|
|
||||||
<div className="card">
|
|
||||||
<div className="card__header">Upload History</div>
|
|
||||||
<table className="table table--ellipsis">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="hide-small">Date</th>
|
|
||||||
<th>Sound</th>
|
|
||||||
<th className="hide-small">Ext</th>
|
|
||||||
<th>User</th>
|
|
||||||
<th className="hide-small">Email</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{sortedSounds.map((s: ISound, i) => {
|
|
||||||
const formattedDate = DateTime.fromISO(s.created_at).toLocaleString();
|
|
||||||
return (
|
|
||||||
<tr key={i}>
|
|
||||||
<td className="hide-small" title={formattedDate}>
|
|
||||||
{formattedDate}
|
|
||||||
</td>
|
|
||||||
<td title={s.name}>{s.name}</td>
|
|
||||||
<td className="hide-small" title={s.extension}>
|
|
||||||
{s.extension}
|
|
||||||
</td>
|
|
||||||
<td title={s.user.username}>{s.user.username}</td>
|
|
||||||
<td className="hide-small" title={s.user.email}>
|
|
||||||
{s.user.email}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ClipPlayerControl showDiscordPlay={showDiscordPlay} sound={s} type="sounds"></ClipPlayerControl>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -37,7 +37,7 @@ export class Clips extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<SoundList soundList={this.state.clipList} type="clips" />
|
<SoundList soundList={this.state.clipList} type="clips" title="Clips" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export * from './not-found/not-found';
|
|||||||
export * from './oauth/oauth';
|
export * from './oauth/oauth';
|
||||||
export * from './soundboard/soundboard';
|
export * from './soundboard/soundboard';
|
||||||
export * from './stats/stats';
|
export * from './stats/stats';
|
||||||
|
export * from './upload-history/upload-history';
|
||||||
export * from './video-archive/video-archive';
|
export * from './video-archive/video-archive';
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export class Soundboard extends React.Component<Props, State> {
|
|||||||
<Uploader onComplete={this.onUploadComplete} />
|
<Uploader onComplete={this.onUploadComplete} />
|
||||||
<SoundList
|
<SoundList
|
||||||
soundList={soundList}
|
soundList={soundList}
|
||||||
|
title="Sounds"
|
||||||
type="sounds"
|
type="sounds"
|
||||||
onPlayDiscord={this.onPlayDiscord}
|
onPlayDiscord={this.onPlayDiscord}
|
||||||
showDiscordPlay={appStore!.hasModPermissions()}
|
showDiscordPlay={appStore!.hasModPermissions()}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { chain, map } from 'lodash';
|
import { chain, map } from 'lodash';
|
||||||
import { inject, observer } from 'mobx-react';
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { HorizontalBar } from 'react-chartjs-2';
|
import { HorizontalBar } from 'react-chartjs-2';
|
||||||
import { UploadHistory } from '../../components';
|
|
||||||
import { ISound } from '../../model';
|
import { ISound } from '../../model';
|
||||||
import { axios, SoundService } from '../../services';
|
import { axios, SoundService } from '../../services';
|
||||||
import { AppStore } from '../../stores';
|
|
||||||
import './stats.scss';
|
import './stats.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {}
|
||||||
appStore: AppStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
data: {
|
data: {
|
||||||
@@ -24,8 +19,6 @@ interface IState {
|
|||||||
* a page to show discord chat statistics
|
* a page to show discord chat statistics
|
||||||
* currently keeps track of number messages that contain external links
|
* currently keeps track of number messages that contain external links
|
||||||
*/
|
*/
|
||||||
@inject('appStore')
|
|
||||||
@observer
|
|
||||||
export class Stats extends Component<IProps, IState> {
|
export class Stats extends Component<IProps, IState> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -74,15 +67,12 @@ export class Stats extends Component<IProps, IState> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { claims, hasModPermissions } = this.props.appStore;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card__header">Posts containing links</div>
|
<div className="card__header">Posts containing links</div>
|
||||||
<HorizontalBar data={data} />
|
<HorizontalBar data={data} />
|
||||||
</div>
|
</div>
|
||||||
{claims && <UploadHistory sounds={this.state.sounds} showDiscordPlay={hasModPermissions()} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
81
client/app/pages/upload-history/upload-history.tsx
Normal file
81
client/app/pages/upload-history/upload-history.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import React from 'react';
|
||||||
|
import { ClipPlayerControl } from '../../components/clip-player-control/clip-player-control';
|
||||||
|
import { ISound } from '../../model';
|
||||||
|
import { SoundService } from '../../services';
|
||||||
|
import { AppStore } from '../../stores';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
appStore: AppStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
sounds: ISound[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@inject('appStore')
|
||||||
|
@observer
|
||||||
|
export class UploadHistory extends React.Component<IProps, IState> {
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
sounds: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
SoundService.getSounds().then(sounds => {
|
||||||
|
this.setState({ sounds });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUploadHistory = (sounds: ISound[], showDiscordPlay: boolean) => {
|
||||||
|
const sortedSounds = orderBy(sounds, 'created_at', 'desc');
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card__header">Upload History</div>
|
||||||
|
<table className="table table--ellipsis">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="hide-small">Date</th>
|
||||||
|
<th>Sound</th>
|
||||||
|
<th className="hide-small">Ext</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th className="hide-small">Email</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sortedSounds.map((s: ISound, i) => {
|
||||||
|
const formattedDate = DateTime.fromISO(s.created_at).toLocaleString();
|
||||||
|
return (
|
||||||
|
<tr key={i}>
|
||||||
|
<td className="hide-small" title={formattedDate}>
|
||||||
|
{formattedDate}
|
||||||
|
</td>
|
||||||
|
<td title={s.name}>{s.name}</td>
|
||||||
|
<td className="hide-small" title={s.extension}>
|
||||||
|
{s.extension}
|
||||||
|
</td>
|
||||||
|
<td title={s.user.username}>{s.user.username}</td>
|
||||||
|
<td className="hide-small" title={s.user.email}>
|
||||||
|
{s.user.email}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ClipPlayerControl showDiscordPlay={showDiscordPlay} sound={s} type="sounds"></ClipPlayerControl>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
public render() {
|
||||||
|
const { hasModPermissions } = this.props.appStore;
|
||||||
|
const { sounds } = this.state;
|
||||||
|
return <div className="content">{this.renderUploadHistory(sounds, hasModPermissions())}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,11 @@ export class AppStore {
|
|||||||
this.navbarOpen = !this.navbarOpen;
|
this.navbarOpen = !this.navbarOpen;
|
||||||
};
|
};
|
||||||
|
|
||||||
public hasModPermissions = () => {
|
public hasModPermissions = (): boolean => {
|
||||||
return this.claims && this.claims.permissions >= Permissions.Mod;
|
return !!this.claims && this.claims.permissions >= Permissions.Mod;
|
||||||
|
};
|
||||||
|
|
||||||
|
public hasAdminPermissions = (): boolean => {
|
||||||
|
return !!this.claims && this.claims.permissions >= Permissions.Admin;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const Wrapper = inject('appStore')(
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Header onButtonClick={appStore.toggleNavbar} />
|
<Header onButtonClick={appStore.toggleNavbar} />
|
||||||
<Navbar claims={appStore.claims} open={appStore.navbarOpen} onNavClick={onNavClick} />
|
<Navbar appStore={appStore} onNavClick={onNavClick} />
|
||||||
<div className={'wrapper ' + openClass}>{children}</div>
|
<div className={'wrapper ' + openClass}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user