diff --git a/app/components/Home.tsx b/app/components/Home.tsx index 5b7f88e..fafb58c 100644 --- a/app/components/Home.tsx +++ b/app/components/Home.tsx @@ -1,19 +1,14 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import { makeStyles } from '@material-ui/core/styles'; +import React, { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { ThemeProvider } from '@material-ui/core/styles'; import { Typography } from '@material-ui/core'; import Box from '@material-ui/core/Box'; +import { ipcRenderer } from 'electron'; import WorkflowManager from '../features/workflow/WorkflowManager'; -import { selectWorkflow } from '../features/workflow/workflowSlice'; +import { selectWorkflow, setTheme } from '../features/workflow/workflowSlice'; import MatchesPanel from '../features/matches/MatchesPanel'; import SplitterPanel from '../features/splitter/SplitterPanel'; - -const useStyles = makeStyles({ - steps: { - height: '100%', - maxHeight: 'fill-available', - }, -}); +import getTheme from '../utils/Theme'; function handleWorkflow(step: number) { switch (step) { @@ -31,15 +26,21 @@ function handleWorkflow(step: number) { } export default function Home(): JSX.Element { - const { currentStep } = useSelector(selectWorkflow); - const classes = useStyles(); + const dispatch = useDispatch(); + const { currentStep, theme } = useSelector(selectWorkflow); + + useEffect(() => { + ipcRenderer.on('set-theme', (_, arg: string) => { + dispatch(setTheme(arg[0])); + }); + }, []); return ( - <> +
{handleWorkflow(currentStep)} - +
); } diff --git a/app/features/matches/matchesSlice.tsx b/app/features/matches/matchesSlice.tsx index b605f08..299219f 100644 --- a/app/features/matches/matchesSlice.tsx +++ b/app/features/matches/matchesSlice.tsx @@ -31,6 +31,9 @@ export const getMatches = createAsyncThunk( (match) => { return compLevelSort.indexOf(match.comp_level); }, + (match) => { + return match.match_number; + }, ]); const matches = sortedMatches.map((tbaMatch: TbaMatch) => @@ -114,7 +117,7 @@ const matchesSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(getMatches.pending, (state, action) => { + builder.addCase(getMatches.pending, (state) => { state.loading = true; }); builder.addCase(getMatches.fulfilled, (state, action) => { diff --git a/app/features/workflow/workflowSlice.tsx b/app/features/workflow/workflowSlice.tsx index 5fc1518..b12a1e5 100644 --- a/app/features/workflow/workflowSlice.tsx +++ b/app/features/workflow/workflowSlice.tsx @@ -1,15 +1,17 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; // eslint-disable-next-line import/no-cycle -import { AppThunk, RootState } from '../../store'; +import { RootState } from '../../store'; export type WorkflowState = { steps: string[]; currentStep: number; + theme: string; }; const initialState: WorkflowState = { - steps: ['Matches', 'Split', 'Upload'], + steps: ['Matches', 'Split'], currentStep: 0, + theme: 'light', }; const workflowSlice = createSlice({ @@ -18,17 +20,20 @@ const workflowSlice = createSlice({ reducers: { nextStep: (state) => { if (state.currentStep < state.steps.length) { - state.currentStep++; + state.currentStep += 1; } }, prevStep: (state) => { if (state.currentStep > 0) { - state.currentStep--; + state.currentStep -= 1; } }, reset: (state) => { state.currentStep = 0; }, + setTheme: (state, action: PayloadAction) => { + state.theme = action.payload; + }, }, }); @@ -36,6 +41,6 @@ export const selectWorkflow = (state: RootState) => { return state.workflow; }; -export const { nextStep, prevStep, reset } = workflowSlice.actions; +export const { nextStep, prevStep, reset, setTheme } = workflowSlice.actions; export default workflowSlice.reducer; diff --git a/app/main.dev.ts b/app/main.dev.ts index b192a18..4992124 100644 --- a/app/main.dev.ts +++ b/app/main.dev.ts @@ -14,7 +14,7 @@ import path from 'path'; import { app, BrowserWindow, ipcMain } from 'electron'; import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; -import MenuBuilder from './menu'; +import MenuBuilder from './menu/menu'; import { split } from './ffmpeg/ffmpegCommands'; import { SplitDetails } from './features/splitter/splitterSlice'; @@ -131,10 +131,8 @@ app.on('activate', () => { }); ipcMain.on('split', async (event, allDetails: SplitDetails[]) => { - for (const details of allDetails) { - await split(event, details).catch((error) => - event.reply('split-error', error) - ); - } + allDetails.forEach((d) => + split(event, d).catch((error) => event.reply('split-error', error)) + ); event.reply('split-end', 'Done'); }); diff --git a/app/menu.ts b/app/menu/menu.ts similarity index 65% rename from app/menu.ts rename to app/menu/menu.ts index 8b23588..ea2caf7 100644 --- a/app/menu.ts +++ b/app/menu/menu.ts @@ -6,30 +6,42 @@ import { MenuItemConstructorOptions, } from 'electron'; +import MenuTemplateBuilder from './menuTemplateBuilder'; + interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { selector?: string; submenu?: DarwinMenuItemConstructorOptions[] | Menu; } +function isDev(): boolean { + return ( + process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true' + ); +} + export default class MenuBuilder { mainWindow: BrowserWindow; + templateBuilder: MenuTemplateBuilder; + constructor(mainWindow: BrowserWindow) { this.mainWindow = mainWindow; + this.templateBuilder = new MenuTemplateBuilder( + mainWindow, + process.platform, + isDev() + ); } buildMenu(): Menu { - if ( - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ) { + if (isDev()) { this.setupDevelopmentEnvironment(); } const template = process.platform === 'darwin' ? this.buildDarwinTemplate() - : this.buildDefaultTemplate(); + : this.templateBuilder.getMenuOptions(); const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); @@ -189,98 +201,4 @@ export default class MenuBuilder { return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]; } - - buildDefaultTemplate() { - const templateDefault = [ - { - label: '&File', - submenu: [ - { - label: '&Open', - accelerator: 'Ctrl+O', - }, - { - label: '&Close', - accelerator: 'Ctrl+W', - click: () => { - this.mainWindow.close(); - }, - }, - ], - }, - { - label: '&View', - submenu: - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ? [ - { - label: '&Reload', - accelerator: 'Ctrl+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle &Full Screen', - accelerator: 'F11', - click: () => { - this.mainWindow.setFullScreen( - !this.mainWindow.isFullScreen() - ); - }, - }, - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ] - : [ - { - label: 'Toggle &Full Screen', - accelerator: 'F11', - click: () => { - this.mainWindow.setFullScreen( - !this.mainWindow.isFullScreen() - ); - }, - }, - ], - }, - { - label: 'Help', - submenu: [ - { - label: 'Learn More', - click() { - shell.openExternal( - 'https://github.com/tytremblay/frc-video-splitter-3' - ); - }, - }, - { - label: 'Documentation', - click() { - shell.openExternal( - 'https://github.com/tytremblay/frc-video-splitter-3/blob/master/README.md' - ); - }, - }, - { - label: 'Submit Issues', - click() { - shell.openExternal( - 'https://github.com/tytremblay/frc-video-splitter-3/issues' - ); - }, - }, - ], - }, - ]; - - return templateDefault; - } } diff --git a/app/menu/menuTemplateBuilder.ts b/app/menu/menuTemplateBuilder.ts new file mode 100644 index 0000000..26fc6bb --- /dev/null +++ b/app/menu/menuTemplateBuilder.ts @@ -0,0 +1,127 @@ +import { shell, BrowserWindow, MenuItemConstructorOptions } from 'electron'; + +/* eslint class-methods-use-this: ["error", { "exceptMethods": ["getHelpMenu"] }] */ + +export default class MenuTemplateBuilder { + platform: string; + + isDev: boolean; + + mainWindow: BrowserWindow; + + constructor(mainWindow: BrowserWindow, platform: string, isDev: boolean) { + this.platform = platform; + this.isDev = isDev; + this.mainWindow = mainWindow; + } + + public getMenuOptions(): MenuItemConstructorOptions[] { + const file = this.getFileMenu(); + const view = this.getViewMenu(); + const help = this.getHelpMenu(); + + return [file, view, help]; + } + + private getFileMenu(): MenuItemConstructorOptions { + return { + label: '&File', + submenu: [ + { + label: '&Open', + accelerator: 'Ctrl+O', + }, + { + label: '&Close', + accelerator: 'Ctrl+W', + click: () => { + this.mainWindow.close(); + }, + }, + ], + }; + } + + private getViewMenu(): MenuItemConstructorOptions { + const prodOptions: MenuItemConstructorOptions[] = [ + { + label: 'Toggle &Full Screen', + accelerator: 'F11', + click: () => { + this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); + }, + }, + { + label: '&Theme', + submenu: [ + { + label: '&Light', + click(_, focusedWindow) { + focusedWindow?.webContents.send('set-theme', ['light']); + }, + }, + { + label: '&Dark', + click(_, focusedWindow) { + focusedWindow?.webContents.send('set-theme', ['dark']); + }, + }, + ], + }, + ]; + + const devOptions: MenuItemConstructorOptions[] = [ + { + label: '&Reload', + accelerator: 'Ctrl+R', + click: () => { + this.mainWindow.webContents.reload(); + }, + }, + { + label: 'Toggle &Developer Tools', + accelerator: 'Alt+Ctrl+I', + click: () => { + this.mainWindow.webContents.toggleDevTools(); + }, + }, + ]; + + return { + label: '&View', + submenu: this.isDev ? prodOptions.concat(devOptions) : prodOptions, + }; + } + + private getHelpMenu(): MenuItemConstructorOptions { + return { + label: 'Help', + submenu: [ + { + label: 'Learn More', + click() { + shell.openExternal( + 'https://github.com/tytremblay/frc-video-splitter-3' + ); + }, + }, + { + label: 'Documentation', + click() { + shell.openExternal( + 'https://github.com/tytremblay/frc-video-splitter-3/blob/master/README.md' + ); + }, + }, + { + label: 'Submit Issues', + click() { + shell.openExternal( + 'https://github.com/tytremblay/frc-video-splitter-3/issues' + ); + }, + }, + ], + }; + } +} diff --git a/app/package.json b/app/package.json index 279997d..da25fca 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "frc-video-splitter-3", "productName": "FRC Video Splitter", - "version": "0.1.0", + "version": "0.1.1", "description": "Automatically split FRC match videos from large recordings.", "main": "./main.prod.js", "author": { diff --git a/app/utils/Theme.tsx b/app/utils/Theme.tsx new file mode 100644 index 0000000..e47a974 --- /dev/null +++ b/app/utils/Theme.tsx @@ -0,0 +1,20 @@ +import { createMuiTheme, Theme } from '@material-ui/core/styles'; + +const darkTheme: Theme = createMuiTheme({ + palette: { + type: 'dark', + }, +}); + +const lightTheme: Theme = createMuiTheme(); + +export default function getTheme(theme: string): Theme { + switch (theme) { + case 'light': + return lightTheme; + case 'dark': + return darkTheme; + default: + return lightTheme; + } +} diff --git a/package.json b/package.json index 7076757..e767a28 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frc-video-splitter-3", "productName": "FRC Video Splitter", - "version": "0.1.0", + "version": "0.1.1", "description": "Automatically split FRC match videos from large recordings.", "scripts": { "build": "yarn build-main && yarn build-renderer",