Feat/excel (#45)

* add dependency

adding dependency to parse the excel file in the node side

* import file

ability to import file
- add extension
- safeguard file type and size

* user is able to upload json or excel

* chore: extract logic into self contained function

* associate model with version

* test id generation

* parse excel data

* prevent UI crash on bad data

* extract validation into self contained function

* chore: add more tests for bad data

* config: stop from opening browser on start

* fix comma in sample db

* handle corrupt files

* error boundary around main components

* jsdocs

* increase id length

* parse excel time string

* feat: excel parsing

* validate json db before import

* chore: test event validator

* feat: make function to clean convert strings

* chore: test parser

* chore: jsdocs

* fix: bug on upload

prevent bug where the component would prevent upload of same file twice
This commit is contained in:
Carlos Valente
2021-11-24 22:07:31 +01:00
committed by GitHub
parent bf8170a947
commit 8c740b7111
22 changed files with 1222 additions and 169 deletions
+19 -16
View File
@@ -4,6 +4,7 @@ import './App.css';
import { QueryClient, QueryClientProvider } from 'react-query';
import SocketProvider from 'app/context/socketContext';
import withSocket from 'features/viewers/ViewWrapper';
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
const Editor = lazy(() => import('features/editors/Editor'));
const PresenterView = lazy(() =>
@@ -65,22 +66,24 @@ function App() {
<SocketProvider>
<QueryClientProvider client={queryClient}>
<div className='App'>
<Suspense fallback={null}>
<Switch>
<Route exact path='/' component={SSpeaker} />
<Route exact path='/sm' component={SStageManager} />
<Route exact path='/speaker' component={SSpeaker} />
<Route exact path='/stage' component={SSpeaker} />
<Route exact path='/speakersimple' component={SSpeakerSimple} />
<Route exact path='/editor' component={Editor} />
<Route exact path='/public' component={SPublic} />
<Route exact path='/pip' component={SPip} />
{/* Lower cannot have fallback */}
<Route exact path='/lower' component={SLowerThird} />
{/* Send to default if nothing found */}
<Route component={SSpeaker} />
</Switch>
</Suspense>
<ErrorBoundary>
<Suspense fallback={null}>
<Switch>
<Route exact path='/' component={SSpeaker} />
<Route exact path='/sm' component={SStageManager} />
<Route exact path='/speaker' component={SSpeaker} />
<Route exact path='/stage' component={SSpeaker} />
<Route exact path='/speakersimple' component={SSpeakerSimple} />
<Route exact path='/editor' component={Editor} />
<Route exact path='/public' component={SPublic} />
<Route exact path='/pip' component={SPip} />
{/* Lower cannot have fallback */}
<Route exact path='/lower' component={SLowerThird} />
{/* Send to default if nothing found */}
<Route component={SSpeaker} />
</Switch>
</Suspense>
</ErrorBoundary>
</div>
</QueryClientProvider>
</SocketProvider>
+1 -1
View File
@@ -47,7 +47,7 @@ export const downloadEvents = async () => {
export const uploadEvents = async (file) => {
const formData = new FormData();
formData.append('jsondb', file); // appending file
formData.append('userFile', file); // appending file
await axios
.post(ontimeURL + '/db', formData, {
headers: {
@@ -0,0 +1,35 @@
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Update state so next render shows fallback UI.
return { errorMessage: error.toString() };
}
componentDidCatch(error, info) {
this.setState({
error: error,
errorInfo: info,
});
// TODO: Log the error to an error reporting service
this.logErrorToServices(error.toString(), info.componentStack);
}
// A fake logging service.
logErrorToServices = console.log;
render() {
if (this.state.errorMessage) {
// You can render any custom fallback UI
return <p>:/</p>;
}
return this.props.children;
}
}
export default ErrorBoundary;
+16 -5
View File
@@ -4,6 +4,7 @@ import { useDisclosure } from '@chakra-ui/hooks';
import styles from './Editor.module.css';
import MenuBar from 'features/menu/MenuBar';
import ModalManager from 'features/modals/ModalManager';
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
const EventListWrapper = lazy(() =>
import('features/editors/list/EventListWrapper')
@@ -26,34 +27,44 @@ export default function Editor() {
<div className={styles.mainContainer}>
<Box id='settings' className={styles.settings}>
<MenuBar onOpen={onOpen} />
<ErrorBoundary>
<MenuBar onOpen={onOpen} />
</ErrorBoundary>
</Box>
<Box className={styles.editor}>
<h1>Event List</h1>
<div className={styles.content}>
<EventListWrapper />
<ErrorBoundary>
<EventListWrapper />
</ErrorBoundary>
</div>
</Box>
<Box className={styles.messages}>
<h1>Display Messages</h1>
<div className={styles.content}>
<MessageControl />
<ErrorBoundary>
<MessageControl />
</ErrorBoundary>
</div>
</Box>
<Box className={styles.playback}>
<h1>Timer Control</h1>
<div className={styles.content}>
<PlaybackControl />
<ErrorBoundary>
<PlaybackControl />
</ErrorBoundary>
</div>
</Box>
<Box className={styles.info}>
<h1>Info</h1>
<div className={styles.content}>
<Info />
<ErrorBoundary>
<Info />
</ErrorBoundary>
</div>
</Box>
</div>
@@ -1,5 +1,5 @@
import { useMutation, useQueryClient } from 'react-query';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import {
fetchAllEvents,
requestPatch,
@@ -26,6 +26,7 @@ export default function EventListWrapper() {
EVENTS_TABLE,
fetchAllEvents
);
const [events, setEvents] = useState(null);
const addEvent = useMutation(requestPost, {
// we optimistically update here
@@ -327,11 +328,17 @@ export default function EventListWrapper() {
[data]
);
// Front end should handle bad arguments
useEffect(() => {
if (data == null) return;
setEvents(data.filter((d) => Object.keys(d).length > 0));
}, [data]);
return (
<>
<EventListMenu eventsHandler={eventsHandler} />
{status === 'success' ? (
<EventList events={data} eventsHandler={eventsHandler} />
{status === 'success' && events != null ? (
<EventList events={events} eventsHandler={eventsHandler} />
) : (
<Empty text='Connecting to server' />
)}
+21 -2
View File
@@ -33,14 +33,33 @@ export default function MenuBar(props) {
const handleUpload = (event) => {
const fileUploaded = event.target.files[0];
if (fileUploaded == null) return;
console.log(fileUploaded);
// Limit file size to 1MB
if (fileUploaded.size > 1000000) {
console.log('Error: File size limit (1MB) exceeded');
return;
}
// Check file extension
if (fileUploaded.name.endsWith('.xlsx')) {
console.log('excel file');
} else if (fileUploaded.name.endsWith('.json')) {
console.log('json file');
} else {
console.log('Error: File type unknown');
return;
}
try {
uploaddb.mutate(fileUploaded);
} catch (error) {
console.log(error);
}
// reset input value
hiddenFileInput.current.value = '';
};
const handleIPC = (action) => {
@@ -102,7 +121,7 @@ export default function MenuBar(props) {
style={{ display: 'none' }}
ref={hiddenFileInput}
onChange={handleUpload}
accept='.json'
accept='.json, .xlsx'
/>
<UploadIconBtn
style={{ fontSize: '1.5em' }}