mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 05:13:32 +00:00
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:
+19
-16
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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' />
|
||||
)}
|
||||
|
||||
@@ -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' }}
|
||||
|
||||
Reference in New Issue
Block a user