mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-09 16:42:48 +00:00
feat: add player controls to uploaded clips on stats page
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { SoundService } from '../../services';
|
||||
|
||||
interface IProps {
|
||||
sound: SoundType;
|
||||
type: 'sounds' | 'clips';
|
||||
showDiscordPlay?: boolean;
|
||||
}
|
||||
|
||||
interface IState {}
|
||||
|
||||
export interface SoundType {
|
||||
extension: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ClipPlayerControl extends React.Component<IProps, IState> {
|
||||
checkExtension(extension: string) {
|
||||
switch (extension) {
|
||||
case 'wav':
|
||||
return true;
|
||||
case 'mp3':
|
||||
return true;
|
||||
case 'mpeg':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
handlePlayAudioInBrowser(sound: SoundType, type: string) {
|
||||
const url = `/public/${type.toLowerCase()}/` + sound.name + '.' + sound.extension;
|
||||
const audio = new Audio(url);
|
||||
audio.play();
|
||||
}
|
||||
|
||||
onPlayDiscord = (sound: SoundType) => {
|
||||
SoundService.playSound(sound);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { sound, showDiscordPlay, type } = this.props;
|
||||
|
||||
return (
|
||||
this.checkExtension(sound.extension) && (
|
||||
<div className="flex flex--v-center">
|
||||
<a
|
||||
href={`/public/${type.toLowerCase()}/` + sound.name + '.' + sound.extension}
|
||||
download
|
||||
title="Download"
|
||||
className="fa fa-download link"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<i
|
||||
title="Play in browser"
|
||||
className="fa fa-play link"
|
||||
aria-hidden="true"
|
||||
style={{ paddingLeft: '15px' }}
|
||||
onClick={() => this.handlePlayAudioInBrowser(sound, type)}
|
||||
/>
|
||||
{showDiscordPlay && (
|
||||
<i
|
||||
title="Play in discord"
|
||||
className="fa fa-play-circle link fa-lg"
|
||||
aria-hidden="true"
|
||||
style={{ paddingLeft: '10px' }}
|
||||
onClick={() => this.onPlayDiscord(sound)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { ClipPlayerControl } from '../clip-player-control/clip-player-control';
|
||||
import './sound-list.scss';
|
||||
|
||||
interface Props {
|
||||
soundList: SoundType[];
|
||||
type: string;
|
||||
type: 'sounds' | 'clips';
|
||||
onPlayDiscord?: (sound: SoundType) => void;
|
||||
showDiscordPlay?: boolean;
|
||||
}
|
||||
@@ -46,7 +47,7 @@ export class SoundList extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onPlayDiscord, showDiscordPlay, soundList, type } = this.props;
|
||||
const { showDiscordPlay, soundList, type } = this.props;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
@@ -65,41 +66,7 @@ export class SoundList extends React.Component<Props, State> {
|
||||
<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
|
||||
controls
|
||||
src={`/public/${type.toLowerCase()}/` + sound.name + '.' + sound.extension}
|
||||
itemType={'audio/' + sound.extension}
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
<a
|
||||
href={`/public/${type.toLowerCase()}/` + sound.name + '.' + sound.extension}
|
||||
download
|
||||
title="Download"
|
||||
className="fa fa-download link"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<i
|
||||
title="Play in browser"
|
||||
className="fa fa-play link"
|
||||
aria-hidden="true"
|
||||
style={{ paddingLeft: '15px' }}
|
||||
onClick={() => this.handlePlayAudioInBrowser(sound, type)}
|
||||
/>
|
||||
{showDiscordPlay &&
|
||||
onPlayDiscord && (
|
||||
<i
|
||||
title="Play in discord"
|
||||
className="fa fa-play-circle link fa-lg"
|
||||
aria-hidden="true"
|
||||
style={{ paddingLeft: '10px' }}
|
||||
onClick={() => onPlayDiscord!(sound)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ClipPlayerControl showDiscordPlay={showDiscordPlay} sound={sound} type={type} />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -2,12 +2,14 @@ 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 }: IProps) => {
|
||||
export const UploadHistory = ({ sounds, showDiscordPlay }: IProps) => {
|
||||
const sortedSounds = _.orderBy(sounds, 'created_at', 'desc');
|
||||
return (
|
||||
<div className="card">
|
||||
@@ -15,7 +17,7 @@ export const UploadHistory = ({ sounds }: IProps) => {
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th className="hide-tiny">Date</th>
|
||||
<th>Sound</th>
|
||||
<th className="hide-tiny">Ext</th>
|
||||
<th>Username</th>
|
||||
@@ -27,7 +29,9 @@ export const UploadHistory = ({ sounds }: IProps) => {
|
||||
const formattedDate = DateTime.fromISO(s.created_at).toLocaleString();
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td title={formattedDate}>{formattedDate}</td>
|
||||
<td className="hide-tiny" title={formattedDate}>
|
||||
{formattedDate}
|
||||
</td>
|
||||
<td title={s.name}>{s.name}</td>
|
||||
<td className="hide-tiny" title={s.extension}>
|
||||
{s.extension}
|
||||
@@ -36,6 +40,9 @@ export const UploadHistory = ({ sounds }: IProps) => {
|
||||
<td className="hide-tiny" title={s.user.email}>
|
||||
{s.user.email}
|
||||
</td>
|
||||
<td>
|
||||
<ClipPlayerControl showDiscordPlay={showDiscordPlay} sound={s} type="sounds"></ClipPlayerControl>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -37,7 +37,7 @@ export class Clips extends React.Component<Props, State> {
|
||||
return (
|
||||
<div className="content">
|
||||
<div className="column">
|
||||
<SoundList soundList={this.state.clipList} type="Clips" />
|
||||
<SoundList soundList={this.state.clipList} type="clips" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import React from 'react';
|
||||
import { SoundList, SoundType, Uploader } from '../../components';
|
||||
import { Permissions } from '../../model';
|
||||
import { axios, SoundService } from '../../services';
|
||||
import { AppStore } from '../../stores';
|
||||
import './soundboard.scss';
|
||||
@@ -73,9 +72,9 @@ export class Soundboard extends React.Component<Props, State> {
|
||||
<Uploader onComplete={this.onUploadComplete} />
|
||||
<SoundList
|
||||
soundList={soundList}
|
||||
type="Sounds"
|
||||
type="sounds"
|
||||
onPlayDiscord={this.onPlayDiscord}
|
||||
showDiscordPlay={appStore!.claims && appStore!.claims!.permissions >= Permissions.Mod}
|
||||
showDiscordPlay={appStore!.hasModPermissions()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -74,7 +74,7 @@ export class Stats extends Component<IProps, IState> {
|
||||
},
|
||||
};
|
||||
|
||||
const { claims } = this.props.appStore;
|
||||
const { claims, hasModPermissions } = this.props.appStore;
|
||||
|
||||
return (
|
||||
<div className="content">
|
||||
@@ -82,7 +82,7 @@ export class Stats extends Component<IProps, IState> {
|
||||
<div className="card__header">Posts containing links</div>
|
||||
<HorizontalBar data={data} />
|
||||
</div>
|
||||
{claims && <UploadHistory sounds={this.state.sounds} />}
|
||||
{claims && <UploadHistory sounds={this.state.sounds} showDiscordPlay={hasModPermissions()} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import { action, observable } from 'mobx';
|
||||
import { IClaims } from '../model';
|
||||
import { IClaims, Permissions } from '../model';
|
||||
import { axios, StorageService } from '../services';
|
||||
import { Util } from '../util';
|
||||
|
||||
@@ -40,4 +40,8 @@ export class AppStore {
|
||||
public toggleNavbar = () => {
|
||||
this.navbarOpen = !this.navbarOpen;
|
||||
};
|
||||
|
||||
public hasModPermissions = () => {
|
||||
return this.claims && this.claims.permissions >= Permissions.Mod;
|
||||
};
|
||||
}
|
||||
|
||||
24
client/package-lock.json
generated
24
client/package-lock.json
generated
@@ -20,9 +20,9 @@
|
||||
"integrity": "sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A=="
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.110",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.110.tgz",
|
||||
"integrity": "sha512-iXYLa6olt4tnsCA+ZXeP6eEW3tk1SulWeYyP/yooWfAtXjozqXgtX4+XUtMuOCfYjKGz3F34++qUc3Q+TJuIIw=="
|
||||
"version": "4.14.137",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.137.tgz",
|
||||
"integrity": "sha512-g4rNK5SRKloO+sUGbuO7aPtwbwzMgjK+bm9BBhLD7jGUiGR7zhwYEhSln/ihgYQBeIJ5j7xjyaYzrWTcu3UotQ=="
|
||||
},
|
||||
"@types/luxon": {
|
||||
"version": "1.2.2",
|
||||
@@ -5405,9 +5405,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"lodash.assign": {
|
||||
"version": "4.2.0",
|
||||
@@ -6237,9 +6237,9 @@
|
||||
}
|
||||
},
|
||||
"normalize.css": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.0.tgz",
|
||||
"integrity": "sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A=="
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
|
||||
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
|
||||
},
|
||||
"npm-run-path": {
|
||||
"version": "2.0.2",
|
||||
@@ -7983,9 +7983,9 @@
|
||||
}
|
||||
},
|
||||
"react-chartjs-2": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.7.2.tgz",
|
||||
"integrity": "sha512-ncTSOZjlI9j3aXGbsiXEoCtb6oS5nixYe9wLWK+3af1aq7K/vcFkzA9faA5Z/q+fgMDWLEa078cWAyzymxvvYg==",
|
||||
"version": "2.7.6",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.7.6.tgz",
|
||||
"integrity": "sha512-xDr0jhgt/o26atftXxTVsepz+QYZI2GNKBYpxtLvYgwffLUm18a9n562reUJAHvuwKsy2v+qMlK5HyjFtSW0mg==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.4",
|
||||
"prop-types": "^15.5.8"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"@types/chart.js": "^2.7.22",
|
||||
"@types/jwt-decode": "^2.2.1",
|
||||
"@types/lodash": "^4.14.110",
|
||||
"@types/lodash": "^4.14.137",
|
||||
"@types/luxon": "^1.2.2",
|
||||
"@types/node": "^10.3.4",
|
||||
"@types/nprogress": "0.0.29",
|
||||
@@ -39,18 +39,18 @@
|
||||
"font-awesome": "^4.7.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.10",
|
||||
"lodash": "^4.17.15",
|
||||
"luxon": "^1.3.3",
|
||||
"mini-css-extract-plugin": "^0.4.2",
|
||||
"mobx": "^5.0.3",
|
||||
"mobx-react": "^5.2.5",
|
||||
"node-sass": "^4.9.3",
|
||||
"normalize.css": "^8.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"query-string": "^6.1.0",
|
||||
"react": "^16.4.1",
|
||||
"react-chartjs-2": "^2.7.2",
|
||||
"react-chartjs-2": "^2.7.6",
|
||||
"react-dom": "^16.4.1",
|
||||
"react-dropzone": "^4.2.11",
|
||||
"react-router-dom": "^4.3.1",
|
||||
|
||||
Reference in New Issue
Block a user