From 48103412463402655928c0958e385359e072fb9b Mon Sep 17 00:00:00 2001 From: Glenn Engel Date: Sat, 18 May 2024 12:33:55 -0700 Subject: [PATCH] Allow finish guide to be turned off. open file explorer. lint fixes. --- native/recorder/index.d.ts | 2 +- native/recorder/src/NdiReader.cpp | 1 + package.json | 1 - src/main/recorder/recorder-main.ts | 4 +- src/main/store/store.ts | 2 +- src/main/util/fileops-handler.ts | 27 +++++ src/main/util/util-preload.ts | 10 ++ src/renderer/components/RGBAImageCanvas.tsx | 33 ++++-- src/renderer/components/SideNavMenu.tsx | 8 +- src/renderer/pages/RecordingLogTable.tsx | 7 +- src/renderer/recorder/RecorderConfig.tsx | 125 +++++++++++++------- src/renderer/recorder/RecorderData.ts | 1 + src/renderer/recorder/RecorderTypes.ts | 1 + src/renderer/util/util.d.ts | 1 + yarn.lock | 5 - 15 files changed, 156 insertions(+), 72 deletions(-) diff --git a/native/recorder/index.d.ts b/native/recorder/index.d.ts index eeb797a..0d28ffa 100644 --- a/native/recorder/index.d.ts +++ b/native/recorder/index.d.ts @@ -1,5 +1,5 @@ import { HandlerResponse } from '../../src/renderer/msgbus/MsgbusTypes'; -import { RecorderMessage } from '../../src/renderer/recorder/RecorderApi'; +import { RecorderMessage } from '../../src/renderer/recorder/RecorderTypes'; /** * This module provides native C++ functionality as a Node.js addon, diff --git a/native/recorder/src/NdiReader.cpp b/native/recorder/src/NdiReader.cpp index 0d6ecb0..38b9913 100644 --- a/native/recorder/src/NdiReader.cpp +++ b/native/recorder/src/NdiReader.cpp @@ -253,6 +253,7 @@ class NdiReader : public VideoReader { }; std::string connect() { + SystemEventQueue::push("NDI", "Searching for NDI sources..."); NDIlib_source_t p_source; CameraInfo foundCamera; std::vector cameras; diff --git a/package.json b/package.json index ae53f9c..07b20d1 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,6 @@ "electron-store": "^8.2.0", "electron-updater": "^6.1.8", "github-markdown-css": "^5.5.1", - "path-browserify": "^1.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^9.0.1", diff --git a/src/main/recorder/recorder-main.ts b/src/main/recorder/recorder-main.ts index c30ed6e..3cf0880 100644 --- a/src/main/recorder/recorder-main.ts +++ b/src/main/recorder/recorder-main.ts @@ -4,11 +4,11 @@ import { setNativeMessageCallback, } from 'crewtimer_video_recorder'; import { msgbus } from '../msgbus/msgbus-util'; +import { setStoredValue } from '../store/store'; import { RecorderMessage, RecorderResponse, -} from '../../renderer/recorder/RecorderApi'; -import { setStoredValue } from '../store/store'; +} from '../../renderer/recorder/RecorderTypes'; app.on('ready', () => { setNativeMessageCallback(({ sender, content }) => { diff --git a/src/main/store/store.ts b/src/main/store/store.ts index f599f85..4555e2d 100644 --- a/src/main/store/store.ts +++ b/src/main/store/store.ts @@ -3,9 +3,9 @@ */ import { ipcMain, IpcMainInvokeEvent } from 'electron'; import Store from 'electron-store'; +import deepequal from 'fast-deep-equal/es6/react'; import { getMainWindow } from '../mainWindow'; import { notifyChange } from '../../renderer/store/StoreUtil'; -import deepequal from 'fast-deep-equal/es6/react'; /** Stored data instance for on-disk storage */ const store = new Store(); diff --git a/src/main/util/fileops-handler.ts b/src/main/util/fileops-handler.ts index 2d04259..bc598b4 100644 --- a/src/main/util/fileops-handler.ts +++ b/src/main/util/fileops-handler.ts @@ -1,6 +1,8 @@ import { BrowserWindow, OpenDialogOptions, dialog, ipcMain } from 'electron'; import { getMainWindow } from '../mainWindow'; +const { exec } = require('child_process'); + const fs = require('fs'); ipcMain.handle('delete-file', async (_event, filename) => { @@ -63,3 +65,28 @@ ipcMain.handle('get-files-in-directory', (_event, dirPath) => { ); }); }); + +function openFileExplorer(folderPath: string) { + const { platform } = process; + let command; + + if (platform === 'win32') { + command = `explorer "${folderPath}"`; + } else if (platform === 'darwin') { + command = `open "${folderPath}"`; + } else if (platform === 'linux') { + command = `xdg-open "${folderPath}"`; + } + + if (command) { + exec(command, (error: any) => { + if (error) { + console.error('Error opening file explorer:', error); + } + }); + } +} + +ipcMain.handle('open-file-explorer', (_event, dirPath) => { + openFileExplorer(dirPath); +}); diff --git a/src/main/util/util-preload.ts b/src/main/util/util-preload.ts index 9972cd4..676024c 100644 --- a/src/main/util/util-preload.ts +++ b/src/main/util/util-preload.ts @@ -54,6 +54,15 @@ export function openDirDialog( }); } +export function openFileExplorer(path: string): Promise { + return new Promise((resolve) => { + ipcRenderer + .invoke('open-file-explorer', path) + .then((result) => resolve(result)) + .catch(() => resolve()); + }); +} + // Function to get the files in a directory and return them as a promise export function getFilesInDirectory(dirPath: string): Promise { // console.log('Executing getFiles in dir preload'); @@ -75,6 +84,7 @@ contextBridge.exposeInMainWorld('Util', { getFilesInDirectory, openFileDialog, openDirDialog, + openFileExplorer, deleteFile, }); diff --git a/src/renderer/components/RGBAImageCanvas.tsx b/src/renderer/components/RGBAImageCanvas.tsx index 1bca365..770a737 100644 --- a/src/renderer/components/RGBAImageCanvas.tsx +++ b/src/renderer/components/RGBAImageCanvas.tsx @@ -4,6 +4,7 @@ import { useFrameGrab, useGuide, useIsRecording, + useRecordingProps, } from '../recorder/RecorderData'; import { requestVideoFrame, @@ -61,6 +62,7 @@ const RGBAImageCanvas: React.FC = ({ divwidth, divheight }) => { let [frame] = useFrameGrab(); const [guide] = useGuide(); const [isRecording] = useIsRecording(); + const [settings] = useRecordingProps(); const canvasRef = useRef(null); if (frame?.data) { @@ -140,19 +142,28 @@ const RGBAImageCanvas: React.FC = ({ divwidth, divheight }) => { scaledWidth, scaledHeight, ); - // Draw the red line - ctx.beginPath(); - ctx.moveTo(offsetX + scaledWidth / 2 + guide.pt1 * scale, 0); // Horizontal center + N pixels offset, scaled - ctx.lineTo( - offsetX + scaledWidth / 2 + guide.pt2 * scale, - offsetY + scaledHeight, - ); - ctx.strokeStyle = 'red'; - ctx.lineWidth = 1; // Line width can be adjusted as needed - ctx.stroke(); + if (settings.showFinishGuide) { + // Draw the red line + ctx.beginPath(); + ctx.moveTo(offsetX + scaledWidth / 2 + guide.pt1 * scale, 0); // Horizontal center + N pixels offset, scaled + ctx.lineTo( + offsetX + scaledWidth / 2 + guide.pt2 * scale, + offsetY + scaledHeight, + ); + ctx.strokeStyle = 'red'; + ctx.lineWidth = 1; // Line width can be adjusted as needed + ctx.stroke(); + } drawIcon(ctx, parentWidth, scaledHeight, isRecording); - }, [frame, divwidth, divheight, guide, isRecording]); + }, [ + frame, + divwidth, + divheight, + guide, + isRecording, + settings.showFinishGuide, + ]); return ( diff --git a/src/renderer/components/SideNavMenu.tsx b/src/renderer/components/SideNavMenu.tsx index f6be22f..abef6a0 100644 --- a/src/renderer/components/SideNavMenu.tsx +++ b/src/renderer/components/SideNavMenu.tsx @@ -1,8 +1,8 @@ import ListItem from '@mui/material/ListItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; -import HomeIcon from '@mui/icons-material/Home'; -import HistoryIcon from '@mui/icons-material/History'; +import HomeIcon from '@mui/icons-material/Settings'; +import SettingsIcon from '@mui/icons-material/History'; import VideocamOutlinedIcon from '@mui/icons-material/VideocamOutlined'; import { List } from '@mui/material'; import { useNavigate } from 'react-router-dom'; @@ -15,7 +15,7 @@ function SideNavMenu() { navigate('/home')}> - + navigate('/video')}> @@ -25,7 +25,7 @@ function SideNavMenu() { navigate('/log')}> - + diff --git a/src/renderer/pages/RecordingLogTable.tsx b/src/renderer/pages/RecordingLogTable.tsx index c93df3d..09e0d05 100644 --- a/src/renderer/pages/RecordingLogTable.tsx +++ b/src/renderer/pages/RecordingLogTable.tsx @@ -8,7 +8,8 @@ import { TableRow, Paper, } from '@mui/material'; -import { RecordingLogEntry, queryRecordingLog } from '../recorder/RecorderApi'; +import { queryRecordingLog } from '../recorder/RecorderApi'; +import { RecordingLogEntry } from '../recorder/RecorderTypes'; const formatTime = (tsMilli: number): string => { const date = new Date(tsMilli); @@ -34,12 +35,12 @@ const RecordingLogTable: React.FC = () => { Time - Sub System + System Message - {entries.map((entry) => ( + {entries.reverse().map((entry) => ( {formatTime(entry.tsMilli)} {entry.subsystem} diff --git a/src/renderer/recorder/RecorderConfig.tsx b/src/renderer/recorder/RecorderConfig.tsx index 52e3aff..f04ffb3 100644 --- a/src/renderer/recorder/RecorderConfig.tsx +++ b/src/renderer/recorder/RecorderConfig.tsx @@ -1,5 +1,14 @@ import React, { useEffect } from 'react'; -import { TextField, Typography, Grid, MenuItem } from '@mui/material'; +import { + TextField, + Typography, + Grid, + MenuItem, + Checkbox, + FormControlLabel, + Tooltip, + IconButton, +} from '@mui/material'; import { UseDatum } from 'react-usedatum'; import { queryCameraList } from './RecorderApi'; import { @@ -9,8 +18,9 @@ import { } from './RecorderData'; import { FullSizeWindow } from '../components/FullSizeWindow'; import RGBAImageCanvas from '../components/RGBAImageCanvas'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; -const { openDirDialog } = window.Util; +const { openDirDialog, openFileExplorer } = window.Util; const RecordingError = () => { const [recordingStatus] = useRecordingStatus(); @@ -53,7 +63,7 @@ const RecorderConfig: React.FC = () => { }, [isRecording]); const chooseDir = () => { - openDirDialog('Choose Video Directory', recordingProps.recordingFolder) + openDirDialog('Choose Video Folder', recordingProps.recordingFolder) .then((result) => { if (!result.cancelled) { setRecordingProps({ @@ -67,10 +77,16 @@ const RecorderConfig: React.FC = () => { }; const handleChange = (event: React.ChangeEvent) => { - const value = - event.target.value === 'First Camera Discovered' - ? '' + let value = + event.target.type === 'checkbox' + ? event.target.checked : event.target.value; + + if (value === 'First Camera Discovered') { + value = ''; + } + + console.log(`Setting ${event.target.name} to ${value}`); setRecordingProps({ ...recordingProps, [event.target.name]: value, @@ -91,7 +107,7 @@ const RecorderConfig: React.FC = () => { return (
{ { ))} - + { onClick={chooseDir} /> - - + + + openFileExplorer(recordingProps.recordingFolder)} + size="medium" + > + + + - - + + + + + + + + + + + + + + } + label="Finish Line" + sx={{ paddingTop: '1em' }} + /> + - {/* */}
diff --git a/src/renderer/recorder/RecorderData.ts b/src/renderer/recorder/RecorderData.ts index 5525e90..cda1c05 100644 --- a/src/renderer/recorder/RecorderData.ts +++ b/src/renderer/recorder/RecorderData.ts @@ -15,6 +15,7 @@ export const [useRecordingProps, , getRecordingProps] = recordingPrefix: 'CT_', recordingDuration: 5, networkCamera: '', + showFinishGuide: true, }); export const [ useRecordingStartTime, diff --git a/src/renderer/recorder/RecorderTypes.ts b/src/renderer/recorder/RecorderTypes.ts index ec69ce2..66ce8d4 100644 --- a/src/renderer/recorder/RecorderTypes.ts +++ b/src/renderer/recorder/RecorderTypes.ts @@ -5,6 +5,7 @@ export interface RecordingProps { recordingPrefix: string; recordingDuration: number; networkCamera: string; + showFinishGuide: boolean; } export interface RecorderMessage { diff --git a/src/renderer/util/util.d.ts b/src/renderer/util/util.d.ts index 3ee6ba4..b62dc92 100644 --- a/src/renderer/util/util.d.ts +++ b/src/renderer/util/util.d.ts @@ -18,6 +18,7 @@ declare global { ): void; openFileDialog(): Promise; openDirDialog(title: string, defaultPath: string): Promise; + openFileExplorer(path: string): Promise; getFilesInDirectory(dirPath: string): Promise; deleteFile(filename: string): Promise; }; diff --git a/yarn.lock b/yarn.lock index 07a2f7c..ece422b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8852,11 +8852,6 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" -path-browserify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" - integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"