Compare commits
3 Commits
b5a0d8bda2
...
705b1f5e12
Author | SHA1 | Date |
---|---|---|
Martyn | 705b1f5e12 | |
Martyn | f721c91690 | |
Martyn | f49d3ec9ab |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -25,3 +25,18 @@
|
||||||
.App-menu {
|
.App-menu {
|
||||||
max-width: 98%;
|
max-width: 98%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Footer {
|
||||||
|
max-width: 98%;
|
||||||
|
background-color: #6207a4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Footer a {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.FooterCell {
|
||||||
|
border: none !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import logo from './../../logo.svg';
|
import logo from './../../logo.svg';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import Container from '@material-ui/core/Container';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import { Button, Paper } from '@material-ui/core';
|
import { Button, Paper, Container, AppBar, Table, TableBody, TableCell, TableContainer, TableRow } from '@material-ui/core';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const client_id = "sau3e70wvs369jw1u25ex8g3cve599"
|
const client_id = "sau3e70wvs369jw1u25ex8g3cve599"
|
||||||
|
@ -44,6 +43,22 @@ function App() {
|
||||||
<Typography component="p" gutterBottom>
|
<Typography component="p" gutterBottom>
|
||||||
...Coming "Soon"(tm)
|
...Coming "Soon"(tm)
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<AppBar position="static" className="Footer">
|
||||||
|
<TableContainer component={Container}>
|
||||||
|
<Table><TableBody>
|
||||||
|
<TableRow className="FooterRow">
|
||||||
|
<TableCell className="FooterCell"><a href="https://discord.gg/sVgZeRt">Discord</a></TableCell>
|
||||||
|
<TableCell className="FooterCell"><a href="https://git.martyn.berlin/martyn/twitchsingstools">Source Code</a></TableCell>
|
||||||
|
<TableCell className="FooterCell"><a href="https://twitch.tv/iMartynOnTwitch">Martyn's Twitch</a></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow className="FooterRow">
|
||||||
|
<TableCell className="FooterCell">Announcements, Support, Optional notifications</TableCell>
|
||||||
|
<TableCell className="FooterCell">Geek out at the really hacky source code from an SRE type person</TableCell>
|
||||||
|
<TableCell className="FooterCell">Streaming and singing is my hobby, not my job, so only if you want to...</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody></Table>
|
||||||
|
</TableContainer>
|
||||||
|
</AppBar>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,3 +26,18 @@
|
||||||
.App-menu {
|
.App-menu {
|
||||||
max-width: 98%;
|
max-width: 98%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Footer {
|
||||||
|
max-width: 98%;
|
||||||
|
background-color: #6207a4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Footer a {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.FooterCell {
|
||||||
|
border: none !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import Typography from '@material-ui/core/Typography';
|
||||||
import TopTenSongs from "../TopTenSongs/TopTenSongs";
|
import TopTenSongs from "../TopTenSongs/TopTenSongs";
|
||||||
import TopTenSingers from "../TopTenSingers/TopTenSingers";
|
import TopTenSingers from "../TopTenSingers/TopTenSingers";
|
||||||
import DuetData from "../DuetData/DuetData";
|
import DuetData from "../DuetData/DuetData";
|
||||||
|
import CacheDeets from "../CacheDeets/CacheDeets";
|
||||||
|
import BotDeets from "../BotDeets/BotDeets";
|
||||||
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@material-ui/core';
|
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@material-ui/core';
|
||||||
import ProblemContainer from "../ProblemContainer/ProblemContainer";
|
import ProblemContainer from "../ProblemContainer/ProblemContainer";
|
||||||
|
|
||||||
|
@ -40,82 +42,159 @@ function a11yProps(index) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppAdmin() {
|
class AppAdmin extends React.Component {
|
||||||
const [page, setPage] = React.useState(0);
|
constructor(props) {
|
||||||
if (
|
super(props);
|
||||||
(window.location.href.split("/").length < 6) ||
|
this.setPage = this.setPage.bind(this);
|
||||||
(window.location.href.split("/")[3].search(/^admin/) < 0) ||
|
this.reloadSongComponent = this.reloadSongComponent.bind(this);
|
||||||
(window.location.href.split("/")[5].length !== 48)
|
this.reloadSingersComponent = this.reloadSingersComponent.bind(this);
|
||||||
) {
|
this.reloadDuetComponent = this.reloadDuetComponent.bind(this);
|
||||||
|
this.state = {
|
||||||
|
page: 0,
|
||||||
|
reloadSongComponent: false,
|
||||||
|
reloadSingersComponent: false,
|
||||||
|
reloadDuetComponent: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setPage(pageNum) {
|
||||||
|
let newState = this.state;
|
||||||
|
newState.page = pageNum;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSongComponent(components) {
|
||||||
|
let newState = this.state;
|
||||||
|
newState.reloadComponents = components;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSingersComponent(components) {
|
||||||
|
let newState = this.state;
|
||||||
|
newState.reloadComponents = components;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDuetComponent(components) {
|
||||||
|
let newState = this.state;
|
||||||
|
newState.reloadDuetComponent = components;
|
||||||
|
console.log("I'm trying!")
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const page = this.state.page
|
||||||
|
if (
|
||||||
|
(window.location.href.split("/").length < 6) ||
|
||||||
|
(window.location.href.split("/")[3].search(/^admin/) < 0) ||
|
||||||
|
(window.location.href.split("/")[5].length !== 48)
|
||||||
|
) {
|
||||||
|
if (window.location.href.split("/")[2] !== "192.168.1.111:3000") {
|
||||||
|
return (
|
||||||
|
<ProblemContainer />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelName = window.location.href.split("/")[4]
|
||||||
|
const adminToken = window.location.href.split("/")[5]
|
||||||
|
const csvURL = "/csv/"+ channelName + "/" + adminToken
|
||||||
|
const tsvURL = "/tsv/"+ channelName + "/" + adminToken
|
||||||
|
const setPage = this.setPage
|
||||||
|
const reloadDuetComponent = this.state.reloadDuetComponent
|
||||||
|
const reloadSongComponent = this.state.reloadSongComponent
|
||||||
|
const reloadSingersComponent = this.state.reloadSingersComponent
|
||||||
|
|
||||||
|
function handleChange(event, newValue) {
|
||||||
|
setPage(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProblemContainer />
|
<Container className="App">
|
||||||
)
|
<Paper>
|
||||||
}
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
|
Twitch Sings Tools
|
||||||
|
</Typography>
|
||||||
|
<AppBar position="static" className="App-menu">
|
||||||
|
<Tabs value={page} onChange={handleChange} aria-label="simple tabs example">
|
||||||
|
<Tab label="Top Songs" {...a11yProps(0)}/>
|
||||||
|
<Tab label="Top Singers" {...a11yProps(1)}/>
|
||||||
|
<Tab label="Data" {...a11yProps(2)}/>
|
||||||
|
<Tab label="Export" {...a11yProps(3)}/>
|
||||||
|
<Tab label="Cache Details" {...a11yProps(4)}/>
|
||||||
|
<Tab label="Chatbot" {...a11yProps(4)}/>
|
||||||
|
</Tabs>
|
||||||
|
</AppBar>
|
||||||
|
<TabPanel value={page} index={0}>
|
||||||
|
<TopTenSongs reloadSongComponent={reloadSongComponent}
|
||||||
|
onReloadedChange={this.reloadSongComponent}/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={page} index={1}>
|
||||||
|
<TopTenSingers reloadSingersComponent={reloadSingersComponent}
|
||||||
|
onReloadedChange={this.reloadSingersComponent}/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={page} index={2}>
|
||||||
|
<DuetData reloadDuetComponent={reloadDuetComponent}
|
||||||
|
onReloadedChange={this.reloadDuetComponent}/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={page} index={3}>
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom>
|
||||||
|
Download your data as :
|
||||||
|
</Typography>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TSV</TableCell>
|
||||||
|
<TableCell>CSV</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell><a href={tsvURL}>here</a></TableCell>
|
||||||
|
<TableCell><a href={csvURL}>here</a></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom>
|
||||||
|
Note: Excel is not very good at handling CSV format it seems...
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" gutterBottom>It is important to "Import Data" not "Open" the csv in many cases (8 year old discussion of this behaviour <a href="https://superuser.com/questions/407082/easiest-way-to-open-csv-with-commas-in-excel">here</a>) - from that post the instructions are :</Typography>
|
||||||
|
<Typography component="q" gutterBottom>In Excel, DATA tab, in the Get External Data subsection, click "From Text" and import your CSV in the Wizard.</Typography>
|
||||||
|
<Typography component="p" gutterBottom>LibreOffice calc kinda just works...just sayin' ;-)</Typography>
|
||||||
|
|
||||||
const channelName = window.location.href.split("/")[4]
|
</TabPanel>
|
||||||
const adminToken = window.location.href.split("/")[5]
|
<TabPanel value={page} index={4}>
|
||||||
const csvURL = "/csv/"+ channelName + "/" + adminToken
|
<CacheDeets
|
||||||
const tsvURL = "/tsv/"+ channelName + "/" + adminToken
|
onReloadSongsChange={this.reloadSongComponent}
|
||||||
|
onReloadSingersChange={this.reloadSingersComponent}
|
||||||
function handleChange(event, newValue) {
|
onReloadDuetChange={this.reloadDuetComponent}
|
||||||
setPage(newValue);
|
/>
|
||||||
}
|
</TabPanel>
|
||||||
|
<TabPanel value={page} index={5}>
|
||||||
return (
|
<BotDeets />
|
||||||
<Container className="App">
|
</TabPanel>
|
||||||
<Paper>
|
<AppBar position="static" className="Footer">
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<TableContainer component={Container}>
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Table><TableBody>
|
||||||
Twitch Sings Tools
|
<TableRow className="FooterRow">
|
||||||
</Typography>
|
<TableCell className="FooterCell"><a href="https://discord.gg/sVgZeRt">Discord</a></TableCell>
|
||||||
<AppBar position="static" className="App-menu">
|
<TableCell className="FooterCell"><a href="https://git.martyn.berlin/martyn/twitchsingstools">Source Code</a></TableCell>
|
||||||
<Tabs value={page} onChange={handleChange} aria-label="simple tabs example">
|
<TableCell className="FooterCell"><a href="https://twitch.tv/iMartynOnTwitch">Martyn's Twitch</a></TableCell>
|
||||||
<Tab label="Top Songs" {...a11yProps(0)}/>
|
|
||||||
<Tab label="Top Singers" {...a11yProps(1)}/>
|
|
||||||
<Tab label="Data" {...a11yProps(2)}/>
|
|
||||||
<Tab label="Export" {...a11yProps(3)}/>
|
|
||||||
</Tabs>
|
|
||||||
</AppBar>
|
|
||||||
<TabPanel value={page} index={0}>
|
|
||||||
<TopTenSongs />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={page} index={1}>
|
|
||||||
<TopTenSingers />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={page} index={2}>
|
|
||||||
<DuetData />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={page} index={3}>
|
|
||||||
<Typography variant="h5" component="h2" gutterBottom>
|
|
||||||
Download your data as :
|
|
||||||
</Typography>
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>TSV</TableCell>
|
|
||||||
<TableCell>CSV</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
<TableRow className="FooterRow">
|
||||||
<TableBody>
|
<TableCell className="FooterCell">Announcements, Support, Optional notifications</TableCell>
|
||||||
<TableRow>
|
<TableCell className="FooterCell">Geek out at the really hacky source code from an SRE type person</TableCell>
|
||||||
<TableCell><a href={tsvURL}>here</a></TableCell>
|
<TableCell className="FooterCell">Streaming and singing is my hobby, not my job, so only if you want to...</TableCell>
|
||||||
<TableCell><a href={csvURL}>here</a></TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody></Table>
|
||||||
</Table>
|
</TableContainer>
|
||||||
</TableContainer>
|
</AppBar>
|
||||||
<Typography variant="h5" component="h2" gutterBottom>
|
</Paper>
|
||||||
Note: Excel is not very good at handling CSV format it seems...
|
</Container>
|
||||||
</Typography>
|
);
|
||||||
<Typography component="p" gutterBottom>It is important to "Import Data" not "Open" the csv in many cases (8 year old discussion of this behaviour <a href="https://superuser.com/questions/407082/easiest-way-to-open-csv-with-commas-in-excel">here</a>) - from that post the instructions are :</Typography>
|
}
|
||||||
<Typography component="q" gutterBottom>In Excel, DATA tab, in the Get External Data subsection, click "From Text" and import your CSV in the Wizard.</Typography>
|
|
||||||
<Typography component="p" gutterBottom>LibreOffice calc kinda just works...just sayin' ;-)</Typography>
|
|
||||||
|
|
||||||
</TabPanel>
|
|
||||||
</Paper>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppAdmin;
|
export default AppAdmin;
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Container, Typography, Button } from '@material-ui/core';
|
||||||
|
|
||||||
|
const channelName = window.location.href.split("/")[4]
|
||||||
|
const adminToken = window.location.href.split("/")[5]
|
||||||
|
const dataURL = "/botdeets/"+ channelName + "/" + adminToken
|
||||||
|
|
||||||
|
function BotDeets() {
|
||||||
|
|
||||||
|
const [botState, setBotState] = React.useState({loading: true, botData: []})
|
||||||
|
useEffect(() => {
|
||||||
|
// We should only fetch once!
|
||||||
|
if (botState.loading) {
|
||||||
|
fetch(dataURL).then((res) => res.json().then((data)=>{
|
||||||
|
setBotState({botData: data, loading: false })
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [setBotState, botState.loading]);
|
||||||
|
|
||||||
|
if (botState.loading) {
|
||||||
|
return <p>Sorry, still loading...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
let dd = botState.botData
|
||||||
|
if (dd.hasleft) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom>
|
||||||
|
The chatbot is currently <b>not</b> in your channel. To change the settings, invite it to your channel using the button below :
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="p">
|
||||||
|
You really don't wanna do that, because it's not ready yet.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="p">
|
||||||
|
<Button variant="contained" color="primary">Join my channel!</Button>
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom>
|
||||||
|
Coming soon! Bot control panel!
|
||||||
|
</Typography>
|
||||||
|
<Typography component="tt" gutterBottom>
|
||||||
|
{dd}
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BotDeets;
|
|
@ -0,0 +1,81 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Container, Typography, Button } from '@material-ui/core';
|
||||||
|
|
||||||
|
const channelName = window.location.href.split("/")[4]
|
||||||
|
const adminToken = window.location.href.split("/")[5]
|
||||||
|
const dataURL = "/cachedeets/"+ channelName + "/" + adminToken
|
||||||
|
const forceURL = "/force/"+ channelName + "/" + adminToken
|
||||||
|
|
||||||
|
class CacheDeets extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
cacheData: [],
|
||||||
|
loading: true
|
||||||
|
};
|
||||||
|
this.setCacheData = this.setCacheData.bind(this);
|
||||||
|
this.setLoading = this.setLoading.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCacheData(data) {
|
||||||
|
let newState = this.state
|
||||||
|
newState.cacheData = data
|
||||||
|
this.setState(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(data) {
|
||||||
|
let newState = this.state
|
||||||
|
newState.loading = data
|
||||||
|
this.setState(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// We should only fetch once!
|
||||||
|
if (this.state.loading) {
|
||||||
|
let actualURL = dataURL
|
||||||
|
if (window.location.href.split("/")[2] === "192.168.1.111:3000") {
|
||||||
|
//Frontend dev mode only
|
||||||
|
actualURL = "/sampleData/cache.json"
|
||||||
|
}
|
||||||
|
fetch(actualURL).then((res) => res.json().then((data)=>{
|
||||||
|
this.setLoading(false)
|
||||||
|
this.setCacheData(data)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const loading = this.state.loading
|
||||||
|
const setLoading = this.setLoading
|
||||||
|
const cacheData = this.state.cacheData
|
||||||
|
|
||||||
|
function handleClick(e) {
|
||||||
|
setLoading(true)
|
||||||
|
fetch(forceURL).then((data)=>{
|
||||||
|
setLoading(false)
|
||||||
|
window.location.reload()
|
||||||
|
})
|
||||||
|
console.log('The link was clicked.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <p>Sorry, still loading...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
let dd = cacheData
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom>
|
||||||
|
Server has cached data from <b>{dd.SongCount} published</b> performances
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" gutterBottom>
|
||||||
|
Cache data is from about {dd.AgeStr} - it automatically gets updated if it's older than an hour. If you're convinced using Martyn's bandwidth to refresh the cache earlier than that is worth it, here's the button below :
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" color="primary" onClick={handleClick}>Force refresh cache</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CacheDeets;
|
Loading…
Reference in New Issue