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:
@@ -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>
|
||||
|
||||
15
client/app/components/embedded-youtube/embedded-youtube.scss
Normal file
15
client/app/components/embedded-youtube/embedded-youtube.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
16
client/app/components/embedded-youtube/embedded-youtube.tsx
Normal file
16
client/app/components/embedded-youtube/embedded-youtube.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 20px;
|
||||
z-index: 100;
|
||||
|
||||
&__title-container {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './embedded-youtube/embedded-youtube';
|
||||
export * from './header/header';
|
||||
export * from './navbar/navbar';
|
||||
export * from './sound-list/sound-list';
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
overflow-y: auto;
|
||||
padding-bottom: 10px;
|
||||
transition: 0.2s left ease-in-out;
|
||||
z-index: 100;
|
||||
|
||||
&--open {
|
||||
left: 0;
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './claims';
|
||||
export * from './permissions';
|
||||
export * from './video-archive';
|
||||
|
||||
13
client/app/model/video-archive.ts
Normal file
13
client/app/model/video-archive.ts
Normal 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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
14
client/app/pages/video-archive/video-archive.scss
Normal file
14
client/app/pages/video-archive/video-archive.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
106
client/app/pages/video-archive/video-archive.tsx
Normal file
106
client/app/pages/video-archive/video-archive.tsx
Normal 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
13
client/app/scss/grid.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
14
client/app/scss/input.scss
Normal file
14
client/app/scss/input.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,9 @@
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin smallScreen {
|
||||
@media only screen and (max-width: 768px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
5
client/app/scss/nprogress.scss
Normal file
5
client/app/scss/nprogress.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
// override nprogress css
|
||||
|
||||
#nprogress .bar {
|
||||
background: $red;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ $navbarWidth: 200px;
|
||||
|
||||
// colors
|
||||
$primaryBlue: #7289da;
|
||||
$red: #dd6e6e;
|
||||
$white: darken(white, 20%);
|
||||
|
||||
$gray1: #2e3136;
|
||||
|
||||
14
client/app/services/archive.service.ts
Normal file
14
client/app/services/archive.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ const getScreenWidth = () => {
|
||||
};
|
||||
|
||||
const isMobileScreen = () => {
|
||||
return getScreenWidth() < 520;
|
||||
return getScreenWidth() < 768;
|
||||
};
|
||||
|
||||
export const Util = {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user