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

feat: add favorites - update dependencies

This commit is contained in:
2020-06-11 22:41:02 -05:00
parent b7d13ee5fd
commit 58170df201
18 changed files with 3040 additions and 2307 deletions

View File

@@ -1,19 +1,18 @@
import React from 'react';
import { SoundService } from '../../services';
import { SoundListType, SoundType } from '../../model';
interface IProps {
sound: SoundType;
type: 'sounds' | 'clips';
showDiscordPlay?: boolean;
type: SoundListType;
hasModPermissions: boolean;
showFavorite?: boolean;
onFavorite?: () => void;
onPlayBrowser: (sound: SoundType) => void;
onPlayDiscord: (sound: SoundType) => void;
}
interface IState {}
export interface SoundType {
extension: string;
name: string;
}
export class ClipPlayerControl extends React.Component<IProps, IState> {
checkExtension(extension: string) {
switch (extension) {
@@ -28,22 +27,21 @@ export class ClipPlayerControl extends React.Component<IProps, IState> {
}
}
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;
const { onPlayBrowser, onPlayDiscord, sound, hasModPermissions, showFavorite, type } = this.props;
return (
this.checkExtension(sound.extension) && (
<div className="flex flex--center">
{showFavorite && hasModPermissions && (
<i
title="Favorite"
className={'fa link fa-lg ' + (type === 'favorites' ? 'fa-trash' : 'fa-heart color__red')}
aria-hidden="true"
style={{ paddingRight: '5px' }}
onClick={() => !this.props.onFavorite || this.props.onFavorite()}
/>
)}
<a
href={`/public/${type.toLowerCase()}/` + sound.name + '.' + sound.extension}
download
@@ -56,15 +54,15 @@ export class ClipPlayerControl extends React.Component<IProps, IState> {
className="fa fa-play link"
aria-hidden="true"
style={{ paddingLeft: '15px' }}
onClick={() => this.handlePlayAudioInBrowser(sound, type)}
onClick={() => onPlayBrowser(sound)}
/>
{showDiscordPlay && (
{hasModPermissions && (
<i
title="Play in discord"
className="fa fa-play-circle link fa-lg"
aria-hidden="true"
style={{ paddingLeft: '10px' }}
onClick={() => this.onPlayDiscord(sound)}
onClick={() => onPlayDiscord(sound)}
/>
)}
</div>

View File

@@ -1,25 +1,23 @@
import React from 'react';
import { SoundListType, SoundType } from '../../model';
import { SoundService } from '../../services';
import { ClipPlayerControl } from '../clip-player-control/clip-player-control';
import './sound-list.scss';
interface Props {
soundList: SoundType[];
type: 'sounds' | 'clips';
type: SoundListType;
title: string;
onPlayDiscord?: (sound: SoundType) => void;
showDiscordPlay?: boolean;
hasModPermissions: boolean;
onFavorite?: (sound: SoundType) => void;
deleteFavorite?: (sound: SoundType) => void;
}
interface State {
showAudioControls: boolean[];
}
export interface SoundType {
extension: string;
name: string;
prefix?: string;
}
export class SoundList extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -41,14 +39,8 @@ export class SoundList extends React.Component<Props, State> {
}
}
handlePlayAudioInBrowser(sound: SoundType, type: string) {
const url = `/public/${type.toLowerCase()}/` + sound.name + '.' + sound.extension;
const audio = new Audio(url);
audio.play();
}
render() {
const { showDiscordPlay, soundList, title, type } = this.props;
const { hasModPermissions, onFavorite, soundList, title, type } = this.props;
return (
<div className="card">
@@ -66,10 +58,18 @@ export class SoundList extends React.Component<Props, State> {
return (
<div key={index} className="sound-list__item">
<div className="text-wrap">
{(type === 'sounds' && sound.prefix ? sound.prefix : '') + sound.name}
{((type === 'sounds' || type === 'favorites') && sound.prefix ? sound.prefix : '') + sound.name}
</div>
<ClipPlayerControl showDiscordPlay={showDiscordPlay} sound={sound} type={type} />
<ClipPlayerControl
showFavorite={type !== 'clips'}
onFavorite={() => !onFavorite || onFavorite(sound)}
hasModPermissions={hasModPermissions}
sound={sound}
type={type}
onPlayBrowser={(sound) => SoundService.playAudioInBrowser(sound, type)}
onPlayDiscord={SoundService.playSound}
/>
</div>
);
})

View File

@@ -1,5 +1,14 @@
import { IUser } from './user';
export type SoundListType = 'sounds' | 'clips' | 'favorites';
export interface SoundType {
extension: string;
name: string;
prefix?: string;
}
// sound from database
export interface ISound {
created_at: string;
deleted_at?: string;

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { SoundList, SoundType } from '../../components';
import { SoundList } from '../../components';
import { SoundType } from '../../model';
import { axios } from '../../services';
interface Props {}
@@ -23,7 +24,7 @@ export class Clips extends React.Component<Props, State> {
private getClipList() {
axios
.get('/api/cliplist')
.then(response => {
.then((response) => {
this.setState({
clipList: response.data,
});
@@ -37,7 +38,8 @@ export class Clips extends React.Component<Props, State> {
return (
<div className="content">
<div className="column">
<SoundList soundList={this.state.clipList} type="clips" title="Clips" />
{/* no need for permissions on this component - set false */}
<SoundList soundList={this.state.clipList} type="clips" title="Clips" hasModPermissions={false} />
</div>
</div>
);

View File

@@ -13,7 +13,7 @@ export class Oauth extends React.Component<Props, State> {
}
componentDidMount() {
const params = queryString.parse(this.props.location.search);
const params: any = queryString.parse(this.props.location.search);
if (params['code']) {
// do stuff here

View File

@@ -1,6 +1,7 @@
import { inject, observer } from 'mobx-react';
import React from 'react';
import { SoundList, SoundType, Uploader } from '../../components';
import { SoundList, Uploader } from '../../components';
import { SoundType } from '../../model';
import { axios, SoundService } from '../../services';
import { AppStore } from '../../stores';
import './soundboard.scss';
@@ -39,7 +40,7 @@ export class Soundboard extends React.Component<Props, State> {
if (!this.soundListCache) {
axios
.get('/api/soundlist')
.then(response => {
.then((response) => {
this.soundListCache = response.data;
this.setState({
soundList: response.data,
@@ -64,18 +65,42 @@ export class Soundboard extends React.Component<Props, State> {
SoundService.playSound(sound);
};
onFavorite = (sound: SoundType) => {
this.props.appStore?.addFavorite(sound);
};
onDeleteFavorite = (sound: SoundType) => {
this.props.appStore?.removeFavorite(sound);
};
render() {
const { soundList } = this.state;
const { appStore } = this.props;
if (!this.props.appStore) {
return null;
}
const { hasModPermissions, getFavorites } = this.props.appStore;
return (
<div className="content">
<Uploader onComplete={this.onUploadComplete} />
{((hasModPermissions && getFavorites().length) || 0 > 0) && (
<SoundList
soundList={getFavorites()}
title="Favorites"
type="favorites"
onPlayDiscord={this.onPlayDiscord}
hasModPermissions={hasModPermissions()}
onFavorite={this.onDeleteFavorite}
/>
)}
<SoundList
soundList={soundList}
title="Sounds"
type="sounds"
onPlayDiscord={this.onPlayDiscord}
showDiscordPlay={appStore!.hasModPermissions()}
hasModPermissions={hasModPermissions()}
onFavorite={this.onFavorite}
/>
</div>
);

View File

@@ -26,12 +26,12 @@ export class UploadHistory extends React.Component<IProps, IState> {
}
componentDidMount() {
SoundService.getSounds().then(sounds => {
SoundService.getSounds().then((sounds) => {
this.setState({ sounds });
});
}
renderUploadHistory = (sounds: ISound[], showDiscordPlay: boolean) => {
renderUploadHistory = (sounds: ISound[], hasModPermissions: boolean) => {
const sortedSounds = orderBy(sounds, 'created_at', 'desc');
return (
<div className="card">
@@ -63,7 +63,13 @@ export class UploadHistory extends React.Component<IProps, IState> {
{s.user.email}
</td>
<td>
<ClipPlayerControl showDiscordPlay={showDiscordPlay} sound={s} type="sounds"></ClipPlayerControl>
<ClipPlayerControl
onPlayBrowser={(sound) => SoundService.playAudioInBrowser(sound, 'sounds')}
onPlayDiscord={SoundService.playSound}
hasModPermissions={hasModPermissions}
sound={s}
type="sounds"
></ClipPlayerControl>
</td>
</tr>
);

View File

@@ -1,5 +1,4 @@
import { SoundType } from '../components/sound-list/sound-list';
import { ISound } from '../model';
import { ISound, SoundListType, SoundType } from '../model';
import { axios } from './axios.service';
const playSound = (sound: SoundType): Promise<any> => {
@@ -11,7 +10,15 @@ const getSounds = async (): Promise<ISound[]> => {
return res.data.data;
};
export const playAudioInBrowser = (sound: SoundType, type: SoundListType) => {
const t = type === 'favorites' ? 'sounds' : type;
const url = `/public/${t.toLowerCase()}/` + sound.name + '.' + sound.extension;
const audio = new Audio(url);
audio.play();
};
export const SoundService = {
getSounds,
playSound,
playAudioInBrowser,
};

View File

@@ -1,3 +1,5 @@
import { SoundType } from '../model';
const clear = () => {
localStorage.clear();
};
@@ -10,8 +12,19 @@ const getJWT = (): string | null => {
return localStorage.getItem('jwt');
};
const getFavorites = (): SoundType[] => {
const f = localStorage.getItem('favorites');
return f ? JSON.parse(f) : [];
};
const setFavorites = (f: SoundType[]): void => {
localStorage.setItem('favorites', JSON.stringify(f));
};
export const StorageService = {
clear,
getJWT,
setJWT,
getFavorites,
setFavorites,
};

View File

@@ -1,6 +1,7 @@
import jwt_decode from 'jwt-decode';
import { filter, uniqBy } from 'lodash';
import { action, observable } from 'mobx';
import { IClaims, Permissions } from '../model';
import { IClaims, Permissions, SoundType } from '../model';
import { axios, StorageService } from '../services';
import { Util } from '../util';
@@ -11,9 +12,12 @@ export class AppStore {
public jwt?: string;
@observable
public claims?: IClaims;
@observable
private favorites: SoundType[] = [];
constructor() {
const jwt = StorageService.getJWT();
this.favorites = StorageService.getFavorites();
this.setJWT(jwt as string);
this.initNavbar();
}
@@ -24,7 +28,7 @@ export class AppStore {
}
}
private setJWT(jwt?: string) {
private setJWT = (jwt?: string) => {
if (!jwt) {
return;
}
@@ -34,7 +38,21 @@ export class AppStore {
if (claims) {
this.claims = claims as IClaims;
}
}
};
public getFavorites = (): SoundType[] => {
return this.favorites;
};
public addFavorite = (f: SoundType): void => {
this.favorites = uniqBy([...this.favorites, f], 'name');
StorageService.setFavorites(this.favorites);
};
public removeFavorite = (f: SoundType): void => {
this.favorites = filter(this.favorites, (fa) => fa.name !== f.name);
StorageService.setFavorites(this.favorites);
};
@action
public toggleNavbar = () => {

View File

@@ -7,7 +7,7 @@ import './wrapper.scss';
export const Wrapper = inject('appStore')(
withRouter(
observer(({ appStore, children }) => {
observer(({ appStore, children }: any) => {
const openClass = appStore.navbarOpen ? 'wrapper--open' : '';
const onNavClick = () => {
if (Util.isMobileScreen()) {

4983
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,16 +10,16 @@
"author": "Mitchell Gerber",
"license": "MIT",
"dependencies": {
"@types/chart.js": "^2.7.22",
"@types/chart.js": "^2.9.21",
"@types/jwt-decode": "^2.2.1",
"@types/lodash": "^4.14.137",
"@types/luxon": "^1.2.2",
"@types/lodash": "^4.14.155",
"@types/luxon": "^1.24.0",
"@types/node": "^10.3.4",
"@types/nprogress": "0.0.29",
"@types/query-string": "^6.1.0",
"@types/react": "^16.4.1",
"@types/query-string": "^6.3.0",
"@types/react": "^16.9.36",
"@types/react-chartjs-2": "^2.5.7",
"@types/react-dom": "^16.0.6",
"@types/react-dom": "^16.9.8",
"@types/react-dropzone": "^4.2.0",
"@types/react-router-dom": "^4.2.7",
"autoprefixer": "^8.6.3",
@@ -31,7 +31,7 @@
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"chart.js": "^2.7.2",
"chart.js": "^2.9.3",
"clean-webpack-plugin": "^0.1.14",
"css-loader": "^0.28.11",
"favicons-webpack-plugin": "0.0.9",
@@ -40,27 +40,27 @@
"html-webpack-plugin": "^3.2.0",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.15",
"luxon": "^1.3.3",
"luxon": "^1.24.1",
"mini-css-extract-plugin": "^0.4.2",
"mobx": "^5.0.3",
"mobx": "^5.15.4",
"mobx-react": "^5.2.5",
"node-sass": "^4.9.3",
"node-sass": "^4.14.1",
"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.6",
"react-dom": "^16.4.1",
"query-string": "^6.13.1",
"react": "^16.13.1",
"react-chartjs-2": "^2.9.0",
"react-dom": "^16.13.1",
"react-dropzone": "^4.2.11",
"react-router-dom": "^4.3.1",
"sass-loader": "^7.0.3",
"style-loader": "^0.21.0",
"ts-loader": "^4.4.1",
"typescript": "^2.9.2",
"typescript": "^3.9.5",
"url-loader": "^1.0.1",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4"
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
}
}