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

UI done for video archiving

This commit is contained in:
2018-08-23 00:07:08 -05:00
parent 5a542e0ffb
commit 94bac26903
30 changed files with 393 additions and 68 deletions

View File

@@ -1,9 +1,9 @@
import 'babel-polyfill';
import './scss/index.scss';
import { Provider } from 'mobx-react';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { Clips, Downloader, NotFound, Oauth, Soundboard, Stats } from './pages';
import { Clips, Downloader, NotFound, Oauth, Soundboard, Stats, VideoArchive } from './pages';
import { rootStoreInstance } from './stores';
import { Wrapper } from './wrapper';
@@ -18,6 +18,7 @@ const App: any = (): any => {
<Route path="/clips" component={Clips} />
<Route path="/oauth" component={Oauth} />
<Route path="/stats" component={Stats} />
<Route path="/video-archive" component={VideoArchive} />
<Route component={NotFound} />
</Switch>
</Wrapper>

View File

@@ -0,0 +1,15 @@
.embedded-youtube {
overflow: hidden;
padding-bottom: 56.25%;
position: relative;
height: 0;
&__iframe {
left: 0;
top: 0;
height: 100%;
width: 100%;
position: absolute;
border: none;
}
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import './embedded-youtube.scss';
interface IProps {
id: string;
className?: string;
}
export const EmbeddedYoutube = ({ id, className }: IProps) => {
const src = `https://www.youtube.com/embed/${id}`;
return (
<div className={`embedded-youtube ${className}`}>
<iframe src={src} className="embedded-youtube__iframe" allowFullScreen />
</div>
);
};

View File

@@ -11,6 +11,7 @@
align-items: center;
justify-content: space-between;
padding-right: 20px;
z-index: 100;
&__title-container {
display: flex;

View File

@@ -1,3 +1,4 @@
export * from './embedded-youtube/embedded-youtube';
export * from './header/header';
export * from './navbar/navbar';
export * from './sound-list/sound-list';

View File

@@ -13,6 +13,7 @@
overflow-y: auto;
padding-bottom: 10px;
transition: 0.2s left ease-in-out;
z-index: 100;
&--open {
left: 0;

View File

@@ -74,6 +74,7 @@ export class Navbar extends React.Component<Props, State> {
return (
<div className={'navbar ' + openClass}>
{this.renderNavLink('Soundboard', '/', { exact: true })}
{this.renderNavLink('Video Archive', '/video-archive')}
{this.renderNavLink('Youtube Downloader', '/downloader')}
{this.renderNavLink('Clips', '/clips')}
{this.renderNavLink('Stats', '/stats')}

View File

@@ -1,2 +1,3 @@
export * from './claims';
export * from './permissions';
export * from './video-archive';

View File

@@ -0,0 +1,13 @@
export interface IVideoArchive {
author: string;
created_at: string;
date_published: string;
description: string;
duration: number;
id: number;
title: string;
updated_at: string;
uploaded_by: string;
url: string;
youtube_id: string;
}

View File

@@ -4,3 +4,4 @@ export * from './not-found/not-found';
export * from './oauth/oauth';
export * from './soundboard/soundboard';
export * from './stats/stats';
export * from './video-archive/video-archive';

View File

@@ -0,0 +1,14 @@
.video-archive {
// custom card
&__card {
border-top-right-radius: 0;
border-top-left-radius: 0;
padding: 0 0 10px;
max-width: initial;
}
&__text-input {
width: 100%;
max-width: 500px;
}
}

View File

@@ -0,0 +1,106 @@
import { DateTime } from 'luxon';
import { inject, observer } from 'mobx-react';
import React from 'react';
import { EmbeddedYoutube } from '../../components';
import { IVideoArchive, Permissions } from '../../model';
import { ArchiveService } from '../../services/archive.service';
import { AppStore } from '../../stores';
import './video-archive.scss';
interface IProps {
appStore: AppStore;
}
interface IState {
archives: IVideoArchive[];
url: string;
error?: string;
}
@inject('appStore')
@observer
export class VideoArchive extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
archives: [],
url: '',
};
}
componentDidMount() {
this.loadArchives();
}
async loadArchives() {
const archives = await ArchiveService.getVideoArchive();
if (archives) {
this.setState({
archives,
});
}
}
onSubmit = async (e: any) => {
e.preventDefault();
const { url } = this.state;
this.setState({ error: undefined });
try {
await ArchiveService.postVideoArchive({ url });
} catch (e) {
this.setState({ error: 'Invalid URL' });
return;
}
this.setState({ url: '' });
this.loadArchives();
};
renderForm() {
const { error, url } = this.state;
return (
<form className="flex flex--v-center" onSubmit={this.onSubmit}>
<input
className="input video-archive__text-input"
placeholder="Enter Youtube URL or ID..."
value={url}
onChange={e => this.setState({ url: e.target.value })}
/>
<input type="submit" className="button button--primary" style={{ marginLeft: '10px' }} />
{error && (
<span className="color__red" style={{ marginLeft: '5px' }}>
{error}
</span>
)}
</form>
);
}
renderArchives() {
return this.state.archives.map((v, k) => (
<div key={k} className="card video-archive__card test">
<EmbeddedYoutube id={v.youtube_id} />
<div style={{ padding: '10px 10px 0' }}>
<h3 style={{ margin: '0 0 5px' }} className="ellipsis" title={v.title}>
{v.title}
</h3>
<div>
<span className="color__red">{v.uploaded_by}</span> -{' '}
<small>{DateTime.fromISO(v.created_at).toLocaleString()}</small>
</div>
</div>
</div>
));
}
render() {
const { claims } = this.props.appStore;
return (
<div className="content">
{claims && claims.permissions > Permissions.User && this.renderForm()}
<div className="archive-grid">{this.renderArchives()}</div>
</div>
);
}
}

13
client/app/scss/grid.scss Normal file
View File

@@ -0,0 +1,13 @@
.archive-grid {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
@include smallScreen() {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
&__item {
height: 300px;
}
}

View File

@@ -1,5 +1,14 @@
@import '~normalize.css/normalize.css';
@import '~font-awesome/css/font-awesome.css';
@import '~nprogress/nprogress.css';
@import './mixins.scss';
@import './variables.scss';
@import './style.scss';
@import './button.scss';
@import './card.scss';
@import './input.scss';
@import './grid.scss';
@import './nprogress.scss';

View File

@@ -0,0 +1,14 @@
.input {
line-height: 38px;
border-radius: 3px;
border: 1px solid $lightGray;
padding-left: 5px;
padding-right: 5px;
height: 38px;
background: $gray5;
color: $white;
&::placeholder {
font-size: 14px;
}
}

View File

@@ -3,3 +3,9 @@
@content;
}
}
@mixin smallScreen {
@media only screen and (max-width: 768px) {
@content;
}
}

View File

@@ -0,0 +1,5 @@
// override nprogress css
#nprogress .bar {
background: $red;
}

View File

@@ -1,6 +1,3 @@
@import './variables.scss';
@import './mixins.scss';
html {
font-family: 'Roboto', sans-serif;
}
@@ -29,13 +26,16 @@ i {
body {
background-color: $gray1;
color: $white;
// padding-left: $navbarWidth;
}
.flex {
display: flex;
}
.flex--v-center {
align-items: center;
}
.content {
padding: 20px;
@include tinyScreen {
@@ -43,16 +43,6 @@ body {
}
}
.input {
border-radius: 3px;
border: 1px solid $lightGray;
padding-left: 5px;
padding-right: 5px;
height: 30px;
background: $gray5;
color: $white;
}
.column {
flex: 1;
@@ -65,3 +55,19 @@ body {
word-break: break-word;
word-wrap: break-word;
}
.color {
&__red {
color: $red;
}
&__primary {
color: $primaryBlue;
}
}
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

View File

@@ -2,6 +2,7 @@ $navbarWidth: 200px;
// colors
$primaryBlue: #7289da;
$red: #dd6e6e;
$white: darken(white, 20%);
$gray1: #2e3136;

View File

@@ -0,0 +1,14 @@
import * as _ from 'lodash';
import { IVideoArchive } from '../model';
import { axios } from './axios.service';
export class ArchiveService {
public static async getVideoArchive(): Promise<IVideoArchive[]> {
const data = (await axios.get('/api/video-archive')).data.data;
return _.orderBy(data, 'created_at', ['desc']);
}
public static postVideoArchive(data: any): Promise<any> {
return axios.post('/api/video-archive', data);
}
}

View File

@@ -1,14 +1,29 @@
import ax from 'axios';
import nprogress from 'nprogress';
nprogress.configure({ showSpinner: false });
export const axios = ax.create();
axios.interceptors.request.use(
config => {
nprogress.start();
// Do something before request is sent
return config;
},
function(error) {
error => {
// Do something with request error
return Promise.reject(error);
},
);
axios.interceptors.response.use(
config => {
nprogress.done();
return config;
},
error => {
nprogress.done();
return Promise.reject(error);
},
);

View File

@@ -8,7 +8,7 @@ const getScreenWidth = () => {
};
const isMobileScreen = () => {
return getScreenWidth() < 520;
return getScreenWidth() < 768;
};
export const Util = {

View File

@@ -2,8 +2,6 @@ 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';