From ec17159f7431d4eb2090a627bf5f631d596b11af Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 26 Oct 2024 19:19:59 +0100 Subject: [PATCH 01/37] yeh this is ok --- backend/extern/afv-native | 2 +- backend/vcpkg | 2 +- src/main/index.ts | 39 ++- src/preload/bindings.ts | 43 ++- .../src/components/MiniModeToggleButton.tsx | 2 +- .../add-station-model/station-modal.tsx | 99 ++++++ src/renderer/src/components/bootstrap.tsx | 1 + src/renderer/src/components/clock.tsx | 6 +- src/renderer/src/components/navbar.tsx | 255 ++++++++++------ src/renderer/src/components/rxinfo.tsx | 27 ++ .../src/components/sidebar/add-frequency.tsx | 11 +- .../src/components/sidebar/radio-status.tsx | 2 +- .../src/components/sidebar/sidebar.tsx | 62 +--- .../src/components/titlebar/TitleBar.scss | 207 +++++++++++++ .../src/components/titlebar/TitleBar.tsx | 289 ++++++++++++++++++ .../components/titlebar/TitleBarElement.tsx | 23 ++ .../components/titlebar/TitleBarSection.tsx | 23 ++ .../session-status/ConnectStatus.scss | 63 ++++ .../session-status/ConnectionStatus.tsx | 126 ++++++++ .../titlebar/session-status/SessionStatus.tsx | 15 + .../components/titlebar/useTitleBarUtils.ts | 124 ++++++++ src/renderer/src/store/utilStore.ts | 12 + src/renderer/src/style/app.scss | 2 +- src/renderer/src/style/variables.scss | 2 +- 24 files changed, 1271 insertions(+), 166 deletions(-) create mode 100644 src/renderer/src/components/add-station-model/station-modal.tsx create mode 100644 src/renderer/src/components/rxinfo.tsx create mode 100644 src/renderer/src/components/titlebar/TitleBar.scss create mode 100644 src/renderer/src/components/titlebar/TitleBar.tsx create mode 100644 src/renderer/src/components/titlebar/TitleBarElement.tsx create mode 100644 src/renderer/src/components/titlebar/TitleBarSection.tsx create mode 100644 src/renderer/src/components/titlebar/session-status/ConnectStatus.scss create mode 100644 src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx create mode 100644 src/renderer/src/components/titlebar/session-status/SessionStatus.tsx create mode 100644 src/renderer/src/components/titlebar/useTitleBarUtils.ts diff --git a/backend/extern/afv-native b/backend/extern/afv-native index c34bb1c..904f896 160000 --- a/backend/extern/afv-native +++ b/backend/extern/afv-native @@ -1 +1 @@ -Subproject commit c34bb1c05fd6ba77353dba500a33e627a5ced28e +Subproject commit 904f896c672109a9e30cdc3f1df4043000cfd385 diff --git a/backend/vcpkg b/backend/vcpkg index 5763791..e60236e 160000 --- a/backend/vcpkg +++ b/backend/vcpkg @@ -1 +1 @@ -Subproject commit 576379156e82da642f8d1834220876759f13534d +Subproject commit e60236ee051183f1122066bee8c54a0b47c43a60 diff --git a/src/main/index.ts b/src/main/index.ts index a88b27d..f3e93ca 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -136,6 +136,8 @@ const createWindow = (): void => { minWidth: 210, minHeight: 120, icon, + trafficLightPosition: { x: 12, y: 10 }, + titleBarStyle: 'hidden', webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false @@ -221,6 +223,22 @@ const createWindow = (): void => { } } }); + + mainWindow.on('enter-full-screen', () => { + mainWindow.webContents.send('is-window-fullscreen', true); + }); + + mainWindow.on('leave-full-screen', () => { + mainWindow.webContents.send('is-window-fullscreen', false); + }); + + mainWindow.on('maximize', () => { + mainWindow.webContents.send('is-window-maximised', true); + }); + + mainWindow.on('unmaximize', () => { + mainWindow.webContents.send('is-window-maximised', false); + }); }; // This method will be called when Electron has finished @@ -435,7 +453,6 @@ ipcMain.handle('set-radio-gain', (_, radioGain: number) => { TrackAudioAfv.SetRadioGain(radioGain); }); - ipcMain.handle('set-frequency-radio-gain', (_, frequency: number, radioGain: number) => { TrackAudioAfv.SetFrequencyRadioGain(frequency, radioGain); }); @@ -489,6 +506,26 @@ ipcMain.handle('get-version', () => { return version; }); +ipcMain.on('maximise-window', () => { + mainWindow.maximize(); +}); + +ipcMain.on('unmaximise-window', () => { + mainWindow.unmaximize(); +}); + +ipcMain.on('minimise-window', () => { + mainWindow.minimize(); +}); + +ipcMain.on('close-window', () => { + mainWindow.close(); +}); + +ipcMain.on('is-window-fullscreen', () => { + mainWindow.webContents.send('is-window-fullscreen', mainWindow.isFullScreen()); +}); + // // Callbacks // diff --git a/src/preload/bindings.ts b/src/preload/bindings.ts index e4d107c..3ca4c6a 100644 --- a/src/preload/bindings.ts +++ b/src/preload/bindings.ts @@ -2,7 +2,6 @@ import { ipcRenderer, IpcRendererEvent } from 'electron'; import { AlwaysOnTopMode, RadioEffects } from '../shared/config.type'; - export const api = { /* eslint-disable @typescript-eslint/no-explicit-any */ on: (channel: string, listener: (...args: any[]) => void) => { @@ -12,6 +11,18 @@ export const api = { listener(...args); }); }, + + onIpc(channel: string, func: (data: T) => void): () => void { + const subscription = (_event: IpcRendererEvent, args: unknown): void => { + func(args as T); + }; + + ipcRenderer.on(channel, subscription); + + return () => { + ipcRenderer.removeListener(channel, subscription); + }; + }, removeAllListeners: (channel: string) => { ipcRenderer.removeAllListeners(channel); }, @@ -95,7 +106,35 @@ export const api = { buttons: string[] ) => ipcRenderer.invoke('dialog', type, title, message, buttons), - settingsReady: () => ipcRenderer.invoke('settings-ready') + settingsReady: () => ipcRenderer.invoke('settings-ready'), + + window: { + checkIsFullscreen(): void { + ipcRenderer.send('is-window-fullscreen'); + }, + minimise: (): void => { + ipcRenderer.send('minimise-window'); + }, + maximise: (): void => { + ipcRenderer.send('maximise-window'); + }, + unmaximise: (): void => { + ipcRenderer.send('unmaximise-window'); + }, + close: (): void => { + ipcRenderer.send('close-window'); + }, + isFullScreen: (callback: (status: boolean) => void): (() => void) => { + return api.onIpc('is-window-fullscreen', (data) => { + callback(data); + }); + }, + isMaximised: (callback: (status: boolean) => void): (() => void) => { + return api.onIpc('is-window-maximised', (data) => { + callback(data); + }); + } + } }; export type API = typeof api; diff --git a/src/renderer/src/components/MiniModeToggleButton.tsx b/src/renderer/src/components/MiniModeToggleButton.tsx index 3e1004a..92437d5 100644 --- a/src/renderer/src/components/MiniModeToggleButton.tsx +++ b/src/renderer/src/components/MiniModeToggleButton.tsx @@ -14,7 +14,7 @@ const MiniModeToggleButton: React.FC = ({ showRestore return ( + + +
+ { + closeModal(); + }} + /> +
+ + +
+ +
+ + + + + ); +}; + +export default AddStationModal; diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index 8b4416c..fc65847 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -10,6 +10,7 @@ const Bootsrap: React.FC = () => { void window.api.RequestPttKeyName(1); void window.api.RequestPttKeyName(2); + window.api.window.checkIsFullscreen(); window.api.on('VuMeter', (vu: string, peakVu: string) => { const vuFloat = Math.abs(parseFloat(vu)); const peakVuFloat = Math.abs(parseFloat(peakVu)); diff --git a/src/renderer/src/components/clock.tsx b/src/renderer/src/components/clock.tsx index b75de22..8d0a66f 100644 --- a/src/renderer/src/components/clock.tsx +++ b/src/renderer/src/components/clock.tsx @@ -18,9 +18,9 @@ const Clock: React.FC = () => { }, 1000); }, []); return ( - <> -
{time}
- +
+
{time}
+
); }; diff --git a/src/renderer/src/components/navbar.tsx b/src/renderer/src/components/navbar.tsx index 0f0fb5a..263e51d 100644 --- a/src/renderer/src/components/navbar.tsx +++ b/src/renderer/src/components/navbar.tsx @@ -3,41 +3,31 @@ import React, { useEffect, useState } from 'react'; import { checkIfCallsignIsRelief, getCleanCallsign } from '../helpers/CallsignHelper'; import useErrorStore from '../store/errorStore'; import useSessionStore from '../store/sessionStore'; -import useUtilStore from '../store/utilStore'; import '../style/navbar.scss'; import Clock from './clock'; import MiniModeToggleButton from './MiniModeToggleButton'; import SettingsModal from './settings-modal/settings-modal'; +import TitleBar from './titlebar/TitleBar'; +import { GearFill, PlusCircleFill } from 'react-bootstrap-icons'; +import SessionStatus from './titlebar/session-status/SessionStatus'; import { Configuration } from 'src/shared/config.type'; +import useUtilStore from '@renderer/store/utilStore'; +import AddStationModal from './add-station-model/station-modal'; +import RxInfo from './rxinfo'; const Navbar: React.FC = () => { - const [showModal, setShowModal] = useState(false); + const [showSettingsModal, setShowSettingsModal] = useState(false); + const [showAddStationModal, setShowAddStationModal] = useState(false); const [platform] = useUtilStore((state) => [state.platform]); - - const postError = useErrorStore((state) => state.postError); - const [ - isConnected, - isConnecting, - setIsConnecting, - setIsConnected, - callsign, - isNetworkConnected, - radioGain, - setRadioGain, - isAtc, - setStationCallsign - ] = useSessionStore((state) => [ - state.isConnected, - state.isConnecting, - state.setIsConnecting, - state.setIsConnected, - state.callsign, - state.isNetworkConnected, - state.radioGain, - state.setRadioGain, - state.isAtc, - state.setStationCallsign - ]); + const [callsign, isConnected, isConnecting, radioGain, setRadioGain] = useSessionStore( + (state) => [ + state.callsign, + state.isConnected, + state.isConnecting, + state.radioGain, + state.setRadioGain + ] + ); // Handles letting the main process know settings can be triggered // remotely, and responds to requests to open the settings dialog. @@ -47,7 +37,8 @@ const Navbar: React.FC = () => { }); window.electron.ipcRenderer.on('show-settings', () => { - setShowModal(true); + if (showAddStationModal) return; + setShowSettingsModal(true); }); }, []); @@ -72,61 +63,6 @@ const Navbar: React.FC = () => { }); }, [setRadioGain]); - const doConnect = () => { - setIsConnecting(true); - window.api - .connect() - .then((ret) => { - if (!ret) { - postError('Error connecting to AFV, check your configuration and credentials.'); - setIsConnecting(false); - setIsConnected(false); - } - }) - .catch((err: unknown) => { - console.error(err); - }); - }; - - const handleConnectDisconnect = () => { - if (isConnected) { - void window.api.disconnect(); - return; - } - - if (!isNetworkConnected) { - return; - } - - if (checkIfCallsignIsRelief(callsign) && isAtc) { - const reliefCallsign = getCleanCallsign(callsign); - window.api - .dialog( - 'question', - 'Relief callsign detected', - 'You might be using a relief callsign, please select which callsign you want to use.', - [callsign, reliefCallsign] - ) - .then((ret) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (ret.response === 0) { - setStationCallsign(callsign); - } else { - setStationCallsign(reliefCallsign); - } - }) - .then(() => { - doConnect(); - }) - .catch((err: unknown) => { - console.error(err); - }); - } else { - setStationCallsign(callsign); - doConnect(); - } - }; - const updateRadioGainValue = (newGain: number) => { window.api .SetRadioGain(newGain / 100) @@ -150,11 +86,136 @@ const Navbar: React.FC = () => { return ( <> -
+ + + + + + +
+ +
+
+ +
+ + {platform === 'linux' && ( + + )} +
+
+ {/* +
+ +
+
*/} +
+ + + + {isConnected && callsign ? `Connected as ${callsign}` : 'Track Audio'} + + + + + {/* +
+ + {isNetworkConnected ? callsign : 'Not Connected'} + +
+
*/} + + + + +
+ +
+
+ + + + + {/* +
+ + Gain: {radioGain.toFixed(0).padStart(3, '0')}% + + +
+
+ +
+ + {platform === 'linux' && ( + + )} +
+
*/} +
+
+ + {/*
@@ -162,7 +223,7 @@ const Navbar: React.FC = () => { @@ -193,7 +254,7 @@ const Navbar: React.FC = () => { { {platform === 'linux' && ( )} -
- {showModal && ( +
*/} + {showSettingsModal && ( { - setShowModal(false); + setShowSettingsModal(false); + }} + /> + )} + + {showAddStationModal && ( + { + setShowAddStationModal(false); }} /> )} diff --git a/src/renderer/src/components/rxinfo.tsx b/src/renderer/src/components/rxinfo.tsx new file mode 100644 index 0000000..9e1fba2 --- /dev/null +++ b/src/renderer/src/components/rxinfo.tsx @@ -0,0 +1,27 @@ +import useRadioState from '@renderer/store/radioStore'; +import React, { useMemo } from 'react'; + +const RxInfo: React.FC = () => { + const radios = useRadioState((state) => state.radios); + const rxRadios = useMemo(() => { + return radios.filter((radio) => radio.rx && radio.lastReceivedCallsign); + }, [radios]); + + if (rxRadios.length === 0) { + return null; + } + + return ( +
+
+ {rxRadios.map((radio, index) => ( +
+ {radio.lastReceivedCallsign} +
+ ))} +
+
+ ); +}; + +export default RxInfo; diff --git a/src/renderer/src/components/sidebar/add-frequency.tsx b/src/renderer/src/components/sidebar/add-frequency.tsx index fecd36d..a95ccb2 100644 --- a/src/renderer/src/components/sidebar/add-frequency.tsx +++ b/src/renderer/src/components/sidebar/add-frequency.tsx @@ -2,7 +2,11 @@ import React, { useRef, useState } from 'react'; import useRadioState, { RadioHelper } from '../../store/radioStore'; import useSessionStore from '../../store/sessionStore'; -const AddFrequency: React.FC = () => { +export interface AddFrequencyProps { + onAddFrequency: () => void; +} + +const AddFrequency: React.FC = () => { const [readyToAdd, setReadyToAdd] = useState(false); const [previousValue, setPreviousValue] = useState(''); const [addRadio] = useRadioState((state) => [state.addRadio]); @@ -38,6 +42,7 @@ const AddFrequency: React.FC = () => { frequencyInputRef.current.value = ''; setPreviousValue(''); setReadyToAdd(false); + onAddFrequency(); }; const checkFrequency = (e: React.ChangeEvent) => { @@ -65,8 +70,8 @@ const AddFrequency: React.FC = () => { }; return ( -
- +
+
Add a VHF frequency
{ }; return ( -
+
Radio Status
diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index 16f38f0..dc89d59 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -1,70 +1,16 @@ -import React, { useRef, useState } from 'react'; -import AddFrequency from './add-frequency'; +import React from 'react'; import RadioStatus from './radio-status'; import useSessionStore from '../../store/sessionStore'; -import useRadioState from '../../store/radioStore'; -import LastReceivedCallsigns from './lastReceivedCallsigns'; const Sidebar: React.FC = () => { - const [readyToAdd, setReadyToAdd] = useState(false); - const [version, isConnected] = useSessionStore((state) => [state.version, state.isConnected]); - const [radios] = useRadioState((state) => [state.radios]); - - const stationInputRef = useRef(null); - - const addStation = () => { - if (!readyToAdd || !isConnected) { - return; - } - - const callsign = stationInputRef.current?.value.toUpperCase(); - if (!callsign?.match(/^[A-Z0-9_ -]+$/) || !stationInputRef.current) { - return; - } - - void window.api.GetStation(callsign); - stationInputRef.current.value = ''; - setReadyToAdd(false); - }; + const [version] = useSessionStore((state) => [state.version, state.isConnected]); return ( <> -
-
- - { - e.target.value.length !== 0 ? setReadyToAdd(true) : setReadyToAdd(false); - }} - onKeyDown={(e) => { - e.key === 'Enter' && addStation(); - }} - > - -
- - - - {/* - Source: Slurper - */} - - - +
-
+
{version} |  svg { + stroke: #fff; + stroke-width: 1.1px; + } + } + + &.caption-close:active { + background-color: rgba(232, 17, 35, 0.6); + } + + > svg { + width: 12px; + height: 12px; + } + } +} +body:not(.task-overlay-is-shown).dark-theme .windows-caption-buttons .element { + stroke: #fff; +} + +.gear { + -webkit-app-region: no-drag; + cursor: pointer; + height: 100%; + display: flex; + align-items: center; + // min-width: 31px; +} diff --git a/src/renderer/src/components/titlebar/TitleBar.tsx b/src/renderer/src/components/titlebar/TitleBar.tsx new file mode 100644 index 0000000..fdb941b --- /dev/null +++ b/src/renderer/src/components/titlebar/TitleBar.tsx @@ -0,0 +1,289 @@ +import React, { useState, useEffect, useRef, ReactNode, useCallback } from 'react'; +import './TitleBar.scss'; +import TitleBarSection, { TitleBarSectionProps } from './TitleBarSection'; +import TitleBarElement, { TitleBarElementProps } from './TitleBarElement'; +import useTitleBarUtils from './useTitleBarUtils'; +import useUtilStore from '@renderer/store/utilStore'; + +interface TitleBarProps { + className?: string; + disableResize?: boolean; + children: ReactNode; +} + +const TitleBar: React.FC & { + Section: React.FC; + Element: React.FC; +} = ({ className, disableResize = false, children }: TitleBarProps) => { + const [focused, setFocused] = useState(true); + const [os, isWindowFullscreen, isWindowMaximised] = useUtilStore((state) => [ + state.platform, + state.isWindowFullscreen, + state.isWindowMaximised + ]); + const [isWindows, isLinux, isMac] = [os === 'win32', os === 'linux', os === 'darwin']; + + const titleBarRef = useRef(null); + const { + sectionRefs, + elementRefs, + calculateAvailableSpace, + calculateGap, + getSectionRef, + getElementRef + } = useTitleBarUtils(children, titleBarRef); + + const handleResize = useCallback(() => { + const sections = React.Children.toArray(children) as React.ReactElement[]; + const spacing = 16; + + sections.sort( + (a, b) => + (b.props as TitleBarSectionProps).priority - (a.props as TitleBarSectionProps).priority + ); + + sections.forEach((section) => { + const sectionName = (section.props as TitleBarSectionProps).name; + const sectionElement = sectionRefs.current[sectionName]; + const elements = Object.values(elementRefs.current[sectionName]).filter(Boolean); + + if (elements.length === 1 && sections.length === 1) { + return; + } + + elements.sort((a, b) => { + const aPriority = Number(a?.getAttribute('data-priority') ?? Infinity); + const bPriority = Number(b?.getAttribute('data-priority') ?? Infinity); + return bPriority - aPriority; + }); + + elements.forEach((element) => { + if (!element) return; + + const gap = calculateGap(sectionName); + const isEnoughGap = gap >= spacing; + + if (gap == -1) { + return; + } + + const isElementVisible = element.style.display !== 'none'; + if (isElementVisible && !isEnoughGap) { + element.style.display = 'none'; + + if (element.getAttribute('data-priority') === '1' && sectionElement) { + sectionElement.visible = false; + } + return; + } + }); + }); + + sections.reverse().forEach((section) => { + const sectionName = (section.props as TitleBarSectionProps).name; + const sectionElement = sectionRefs.current[sectionName]; + const elements = Object.values(elementRefs.current[sectionName]).filter(Boolean); + + if (elements.length === 1 && sections.length === 1) { + return; + } + + elements.reverse().forEach((element) => { + if (!element) return; + + const isElementVisible = element.style.display !== 'none'; + const isTemporarilyHidden = + element.style.visibility === 'hidden' && element.style.display === 'block'; + if (isElementVisible && !isTemporarilyHidden) { + return; + } + + let elementWidth = element.offsetWidth; + const isEnoughSpace = calculateAvailableSpace() - elementWidth >= spacing; + const gap = calculateGap(sectionName); + + if (gap == -1) { + return; + } + let isEnoughGap = gap - elementWidth >= spacing; + + if (!isElementVisible && isEnoughSpace && isEnoughGap) { + element.style.visibility = 'hidden'; + element.style.display = 'block'; + elementWidth = element.offsetWidth; + isEnoughGap = calculateGap(sectionName) >= spacing; + } + + if (isEnoughGap && isEnoughSpace) { + if (sectionElement) { + if ( + !sectionElement.visible && + element.getAttribute('data-priority') !== '1' && + (section.props as TitleBarSectionProps).priority !== 1 + ) { + return; + } + sectionElement.visible = true; + } + + element.style.visibility = 'visible'; + } else { + element.style.display = 'none'; + } + }); + }); + }, [children, calculateAvailableSpace, calculateGap, sectionRefs, elementRefs]); + + useEffect(() => { + handleResize(); + + window.addEventListener('resize', handleResize); + window.addEventListener('focus', () => { + setFocused(true); + }); + window.addEventListener('blur', () => { + setFocused(false); + }); + + return () => { + window.removeEventListener('resize', handleResize); + window.removeEventListener('focus', () => { + setFocused(true); + }); + window.removeEventListener('blur', () => { + setFocused(false); + }); + }; + }, [children, handleResize]); + + const isTitleBarElement = ( + element: React.ReactNode + ): element is React.ReactElement => { + return ( + React.isValidElement(element) && + typeof (element.props as TitleBarElementProps).priority === 'number' + ); + }; + + const minimiseWindow = (): void => { + window.api.window.minimise(); + }; + + const maximiseWindow = (): void => { + window.api.window.maximise(); + }; + + const unmaximiseWindow = (): void => { + window.api.window.unmaximise(); + }; + + const closeWindow = (): void => { + window.api.window.close(); + }; + + return ( +
+ {React.Children.map(children, (child) => { + if (React.isValidElement(child)) { + const { name, priority } = child.props as TitleBarSectionProps; + return ( +
+ {React.Children.map((child.props as TitleBarSectionProps).children, (element) => + isTitleBarElement(element) ? ( +
+ {element} +
+ ) : null + )} +
+ ); + } + return null; + })} + + {(isWindows || isLinux) && ( + <> +
+
+
{ + if (!disableResize) minimiseWindow(); + }} + > + + + +
+ {!isWindowMaximised && ( +
{ + if (!disableResize) maximiseWindow(); + }} + > + + + +
+ )} + {isWindowMaximised && ( +
{ + if (!disableResize) unmaximiseWindow(); + }} + > + + + + +
+ )} +
{ + closeWindow(); + }} + > + + + +
+
+
+ + )} +
+ ); +}; + +// Add a display name for easier debugging + +// Add PropTypes for additional runtime validation + +TitleBar.Section = TitleBarSection; +TitleBar.Element = TitleBarElement; + +export default TitleBar; diff --git a/src/renderer/src/components/titlebar/TitleBarElement.tsx b/src/renderer/src/components/titlebar/TitleBarElement.tsx new file mode 100644 index 0000000..f1f8f32 --- /dev/null +++ b/src/renderer/src/components/titlebar/TitleBarElement.tsx @@ -0,0 +1,23 @@ +import React, { useMemo } from 'react'; + +export interface TitleBarElementProps { + children: React.ReactNode; + priority: number; +} + +const TitleBarElement = ({ children, priority }: TitleBarElementProps): JSX.Element => { + const calculatedChildren = useMemo(() => { + return ( +
+ {children} +
+ ); + }, [children, priority]); + + return calculatedChildren; +}; + +// Add a display name for easier debugging +TitleBarElement.displayName = 'TitleBarElement'; + +export default TitleBarElement; diff --git a/src/renderer/src/components/titlebar/TitleBarSection.tsx b/src/renderer/src/components/titlebar/TitleBarSection.tsx new file mode 100644 index 0000000..fbcd907 --- /dev/null +++ b/src/renderer/src/components/titlebar/TitleBarSection.tsx @@ -0,0 +1,23 @@ +import React, { useMemo } from 'react'; +import TitleBarElement from './TitleBarElement'; + +export interface TitleBarSectionProps { + children: React.ReactNode; + priority: number; + name: string; +} + +const TitleBarSection = ({ children }: TitleBarSectionProps): JSX.Element => { + const childrenWithPriorities = useMemo(() => { + React.Children.map(children, (child) => { + if (React.isValidElement(child) && child.type === TitleBarElement) { + return child; + } + return {child}; + }); + }, [children]); + + return <>{childrenWithPriorities}; +}; + +export default TitleBarSection; diff --git a/src/renderer/src/components/titlebar/session-status/ConnectStatus.scss b/src/renderer/src/components/titlebar/session-status/ConnectStatus.scss new file mode 100644 index 0000000..3f573bb --- /dev/null +++ b/src/renderer/src/components/titlebar/session-status/ConnectStatus.scss @@ -0,0 +1,63 @@ +@use '../../../style/variables.scss' as GlobalVars; + +.connect-status { + // background-color: darken($dark, 5%) !important; + font-family: GlobalVars.$font-medium; + // height: calc($navbar-height - 2px) !important; +} + +.freq-status { + font-family: GlobalVars.$font-medium; + padding: 1px 2px; + margin: 2px 0px; + font-size: 7px; + color: GlobalVars.$body-color; + background-color: rgb(129, 129, 129); + border-radius: 3px; + display: flex; + justify-content: center; + align-items: center; +} + +.freq-status:last-child { + margin-top: auto; +} + +.freq-status.active { + color: GlobalVars.$body-color; + background-color: rgb(249, 112, 0); +} + +.connected { + background-color: rgba(GlobalVars.$success, 0.6) !important; + color: white !important; + border-color: GlobalVars.$success !important; + cursor: pointer; +} + +.rxOnly { + background-color: rgba(GlobalVars.$info, 0.6) !important; + color: white !important; + border-color: GlobalVars.$info !important; + cursor: pointer; +} + +.txOnly { + background-color: rgba(GlobalVars.$warning, 0.6) !important; + color: white !important; + border-color: GlobalVars.$warning !important; + cursor: pointer; +} + +.disconnected { + background-color: rgba(GlobalVars.$success, 0.6) !important; + color: white !important; + border-color: GlobalVars.$success !important; + cursor: pointer; +} + +.vatsim-logo { + // padding: 2px; + height: 100%; + opacity: 0.9; +} diff --git a/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx b/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx new file mode 100644 index 0000000..c84d2c2 --- /dev/null +++ b/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx @@ -0,0 +1,126 @@ +import React from 'react'; +import useSessionStore from '@renderer/store/sessionStore'; +import useErrorStore from '@renderer/store/errorStore'; +import { checkIfCallsignIsRelief, getCleanCallsign } from '../../../helpers/CallsignHelper'; +import clsx from 'clsx'; + +interface ConnectionStatusProps { + className?: string; +} + +const ConnectionStatus: React.FC = ({ className = '' }) => { + const postError = useErrorStore((state) => state.postError); + const [ + isConnected, + isConnecting, + setIsConnecting, + setIsConnected, + callsign, + isNetworkConnected, + isAtc, + setStationCallsign + ] = useSessionStore((state) => [ + state.isConnected, + state.isConnecting, + state.setIsConnecting, + state.setIsConnected, + state.callsign, + state.isNetworkConnected, + state.isAtc, + state.setStationCallsign + ]); + + const doConnect = () => { + setIsConnecting(true); + window.api + .connect() + .then((ret) => { + if (!ret) { + postError('Error connecting to AFV, check your configuration and credentials.'); + setIsConnecting(false); + setIsConnected(false); + } + }) + .catch((err: unknown) => { + console.error(err); + }); + }; + + const handleConnectDisconnect = () => { + if (isConnected) { + void window.api.disconnect(); + return; + } + + if (!isNetworkConnected) { + return; + } + + if (checkIfCallsignIsRelief(callsign) && isAtc) { + const reliefCallsign = getCleanCallsign(callsign); + window.api + .dialog( + 'question', + 'Relief callsign detected', + 'You might be using a relief callsign, please select which callsign you want to use.', + [callsign, reliefCallsign] + ) + .then((ret) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (ret.response === 0) { + setStationCallsign(callsign); + } else { + setStationCallsign(reliefCallsign); + } + }) + .then(() => { + doConnect(); + }) + .catch((err: unknown) => { + console.error(err); + }); + } else { + setStationCallsign(callsign); + doConnect(); + } + }; + + return ( + + ); + + return isConnected && callsign ? ( +
+ {callsign} +
+ ) : ( + + ); +}; + +export default ConnectionStatus; diff --git a/src/renderer/src/components/titlebar/session-status/SessionStatus.tsx b/src/renderer/src/components/titlebar/session-status/SessionStatus.tsx new file mode 100644 index 0000000..6be224b --- /dev/null +++ b/src/renderer/src/components/titlebar/session-status/SessionStatus.tsx @@ -0,0 +1,15 @@ +import './ConnectStatus.scss'; +import ConnectionStatus from './ConnectionStatus'; +const SessionStatus = () => { + return ( + <> +
+
+ +
+
+ + ); +}; + +export default SessionStatus; diff --git a/src/renderer/src/components/titlebar/useTitleBarUtils.ts b/src/renderer/src/components/titlebar/useTitleBarUtils.ts new file mode 100644 index 0000000..dd4842d --- /dev/null +++ b/src/renderer/src/components/titlebar/useTitleBarUtils.ts @@ -0,0 +1,124 @@ +import React, { useRef, useCallback } from 'react'; + +interface SectionRef { + element: HTMLDivElement | null; + visible: boolean; +} + +const useTitleBarUtils = ( + children: React.ReactNode, + titleBarRef: React.RefObject +): { + sectionRefs: React.MutableRefObject>; + elementRefs: React.MutableRefObject>>; + calculateAvailableSpace: () => number; + calculateGap: (sectionName: string) => number; + getSectionRef: (name: string) => (ref: HTMLDivElement | null) => void; + getElementRef: (sectionName: string, priority: number) => (ref: HTMLDivElement | null) => void; +} => { + const sectionRefs = useRef>({}); + const elementRefs = useRef>>({}); + + const calculateAvailableSpace = useCallback((): number => { + const titleBarWidth = titleBarRef.current?.offsetWidth || 0; + const sections = React.Children.toArray(children) as React.ReactElement[]; + const spacing = 15; + + return sections.reduce((sum, section) => { + const sectionName = section.props.name; + const elements = Object.values(elementRefs.current[sectionName] || {}).filter(Boolean); + + const visibleElements = elements.filter( + (element) => element && element.style.display !== 'none' + ); + const sectionWidth = + visibleElements.reduce((sum, element) => sum + (element ? element.offsetWidth : 0), 0) + + spacing * (visibleElements.length - 1); + + return sum - sectionWidth; + }, titleBarWidth); + }, [children, titleBarRef]); + + const calculateGap = useCallback((sectionName: string): number => { + const sections = ['left', 'center', 'right']; + const currentIndex = sections.indexOf(sectionName); + + if (currentIndex === -1) { + return 0; + } + + const currentSectionElement = sectionRefs.current[sectionName]?.element; + + if (!currentSectionElement) { + return 0; + } + + const currentSectionRect = currentSectionElement.getBoundingClientRect(); + + let minGap = Infinity; + let neighborFound = false; + + if (currentIndex > 0) { + const leftNeighborSection = sections[currentIndex - 1]; + const leftNeighborElement = sectionRefs.current[leftNeighborSection]?.element; + + if (leftNeighborElement) { + neighborFound = true; + const leftNeighborRect = leftNeighborElement.getBoundingClientRect(); + const gap = currentSectionRect.left - leftNeighborRect.right; + if (gap < 0) return 0; + minGap = Math.min(minGap, gap); + } + } + + if (currentIndex < sections.length - 1) { + const rightNeighborSection = sections[currentIndex + 1]; + const rightNeighborElement = sectionRefs.current[rightNeighborSection]?.element; + + if (rightNeighborElement) { + neighborFound = true; + const rightNeighborRect = rightNeighborElement.getBoundingClientRect(); + const gap = rightNeighborRect.left - currentSectionRect.right; + if (gap < 0) return 0; + minGap = Math.min(minGap, gap); + } + } + + if (!neighborFound) { + return -1; + } + + return minGap !== Infinity ? minGap : 0; + }, []); + + const getSectionRef = useCallback( + (name: string) => (ref: HTMLDivElement | null) => { + sectionRefs.current[name] = { element: ref, visible: true }; + if (!elementRefs.current[name]) { + elementRefs.current[name] = {}; + } + }, + [] + ); + + const getElementRef = useCallback( + (sectionName: string, priority: number) => (ref: HTMLDivElement | null) => { + if (!elementRefs.current[sectionName]) { + elementRefs.current[sectionName] = {}; + } + elementRefs.current[sectionName][priority] = ref; + }, + [] + ); + + return { + sectionRefs, + elementRefs, + calculateAvailableSpace, + calculateGap, + getSectionRef, + getElementRef + }; +}; + +export default useTitleBarUtils; diff --git a/src/renderer/src/store/utilStore.ts b/src/renderer/src/store/utilStore.ts index d59cbd2..6e7535f 100644 --- a/src/renderer/src/store/utilStore.ts +++ b/src/renderer/src/store/utilStore.ts @@ -6,6 +6,8 @@ interface UtilStore { platform: string; ptt1KeyName: string; ptt2KeyName: string; + isWindowFullscreen: boolean; + isWindowMaximised: boolean; hasPtt1BeenSetDuringSetup: boolean; hasPtt2BeenSetDuringSetup: boolean; setPtt1KeyName: (ptt1KeyName: string) => void; @@ -14,6 +16,8 @@ interface UtilStore { updatePlatform: (platform: string) => void; updatePtt1KeySet: (hasPtt1BeenSetDuringSetup: boolean) => void; updatePtt2KeySet: (hasPtt2BeenSetDuringSetup: boolean) => void; + setWindowFullscreen: (fullscreen: boolean) => void; + setWindowMaximised: (maximised: boolean) => void; } const useUtilStore = create((set) => ({ @@ -24,6 +28,8 @@ const useUtilStore = create((set) => ({ ptt2KeyName: '', hasPtt1BeenSetDuringSetup: false, hasPtt2BeenSetDuringSetup: false, + isWindowFullscreen: false, + isWindowMaximised: false, setPtt1KeyName: (ptt1KeyName: string) => { set({ ptt1KeyName }); }, @@ -41,6 +47,12 @@ const useUtilStore = create((set) => ({ }, updatePtt2KeySet: (hasPtt2BeenSetDuringSetup: boolean) => { set({ hasPtt2BeenSetDuringSetup }); + }, + setWindowFullscreen: (fullscreen: boolean): void => { + set({ isWindowFullscreen: fullscreen }); + }, + setWindowMaximised: (maximised: boolean): void => { + set({ isWindowMaximised: maximised }); } })); diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 6483c66..13efa7c 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -434,5 +434,5 @@ select:disabled { } ::-webkit-scrollbar-thumb:hover { - background: lighten(rgb(50, 50, 58), 10%) /* Color of the scrollbar thumb on hover */ + background: lighten(rgb(50, 50, 58), 10%); /* Color of the scrollbar thumb on hover */ } diff --git a/src/renderer/src/style/variables.scss b/src/renderer/src/style/variables.scss index 3db71c4..917b33d 100644 --- a/src/renderer/src/style/variables.scss +++ b/src/renderer/src/style/variables.scss @@ -116,7 +116,7 @@ $sub-font-size: 11px; $modal-border: 2px solid $dark-border; $modal-border-danger: 2px solid $dark-border; $sidebar-width: 250px; -$navbar-height: 46px; +$navbar-height: 36px; $focusbar-height: 30px; /* buttons */ From cf7dc29aead8e3259a20325245319d1f362cef4c Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 26 Oct 2024 20:54:19 +0100 Subject: [PATCH 02/37] Faffing around with faffing --- src/renderer/src/app.tsx | 4 +- src/renderer/src/components/bootstrap.tsx | 1 + .../src/components/delete-multiple-radios.tsx | 61 ++++++++++++ src/renderer/src/components/focusBar.tsx | 39 ++++++++ src/renderer/src/components/navbar.tsx | 84 ++++++++++++---- .../src/components/radio/radio-container.tsx | 2 +- src/renderer/src/components/radio/radio.tsx | 27 +++++- .../components/refresh-multiple-radios.tsx | 34 +++++++ .../src/components/sidebar/radio-status.tsx | 95 +++---------------- src/renderer/src/store/radioStore.ts | 23 ++++- src/renderer/src/store/utilStore.ts | 6 ++ src/renderer/src/style/app.scss | 27 +++++- src/renderer/src/style/variables.scss | 2 +- 13 files changed, 295 insertions(+), 110 deletions(-) create mode 100644 src/renderer/src/components/delete-multiple-radios.tsx create mode 100644 src/renderer/src/components/focusBar.tsx create mode 100644 src/renderer/src/components/refresh-multiple-radios.tsx diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 6006761..b1349ce 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -9,6 +9,7 @@ import Mini from './components/mini'; import './index.scss'; import './style/app.scss'; +import FocusBar from './components/focusBar'; function App() { return ( @@ -19,7 +20,8 @@ function App() {
- + + {/* */}
); diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index fc65847..5c0d40e 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -111,6 +111,7 @@ const Bootsrap: React.FC = () => { useSessionStore.getState().setIsConnecting(false); useSessionStore.getState().setIsConnected(false); useRadioState.getState().reset(); + useUtilStore.getState().setIsEditMode(false); }); window.api.on('network-connected', (callsign: string, dataString: string) => { diff --git a/src/renderer/src/components/delete-multiple-radios.tsx b/src/renderer/src/components/delete-multiple-radios.tsx new file mode 100644 index 0000000..a4aac11 --- /dev/null +++ b/src/renderer/src/components/delete-multiple-radios.tsx @@ -0,0 +1,61 @@ +import useRadioState from '@renderer/store/radioStore'; +import useSessionStore from '@renderer/store/sessionStore'; +import React from 'react'; +import { CheckCircleFill, TrashFill } from 'react-bootstrap-icons'; + +const DeleteMultipleRadios: React.FC = () => { + const [isConnected] = useSessionStore((state) => [state.isConnected]); + const [radiosToBeDeleted, removeRadio, setPendingDeletion] = useRadioState((state) => [ + state.radiosSelected, + state.removeRadio, + state.setPendingDeletion + ]); + + const handleDeleteRadios = () => { + radiosToBeDeleted.forEach((radio) => { + setPendingDeletion(radio.frequency, true); + awaitEndOfRxForDeletion(radio.frequency); + }); + }; + + const awaitEndOfRxForDeletion = (frequency: number): void => { + const interval = setInterval( + (frequency: number) => { + const radio = useRadioState.getState().radios.find((r) => r.frequency === frequency); + if (!radio) { + clearInterval(interval); + return; + } + + if (!radio.currentlyRx && !radio.currentlyTx) { + void window.api.removeFrequency(radio.frequency); + removeRadio(radio.frequency); + clearInterval(interval); + } + }, + 60, + frequency + ); + + // Clear the interval after 5 seconds + setTimeout(() => { + clearInterval(interval); + }, 10000); + }; + + return ( +
+ +
+ ); +}; + +export default DeleteMultipleRadios; diff --git a/src/renderer/src/components/focusBar.tsx b/src/renderer/src/components/focusBar.tsx new file mode 100644 index 0000000..68a5e6e --- /dev/null +++ b/src/renderer/src/components/focusBar.tsx @@ -0,0 +1,39 @@ +import useSessionStore from '@renderer/store/sessionStore'; +import RadioStatus from './sidebar/radio-status'; + +const FocusBar = () => { + const [version] = useSessionStore((state) => [state.version]); + return ( +
+ ); +}; + +export default FocusBar; diff --git a/src/renderer/src/components/navbar.tsx b/src/renderer/src/components/navbar.tsx index 263e51d..962b9ed 100644 --- a/src/renderer/src/components/navbar.tsx +++ b/src/renderer/src/components/navbar.tsx @@ -8,17 +8,31 @@ import Clock from './clock'; import MiniModeToggleButton from './MiniModeToggleButton'; import SettingsModal from './settings-modal/settings-modal'; import TitleBar from './titlebar/TitleBar'; -import { GearFill, PlusCircleFill } from 'react-bootstrap-icons'; +import { + Check2Circle, + CheckCircleFill, + GearFill, + PencilSquare, + PlusCircleFill, + XCircleFill +} from 'react-bootstrap-icons'; import SessionStatus from './titlebar/session-status/SessionStatus'; import { Configuration } from 'src/shared/config.type'; import useUtilStore from '@renderer/store/utilStore'; import AddStationModal from './add-station-model/station-modal'; import RxInfo from './rxinfo'; +import DeleteMultipleRadios from './delete-multiple-radios'; +import useRadioState from '@renderer/store/radioStore'; +import RefreshMultipleRadios from './refresh-multiple-radios'; const Navbar: React.FC = () => { const [showSettingsModal, setShowSettingsModal] = useState(false); const [showAddStationModal, setShowAddStationModal] = useState(false); - const [platform] = useUtilStore((state) => [state.platform]); + const [platform, isEditMode, setIsEditMode] = useUtilStore((state) => [ + state.platform, + state.isEditMode, + state.setIsEditMode + ]); const [callsign, isConnected, isConnecting, radioGain, setRadioGain] = useSessionStore( (state) => [ state.callsign, @@ -29,6 +43,8 @@ const Navbar: React.FC = () => { ] ); + const [clearRadiosToBeDeleted] = useRadioState((state) => [state.clearRadiosToBeDeleted]); + // Handles letting the main process know settings can be triggered // remotely, and responds to requests to open the settings dialog. useEffect(() => { @@ -88,36 +104,68 @@ const Navbar: React.FC = () => { <> - +
- -
- - {platform === 'linux' && ( + {isEditMode && ( + + + + )} + {isEditMode && ( + + + + )} + {!isEditMode && ( + +
- )} -
-
+
+
+ )} + {!isEditMode && ( + +
+ + {platform === 'linux' && ( + + )} +
+
+ )} + {/*
{
-
+
{filteredRadios.map((radio) => ( ))} diff --git a/src/renderer/src/components/radio/radio.tsx b/src/renderer/src/components/radio/radio.tsx index 5444814..d7562d2 100644 --- a/src/renderer/src/components/radio/radio.tsx +++ b/src/renderer/src/components/radio/radio.tsx @@ -3,6 +3,8 @@ import useRadioState, { RadioType } from '../../store/radioStore'; import clsx from 'clsx'; import useErrorStore from '../../store/errorStore'; import useSessionStore from '../../store/sessionStore'; +import useUtilStore from '../../store/utilStore'; +import { is } from '@electron-toolkit/utils'; export interface RadioProps { radio: RadioType; @@ -10,16 +12,28 @@ export interface RadioProps { const Radio: React.FC = ({ radio }) => { const postError = useErrorStore((state) => state.postError); - const [setRadioState, selectRadio, removeRadio, setPendingDeletion] = useRadioState((state) => [ + const [ + setRadioState, + selectRadio, + removeRadio, + setPendingDeletion, + addOrRemoveRadioToBeDeleted, + radiosToBeDeleted + ] = useRadioState((state) => [ state.setRadioState, state.selectRadio, state.removeRadio, - state.setPendingDeletion + state.setPendingDeletion, + state.addOrRemoveRadioToBeDeleted, + state.radiosSelected ]); - + const [isEditMode] = useUtilStore((state) => [state.isEditMode]); const isATC = useSessionStore((state) => state.isAtc); const clickRadioHeader = () => { + if (isEditMode) { + addOrRemoveRadioToBeDeleted(radio); + } selectRadio(radio.frequency); if (radio.transceiverCount === 0 && radio.callsign !== 'MANUAL') { void window.api.RefreshStation(radio.callsign); @@ -203,7 +217,12 @@ const Radio: React.FC = ({ radio }) => { return ( <> -
+
r.frequency === radio.frequency) && 'bg-info' + )} + >
+
+ ); +}; + +export default RefreshMultipleRadios; diff --git a/src/renderer/src/components/sidebar/radio-status.tsx b/src/renderer/src/components/sidebar/radio-status.tsx index dc75336..c4871c9 100644 --- a/src/renderer/src/components/sidebar/radio-status.tsx +++ b/src/renderer/src/components/sidebar/radio-status.tsx @@ -2,93 +2,24 @@ import React from 'react'; import useRadioState, { RadioHelper } from '../../store/radioStore'; const RadioStatus: React.FC = () => { - const [selectedRadio, removeRadio, setPendingDeletion] = useRadioState((state) => [ - state.getSelectedRadio(), - state.removeRadio, - state.setPendingDeletion - ]); + const [selectedRadio] = useRadioState((state) => [state.getSelectedRadio()]); - const awaitEndOfRxForDeletion = (frequency: number): void => { - const interval = setInterval( - (frequency: number) => { - const radio = useRadioState.getState().radios.find((r) => r.frequency === frequency); - if (!radio) { - clearInterval(interval); - return; - } - - if (!radio.currentlyRx && !radio.currentlyTx) { - void window.api.removeFrequency(radio.frequency); - removeRadio(radio.frequency); - clearInterval(interval); - } - }, - 60, - frequency - ); - - // Clear the interval after 5 seconds - setTimeout(() => { - clearInterval(interval); - }, 10000); - }; - - const handleDeleteRadio = () => { - if (!selectedRadio) { - return; - } - setPendingDeletion(selectedRadio.frequency, true); - awaitEndOfRxForDeletion(selectedRadio.frequency); - }; - - const handleForceRefresh = () => { - if (!selectedRadio) { - return; - } - if (selectedRadio.callsign === 'MANUAL') { - return; - } - void window.api.RefreshStation(selectedRadio.callsign); - }; + if (!selectedRadio) { + return; + } return ( -
-
- Radio Status +
+
+ Callsign:
- Callsign: {selectedRadio ? selectedRadio.callsign : ''} -
- - Frequency: {selectedRadio ? RadioHelper.convertHzToMhzString(selectedRadio.frequency) : ''} - -
- - Transceivers:{' '} - {selectedRadio - ? selectedRadio.callsign !== 'MANUAL' - ? selectedRadio.transceiverCount - : 'MAN' - : ''} + {selectedRadio.callsign} + Frequency: + {RadioHelper.convertHzToMhzString(selectedRadio.frequency)} + Transceivers: + + {selectedRadio.callsign !== 'MANUAL' ? selectedRadio.transceiverCount : 'MAN'} -
- -
); }; diff --git a/src/renderer/src/store/radioStore.ts b/src/renderer/src/store/radioStore.ts index c1bfef9..b1875a3 100644 --- a/src/renderer/src/store/radioStore.ts +++ b/src/renderer/src/store/radioStore.ts @@ -35,6 +35,7 @@ export interface FrequencyState { interface RadioState { radios: RadioType[]; + radiosSelected: RadioType[]; pttIsOn: boolean; addRadio: (frequency: number, callsign: string, stationCallsign: string) => void; removeRadio: (frequency: number) => void; @@ -49,6 +50,8 @@ interface RadioState { setTransceiverCountForStationCallsign: (callsign: string, count: number) => void; setPendingDeletion: (frequency: number, value: boolean) => void; reset: () => void; + addOrRemoveRadioToBeDeleted: (radio: RadioType) => void; + clearRadiosToBeDeleted: () => void; } // eslint-disable-next-line @typescript-eslint/no-extraneous-class @@ -76,6 +79,7 @@ export class RadioHelper { const useRadioState = create((set) => ({ radios: [], + radiosSelected: [], pttIsOn: false, addRadio: (frequency, callsign, stationCallsign) => { if (RadioHelper.doesRadioExist(useRadioState.getState().radios, frequency)) { @@ -113,6 +117,22 @@ const useRadioState = create((set) => ({ ].sort((a, b) => radioCompare(a, b, stationCallsign)) })); }, + addOrRemoveRadioToBeDeleted: (radio) => { + if (useRadioState.getState().radiosSelected.some((r) => r.frequency === radio.frequency)) { + set((state) => ({ + radiosSelected: state.radiosSelected.filter((r) => r.frequency !== radio.frequency) + })); + } else { + set((state) => ({ + radiosSelected: [...state.radiosSelected, radio] + })); + } + }, + clearRadiosToBeDeleted: () => { + set(() => ({ + radiosSelected: [] + })); + }, removeRadio: (frequency) => { set((state) => ({ radios: state.radios.filter((radio) => radio.frequency !== frequency) @@ -154,7 +174,8 @@ const useRadioState = create((set) => ({ }, reset: () => { set(() => ({ - radios: [] + radios: [], + radiosSelected: [] })); }, setTransceiverCountForStationCallsign: (callsign, count) => { diff --git a/src/renderer/src/store/utilStore.ts b/src/renderer/src/store/utilStore.ts index 6e7535f..24efa5d 100644 --- a/src/renderer/src/store/utilStore.ts +++ b/src/renderer/src/store/utilStore.ts @@ -10,6 +10,8 @@ interface UtilStore { isWindowMaximised: boolean; hasPtt1BeenSetDuringSetup: boolean; hasPtt2BeenSetDuringSetup: boolean; + isEditMode: boolean; + setIsEditMode: (isEditMode: boolean) => void; setPtt1KeyName: (ptt1KeyName: string) => void; setPtt2KeyName: (ptt2KeyName: string) => void; updateVu: (vu: number, peakVu: number) => void; @@ -30,6 +32,10 @@ const useUtilStore = create((set) => ({ hasPtt2BeenSetDuringSetup: false, isWindowFullscreen: false, isWindowMaximised: false, + isEditMode: false, + setIsEditMode: (isEditMode: boolean) => { + set({ isEditMode }); + }, setPtt1KeyName: (ptt1KeyName: string) => { set({ ptt1KeyName }); }, diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 13efa7c..2659aa1 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -204,7 +204,7 @@ button:disabled { } .structure { - height: calc(100vh - $navbar-height - $focusbar-height); + height: calc(100vh - $navbar-height - $focusbar-height - 2rem); } .text-box-container, @@ -224,6 +224,20 @@ button:disabled { outline: lighten($disabled, 15%) solid 3px; padding: 10px; border-radius: 0.375rem; + overflow: auto; +} + +.box-container::-webkit-scrollbar-track { + padding: 0px 0; + background-color: $dark; +} + +.box-container::-webkit-scrollbar { + width: 5px; +} + +.box-container::-webkit-scrollbar-thumb { + background-color: $body-color; } .freq-box { @@ -236,6 +250,15 @@ button:disabled { padding: 0; } +.focusbar-container { + height: $focusbar-height; + width: 100%; + position: fixed; + bottom: 0; + left: 0; + z-index: 1000; +} + // When changing the max-width property make sure to update the miniModeWidthBreakpoint constant in // the toggleMiniMode method in index.ts as well. @media only screen and (max-width: $mini-mode-width-break-point) { @@ -268,7 +291,7 @@ button:disabled { .radio { margin-right: 10px; - margin-top: 10px; + margin-top: 5px; color: $btn-text-color; background-color: lighten($disabled, 5%); border-color: lighten($disabled, 20%); diff --git a/src/renderer/src/style/variables.scss b/src/renderer/src/style/variables.scss index 917b33d..d204a74 100644 --- a/src/renderer/src/style/variables.scss +++ b/src/renderer/src/style/variables.scss @@ -117,7 +117,7 @@ $modal-border: 2px solid $dark-border; $modal-border-danger: 2px solid $dark-border; $sidebar-width: 250px; $navbar-height: 36px; -$focusbar-height: 30px; +$focusbar-height: 28px; /* buttons */ $btn-padding-x: 7px; From 0d378c19e75bd3456ae3bf095bd21f3c9a305d29 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 26 Oct 2024 22:20:45 +0100 Subject: [PATCH 03/37] this defo was rebase --- backend/extern/afv-native | 2 +- package-lock.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/extern/afv-native b/backend/extern/afv-native index 904f896..c34bb1c 160000 --- a/backend/extern/afv-native +++ b/backend/extern/afv-native @@ -1 +1 @@ -Subproject commit 904f896c672109a9e30cdc3f1df4043000cfd385 +Subproject commit c34bb1c05fd6ba77353dba500a33e627a5ced28e diff --git a/package-lock.json b/package-lock.json index a31be06..f6104fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8196,8 +8196,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-y6VeQp/9SUFAQJNfLk4x5g7erwJORMzITDMOgNm3C/wcwGhnfTQxO4Chz6csIt+H0QLC+Q/iPdRPJR+hHf5XlA==", - "license": "GPL-3.0-only", + "integrity": "sha512-lmecSxriv8u7siGMf83Wh0OEYQbEGaNVOHZz+YCLMZ63hZaxEWWbpAazdj4j5EAqlfdd5fA3wZoIHDZVOfF87A==", "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^1.1.0" From 8213c86687871ef849e5a7f1847ec3d04662c25d Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 26 Oct 2024 22:47:53 +0100 Subject: [PATCH 04/37] maybe cooking maybe not --- src/renderer/src/components/navbar.tsx | 6 ++--- .../src/components/radio/radio-container.tsx | 11 +++++----- .../radio/unicom-guard-container.tsx | 22 +++++++++++++++++++ src/renderer/src/components/rxinfo.tsx | 4 ---- src/renderer/src/style/app.scss | 2 +- 5 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 src/renderer/src/components/radio/unicom-guard-container.tsx diff --git a/src/renderer/src/components/navbar.tsx b/src/renderer/src/components/navbar.tsx index 962b9ed..cf57663 100644 --- a/src/renderer/src/components/navbar.tsx +++ b/src/renderer/src/components/navbar.tsx @@ -102,7 +102,7 @@ const Navbar: React.FC = () => { return ( <> - + @@ -201,9 +201,9 @@ const Navbar: React.FC = () => {
*/} - + {/* - + */}
-
-
- - - - - {/* -
- - Gain: {radioGain.toFixed(0).padStart(3, '0')}% - - -
-
- -
- - {platform === 'linux' && ( + {isNetworkConnected && ( + +
- )} -
-
*/} - - - - {/*
- - + )} - > - {isNetworkConnected ? callsign : 'Not Connected'} - - - + + - - Gain: {radioGain.toFixed(0).padStart(3, '0')}% - - - - {platform === 'linux' && ( - - )} -
*/} {showSettingsModal && ( { diff --git a/src/renderer/src/components/radio/connection-steps.tsx b/src/renderer/src/components/radio/connection-steps.tsx new file mode 100644 index 0000000..b2a005b --- /dev/null +++ b/src/renderer/src/components/radio/connection-steps.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { CheckCircleFill, Gear, Link45deg, Icon, Wifi } from 'react-bootstrap-icons'; + +interface Step { + id: number; + title: string; + description: string; + icon: Icon; + isCompleted: boolean; +} + +interface ConnectionStepsProps { + isNetworkConnected: boolean; + isConnected: boolean; +} + +const ConnectionSteps: React.FC = ({ isNetworkConnected, isConnected }) => { + const steps: Step[] = [ + { + id: 1, + title: 'Network Connection', + description: 'Check your network connectivity', + icon: Wifi, + isCompleted: isNetworkConnected + }, + { + id: 2, + title: 'Ready to Connect', + description: 'System is ready for connection', + icon: Gear, + isCompleted: isNetworkConnected && !isConnected + }, + { + id: 3, + title: 'Connect to Simulator', + description: 'Establish simulator connection', + icon: Link45deg, + isCompleted: isConnected + } + ]; + + return ( +
+
+ {steps.map((step, index) => ( +
+ {/* Connector Line */} + {index < steps.length - 1 && ( +
+ )} + + {/* Step Item */} +
+ {/* Icon Circle */} +
+ {step.isCompleted ? ( + + ) : ( + + )} +
+ + {/* Content */} +
+
+ {step.title} +
+

{step.description}

+
+
+
+ ))} +
+
+ ); +}; + +export default ConnectionSteps; diff --git a/src/renderer/src/components/radio/expanded-rx-info.tsx b/src/renderer/src/components/radio/expanded-rx-info.tsx new file mode 100644 index 0000000..1ebb95e --- /dev/null +++ b/src/renderer/src/components/radio/expanded-rx-info.tsx @@ -0,0 +1,52 @@ +import useRadioState from '@renderer/store/radioStore'; +import React, { useMemo } from 'react'; + +const ExpandedRxInfo: React.FC = () => { + const radios = useRadioState((state) => state.radios); + + const radiosWithRx = useMemo(() => { + return radios.filter((radio) => radio.rx); + }, [radios]); + + return ( +
+
RX LIST
+ {radiosWithRx.map((radio) => ( +
+
+ {radio.callsign}: +
+
+ {radio.lastReceivedCallsign ? ( + +
+ {radio.lastReceivedCallsign} +
+
+ ) : ( + +
--------
+
+ )} +
+
+ ))} +
+ ); +}; + +export default ExpandedRxInfo; diff --git a/src/renderer/src/components/radio/global-radio-gain.tsx b/src/renderer/src/components/radio/global-radio-gain.tsx new file mode 100644 index 0000000..b30bb28 --- /dev/null +++ b/src/renderer/src/components/radio/global-radio-gain.tsx @@ -0,0 +1,75 @@ +import useSessionStore from '@renderer/store/sessionStore'; +import '../../style/GlobalRadio.scss'; +import { useEffect } from 'react'; +import { Configuration } from 'src/shared/config.type'; +const GlobalRadioGain = () => { + const [radioGain, setRadioGain] = useSessionStore((state) => [ + state.radioGain, + state.setRadioGain + ]); + + useEffect(() => { + window.api + .getConfig() + .then((config: Configuration) => { + const gain = config.radioGain || 0.5; + const UiGain = gain * 100 || 50; + + window.api + .SetRadioGain(gain) + .then(() => { + setRadioGain(UiGain); + }) + .catch((err: unknown) => { + console.error(err); + }); + }) + .catch((err: unknown) => { + console.error(err); + }); + }, [setRadioGain]); + + const updateRadioGainValue = (newGain: number) => { + window.api + .SetRadioGain(newGain / 100) + .then(() => { + setRadioGain(newGain); + }) + .catch((err: unknown) => { + console.error(err); + }); + }; + + const handleRadioGainChange = (event: React.ChangeEvent) => { + updateRadioGainValue(event.target.valueAsNumber); + }; + + const handleRadioGainMouseWheel = (event: React.WheelEvent) => { + const newValue = Math.min(Math.max(radioGain + (event.deltaY > 0 ? -1 : 1), 0), 100); + updateRadioGainValue(newValue); + }; + + return ( +
+ + + MAIN + + + + + + +
+ ); +}; +export default GlobalRadioGain; diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index afcdb26..e6c6e87 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -1,31 +1,65 @@ import React, { useMemo } from 'react'; import Radio from './radio'; import useRadioState from '../../store/radioStore'; -import UnicomGuardBar from './unicom-guard'; -import UnicomGuardContainer from './unicom-guard-container'; +import TopBarContainer from './top-bar-container'; +import useSessionStore from '@renderer/store/sessionStore'; +import useUtilStore from '@renderer/store/utilStore'; +import ExpandedRxInfo from './expanded-rx-info'; +import { useMediaQuery } from 'react-responsive'; const RadioContainer: React.FC = () => { const radios = useRadioState((state) => state.radios); - + const [isNetworkConnected] = useSessionStore((state) => [state.isNetworkConnected]); + const isWideScreen = useMediaQuery({ minWidth: '690px' }); const filteredRadios = useMemo(() => { return radios.filter( (radio) => radio.frequency !== 0 && radio.frequency !== 122.8e6 && radio.frequency !== 121.5e6 ); }, [radios]); + const [showExpandedRxInfo] = useUtilStore((state) => [state.showExpandedRxInfo]); + + if (!isNetworkConnected) { + return ( +
+
+ No VATSIM connection found! +
+
+ Ensure you have established a valid connection to the VATSIM network before attempting to + connect to AFV. +
+
+ ); + } return ( <>
- +
-
-
- {filteredRadios.map((radio) => ( - - ))} +
+
+ {filteredRadios.length === 0 ? ( +
+ ) : ( +
+ {filteredRadios.map((radio) => ( + + ))} +
+ )}
+ + {showExpandedRxInfo && isWideScreen && ( +
+ {/* Content for the right box-container */} + +
+ )}
diff --git a/src/renderer/src/components/radio/radio.tsx b/src/renderer/src/components/radio/radio.tsx index d7562d2..6cacaf3 100644 --- a/src/renderer/src/components/radio/radio.tsx +++ b/src/renderer/src/components/radio/radio.tsx @@ -4,7 +4,6 @@ import clsx from 'clsx'; import useErrorStore from '../../store/errorStore'; import useSessionStore from '../../store/sessionStore'; import useUtilStore from '../../store/utilStore'; -import { is } from '@electron-toolkit/utils'; export interface RadioProps { radio: RadioType; diff --git a/src/renderer/src/components/radio/rxinfo.tsx b/src/renderer/src/components/radio/rxinfo.tsx new file mode 100644 index 0000000..704cc3c --- /dev/null +++ b/src/renderer/src/components/radio/rxinfo.tsx @@ -0,0 +1,88 @@ +import useRadioState from '@renderer/store/radioStore'; +import React, { useMemo, useState, useEffect } from 'react'; +import type { RadioType } from '@renderer/store/radioStore'; +import { ArrowsAngleExpand } from 'react-bootstrap-icons'; +import useUtilStore from '@renderer/store/utilStore'; +import clsx from 'clsx'; + +const RxInfo: React.FC = () => { + const radios = useRadioState((state) => state.radios); + const [lastActiveRadio, setLastActiveRadio] = useState(null); + const [showExpandedRxInfo, setShowExpandedRxInfo] = useUtilStore((state) => [ + state.showExpandedRxInfo, + state.setShowExpandedRxInfo + ]); + useEffect(() => { + const currentlyReceiving = radios.find( + (radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx + ); + + if (currentlyReceiving) { + setLastActiveRadio(currentlyReceiving); + } + }, [radios]); + + const displayRadio = useMemo(() => { + const currentlyReceiving = radios.find( + (radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx + ); + return currentlyReceiving ?? lastActiveRadio; + }, [radios, lastActiveRadio]); + + const isCurrentlyReceiving = useMemo(() => { + return radios.some((radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx); + }, [radios]); + + return ( +
+
+
+ RX: +
+
+ {displayRadio ? ( + +
+ {displayRadio.lastReceivedCallsign} +
+
+ ) : ( + +
--------
+
+ )} +
+
+
+ +
+
+ ); + + // return ( + //
+ //
+ // {displayRadio && ( + //
+ // {displayRadio.lastReceivedCallsign} + //
+ // )} + //
+ //
+ // ); +}; + +export default RxInfo; diff --git a/src/renderer/src/components/radio/top-bar-container.tsx b/src/renderer/src/components/radio/top-bar-container.tsx new file mode 100644 index 0000000..709bce8 --- /dev/null +++ b/src/renderer/src/components/radio/top-bar-container.tsx @@ -0,0 +1,36 @@ +import RxInfo from './rxinfo'; +import GlobalRadioGain from './global-radio-gain'; +import UnicomGuardBar from './unicom-guard'; +import { useMediaQuery } from 'react-responsive'; + +const TopBarContainer = () => { + const isWideScreen = useMediaQuery({ minWidth: '790px' }); + + return ( +
+ {/* Main container with the centered content and right-aligned text */} +
+ {/* Left-aligned element */} + {isWideScreen && ( +
+ +
+ )} + + {/* Center element */} +
+ +
+ + {/* Right-aligned element */} + {isWideScreen && ( +
+ +
+ )} +
+
+ ); +}; + +export default TopBarContainer; diff --git a/src/renderer/src/components/radio/unicom-guard-container.tsx b/src/renderer/src/components/radio/unicom-guard-container.tsx deleted file mode 100644 index 998a929..0000000 --- a/src/renderer/src/components/radio/unicom-guard-container.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import RxInfo from '../rxinfo'; -import UnicomGuardBar from './unicom-guard'; - -const UnicomGuardContainer = () => { - return ( -
- {/* Main container with the centered content and right-aligned text */} -
- {/* Center element */} -
- -
- - {/* Right-aligned text absolute positioned */} -
- -
-
-
- ); -}; -export default UnicomGuardContainer; diff --git a/src/renderer/src/components/rxinfo.tsx b/src/renderer/src/components/rxinfo.tsx deleted file mode 100644 index dd02235..0000000 --- a/src/renderer/src/components/rxinfo.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import useRadioState from '@renderer/store/radioStore'; -import React, { useMemo, useState, useEffect } from 'react'; -import type { RadioType } from '@renderer/store/radioStore'; - -const RxInfo: React.FC = () => { - const radios = useRadioState((state) => state.radios); - const [lastActiveRadio, setLastActiveRadio] = useState(null); - - useEffect(() => { - const currentlyReceiving = radios.find( - (radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx - ); - - if (currentlyReceiving) { - setLastActiveRadio(currentlyReceiving); - } - }, [radios]); - - const displayRadio = useMemo(() => { - const currentlyReceiving = radios.find( - (radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx - ); - return currentlyReceiving ?? lastActiveRadio; - }, [radios, lastActiveRadio]); - - const isCurrentlyReceiving = useMemo(() => { - return radios.some((radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx); - }, [radios]); - - return ( -
-
- {displayRadio && ( -
- {displayRadio.lastReceivedCallsign} -
- )} -
-
- ); -}; - -export default RxInfo; diff --git a/src/renderer/src/components/settings-modal/settings-modal.tsx b/src/renderer/src/components/settings-modal/settings-modal.tsx index 544ef08..7e6cacb 100644 --- a/src/renderer/src/components/settings-modal/settings-modal.tsx +++ b/src/renderer/src/components/settings-modal/settings-modal.tsx @@ -25,7 +25,7 @@ const SettingsModal: React.FC = ({ closeModal }) => { const [audioApis, setAudioApis] = useState(Array); const [audioOutputDevices, setAudioOutputDevices] = useState(Array); const [audioInputDevices, setAudioInputDevices] = useState(Array); - const [radioEffects, setRadioEffects] = useState("on"); + const [radioEffects, setRadioEffects] = useState('on'); const [hardwareType, setHardwareType] = useState(0); const [config, setConfig] = useState({} as Configuration); const [alwaysOnTop, setAlwaysOnTop] = useState('never'); @@ -44,7 +44,9 @@ const SettingsModal: React.FC = ({ closeModal }) => { hasPtt1BeenSetDuringSetup, hasPtt2BeenSetDuringSetup, updatePtt1KeySet, - updatePtt2KeySet + updatePtt2KeySet, + showExpandedRxInfo, + setShowExpandedRxInfo ] = useUtilStore((state) => [ state.vu, state.peakVu, @@ -54,7 +56,9 @@ const SettingsModal: React.FC = ({ closeModal }) => { state.hasPtt1BeenSetDuringSetup, state.hasPtt2BeenSetDuringSetup, state.updatePtt1KeySet, - state.updatePtt2KeySet + state.updatePtt2KeySet, + state.showExpandedRxInfo, + state.setShowExpandedRxInfo ]); const [isMicTesting, setIsMicTesting] = useState(false); @@ -68,6 +72,7 @@ const SettingsModal: React.FC = ({ closeModal }) => { setRadioEffects(config.radioEffects); setHardwareType(config.hardwareType); setAlwaysOnTop(config.alwaysOnTop as AlwaysOnTopMode); // Type assertion since the config will never be a boolean at this point + setShowExpandedRxInfo(config.showExpandedRx); }) .catch((err: unknown) => { console.error(err); @@ -172,6 +177,18 @@ const SettingsModal: React.FC = ({ closeModal }) => { setChangesSaved(SaveStatus.Saved); }; + const handleShowExpandedRxChange = (e: React.ChangeEvent) => { + setChangesSaved(SaveStatus.Saving); + if (e.target.value === 'true') { + setShowExpandedRxInfo(true); + window.api.setShowExpandedRx(true); + } else { + setShowExpandedRxInfo(false); + window.api.setShowExpandedRx(false); + } + setChangesSaved(SaveStatus.Saved); + }; + const handleSetPtt = (pttIndex: number) => { if (pttIndex === 1) { updatePtt1KeySet(false); @@ -199,7 +216,7 @@ const SettingsModal: React.FC = ({ closeModal }) => { const radioEffects = e.target.value as RadioEffects; void window.api.SetRadioEffects(radioEffects); setRadioEffects(radioEffects); - setConfig({ ...config, radioEffects: radioEffects}); + setConfig({ ...config, radioEffects: radioEffects }); setChangesSaved(SaveStatus.Saved); }; @@ -254,10 +271,12 @@ const SettingsModal: React.FC = ({ closeModal }) => { > - + + +
diff --git a/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx b/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx index c84d2c2..59182cb 100644 --- a/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx +++ b/src/renderer/src/components/titlebar/session-status/ConnectionStatus.tsx @@ -101,7 +101,7 @@ const ConnectionStatus: React.FC = ({ className = '' }) = : isConnecting ? 'CONNECTING' : callsign - ? `CONNECT AS ${callsign}` + ? `CONNECT` : 'NO ACTIVE CONNECTION'} ); diff --git a/src/renderer/src/store/utilStore.ts b/src/renderer/src/store/utilStore.ts index 24efa5d..e66caf0 100644 --- a/src/renderer/src/store/utilStore.ts +++ b/src/renderer/src/store/utilStore.ts @@ -8,6 +8,7 @@ interface UtilStore { ptt2KeyName: string; isWindowFullscreen: boolean; isWindowMaximised: boolean; + showExpandedRxInfo: boolean; hasPtt1BeenSetDuringSetup: boolean; hasPtt2BeenSetDuringSetup: boolean; isEditMode: boolean; @@ -20,6 +21,7 @@ interface UtilStore { updatePtt2KeySet: (hasPtt2BeenSetDuringSetup: boolean) => void; setWindowFullscreen: (fullscreen: boolean) => void; setWindowMaximised: (maximised: boolean) => void; + setShowExpandedRxInfo: (showExpandedRxInfo: boolean) => void; } const useUtilStore = create((set) => ({ @@ -32,6 +34,7 @@ const useUtilStore = create((set) => ({ hasPtt2BeenSetDuringSetup: false, isWindowFullscreen: false, isWindowMaximised: false, + showExpandedRxInfo: false, isEditMode: false, setIsEditMode: (isEditMode: boolean) => { set({ isEditMode }); @@ -59,6 +62,9 @@ const useUtilStore = create((set) => ({ }, setWindowMaximised: (maximised: boolean): void => { set({ isWindowMaximised: maximised }); + }, + setShowExpandedRxInfo: (showExpandedRxInfo: boolean): void => { + set({ showExpandedRxInfo }); } })); diff --git a/src/renderer/src/style/GlobalRadio.scss b/src/renderer/src/style/GlobalRadio.scss new file mode 100644 index 0000000..676e5a7 --- /dev/null +++ b/src/renderer/src/style/GlobalRadio.scss @@ -0,0 +1,68 @@ +@import 'variables'; + +.unicon-overall-container { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.gain-bar-container { + height: 28px; + line-height: 25px; + width: fit-content; + margin: 10px; + background-color: lighten($disabled, 10%); + border-radius: $card-border-radius; + border-color: lighten($disabled, 20%); + outline: lighten($disabled, 25%) solid 2px; + padding-left: 10px; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; // Added to center items horizontally +} + +.hide-gain-container { + @media screen and (max-width: 465px) { + display: none; + } +} + +.gain-line-item { + margin-right: 10px; +} + +.gain-text { + display: inline-block; + vertical-align: middle; +} + +.sm-button { + height: 19px !important; + width: 30px !important; + min-height: 0 !important; + padding-left: 3px !important; + padding-right: 3px !important; + padding-top: 2px !important; + padding-bottom: 2px !important; + margin: 0 !important; + line-height: 2px !important; + font-size: 14px !important; + outline: darken($info, 15%) solid 1px !important; + margin-left: 5px !important; + + &.btn-success { + outline: darken($success, 15%) solid 1px !important; + } + + &.btn-warning { + outline: darken($warning, 15%) solid 1px !important; + } +} + +.gain-volume-bar { + margin-right: 10px; + width: 50px !important; +} diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss index 7a76aa2..47e30a5 100644 --- a/src/renderer/src/style/UnicomGuard.scss +++ b/src/renderer/src/style/UnicomGuard.scss @@ -22,7 +22,22 @@ display: flex; align-items: center; justify-content: center; // Added to center items horizontally +} +.rx-bar-container { + height: 28px; + line-height: 25px; + width: fit-content; + margin: 5px 0px; + background-color: lighten($disabled, 10%); + border-radius: $card-border-radius; + border-color: lighten($disabled, 20%); + outline: lighten($disabled, 25%) solid 2px; + padding: 0px 5px; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; // Added to center items horizontally } .hide-unicom-container { @@ -40,6 +55,13 @@ vertical-align: middle; } +.rx-text { + display: inline-block; + font-size: 15px; + font-weight: 600; + vertical-align: middle; +} + .sm-button { height: 19px !important; width: 30px !important; @@ -67,3 +89,7 @@ margin-right: 10px; width: 50px !important; } + +.global-volume-bar { + width: 100px !important; +} diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 9a3829a..6a6cff2 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -207,6 +207,10 @@ button:disabled { height: calc(100vh - $navbar-height - $focusbar-height - 1rem); } +.sub-structure { + height: calc(100vh - $navbar-height - $focusbar-height - 35px - 2rem); +} + .text-box-container, .text-box-container:hover { min-height: 25px; @@ -459,3 +463,12 @@ select:disabled { ::-webkit-scrollbar-thumb:hover { background: lighten(rgb(50, 50, 58), 10%); /* Color of the scrollbar thumb on hover */ } + +.radio-text { + font-size: 20px; + font-weight: 600; +} + +.radio-sub-text { + font-size: 13px; +} diff --git a/src/shared/config.type.ts b/src/shared/config.type.ts index da1264f..d4d56a7 100644 --- a/src/shared/config.type.ts +++ b/src/shared/config.type.ts @@ -19,4 +19,6 @@ export interface Configuration { // Boolean is the prior type for this property, AlwaysOnTopMode is the updated type. alwaysOnTop: boolean | AlwaysOnTopMode; radioEffects: RadioEffects; + + showExpandedRx: boolean; } From 235b4ce8e9378f00ec22d9c4b51e203b83852874 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sun, 27 Oct 2024 10:27:26 +0000 Subject: [PATCH 07/37] Fix looks --- .../src/components/radio/expanded-rx-info.tsx | 2 +- .../src/components/radio/radio-container.tsx | 12 +++++++++--- src/renderer/src/style/UnicomGuard.scss | 2 +- src/renderer/src/style/app.scss | 4 ++++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/components/radio/expanded-rx-info.tsx b/src/renderer/src/components/radio/expanded-rx-info.tsx index 1ebb95e..681eb47 100644 --- a/src/renderer/src/components/radio/expanded-rx-info.tsx +++ b/src/renderer/src/components/radio/expanded-rx-info.tsx @@ -24,7 +24,7 @@ const ExpandedRxInfo: React.FC = () => { {radio.callsign}:
{ const radios = useRadioState((state) => state.radios); const [isNetworkConnected] = useSessionStore((state) => [state.isNetworkConnected]); - const isWideScreen = useMediaQuery({ minWidth: '690px' }); + const isWideScreen = useMediaQuery({ minWidth: '790px' }); const filteredRadios = useMemo(() => { return radios.filter( (radio) => radio.frequency !== 0 && radio.frequency !== 122.8e6 && radio.frequency !== 121.5e6 @@ -41,7 +41,7 @@ const RadioContainer: React.FC = () => {
{filteredRadios.length === 0 ? (
@@ -55,7 +55,13 @@ const RadioContainer: React.FC = () => {
{showExpandedRxInfo && isWideScreen && ( -
+
{/* Content for the right box-container */}
diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss index 47e30a5..f785096 100644 --- a/src/renderer/src/style/UnicomGuard.scss +++ b/src/renderer/src/style/UnicomGuard.scss @@ -28,7 +28,7 @@ height: 28px; line-height: 25px; width: fit-content; - margin: 5px 0px; + margin: 10px 0px; background-color: lighten($disabled, 10%); border-radius: $card-border-radius; border-color: lighten($disabled, 20%); diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 6a6cff2..c68aa57 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -306,6 +306,10 @@ button:disabled { width: 205px; } +.radio-list-expanded { + width: 100%; +} + .radio:hover { background-color: lighten($disabled, 10%); } From ab338fce72eb99a147990f851a54097c2bcf1599 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sun, 27 Oct 2024 10:47:37 +0000 Subject: [PATCH 08/37] Fix some stuff --- .../src/components/radio/radio-container.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index fba21f9..2e0466d 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -9,7 +9,10 @@ import { useMediaQuery } from 'react-responsive'; const RadioContainer: React.FC = () => { const radios = useRadioState((state) => state.radios); - const [isNetworkConnected] = useSessionStore((state) => [state.isNetworkConnected]); + const [isConnected, isNetworkConnected] = useSessionStore((state) => [ + state.isConnected, + state.isNetworkConnected + ]); const isWideScreen = useMediaQuery({ minWidth: '790px' }); const filteredRadios = useMemo(() => { return radios.filter( @@ -20,7 +23,7 @@ const RadioContainer: React.FC = () => { if (!isNetworkConnected) { return ( -
+
No VATSIM connection found!
@@ -32,9 +35,22 @@ const RadioContainer: React.FC = () => { ); } + if (!isConnected) { + return ( +
+
+ VATSIM connection found! +
+
+ Click the connect button to establish a connection to AFV. +
+
+ ); + } + return ( <> -
+
From dc6e1ba535f853bfd789e5afc3e2024c59d53296 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sun, 27 Oct 2024 12:23:34 +0000 Subject: [PATCH 09/37] global no select --- src/renderer/src/components/radio/radio-container.tsx | 1 - src/renderer/src/style/app.scss | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index 2e0466d..f506d1f 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -78,7 +78,6 @@ const RadioContainer: React.FC = () => { minWidth: '200px' }} > - {/* Content for the right box-container */}
)} diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index c68aa57..d3b4126 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -476,3 +476,9 @@ select:disabled { .radio-sub-text { font-size: 13px; } + +* { + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} From f52f1400fa8c9093a00933ddf72dbe530c065378 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Mon, 28 Oct 2024 17:43:13 +0000 Subject: [PATCH 10/37] Transparent mini bar --- src/main/index.ts | 57 +++++++++++++------ src/preload/bindings.ts | 6 +- .../src/components/MiniModeToggleButton.tsx | 6 +- src/renderer/src/components/mini.tsx | 32 ++++++++--- .../src/components/titlebar/TitleBar.scss | 14 +++++ src/renderer/src/style/app.scss | 21 +++++-- 6 files changed, 104 insertions(+), 32 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 9957c71..b6b4a8d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -15,7 +15,7 @@ let mainWindow: BrowserWindow; const defaultWindowSize = { width: 800, height: 660 }; const miniModeWidthBreakpoint = 330; // This must match the value for $mini-mode-width-breakpoint in variables.scss. -const defaultMiniModeWidth = 300; // Default width to use for mini mode if the user hasn't explicitly resized it to something else. +const defaultMiniModeWidth = 250; // Default width to use for mini mode if the user hasn't explicitly resized it to something else. // This flag is set to true if the settings dialog should be shown automatically on launch. // This happens when either there's no prior saved config, or the saved config had its audio @@ -75,11 +75,21 @@ const saveWindowBounds = () => { * mode requested. * @param mode The size to restore to: mini or maxi. */ -const restoreWindowBounds = (mode: WindowMode) => { +const restoreWindowBounds = (mode: WindowMode, numOfRadios = 0) => { + const miniModeHeight = 33 + 24 * (numOfRadios === 0 ? 1 : numOfRadios); + const miniModeHeightMin = 22 + 24 * (numOfRadios === 0 ? 1 : numOfRadios); + const savedBounds = mode === 'maxi' ? store.get('bounds') : store.get('miniBounds'); const boundsRectangle = savedBounds as Rectangle; + if (mode === 'mini') { + mainWindow.setMinimumSize(250, miniModeHeightMin); + } else { + mainWindow.setMinimumSize(250, 120); + } + if (savedBounds !== undefined && savedBounds !== null) { const screenArea = screen.getDisplayMatching(boundsRectangle).workArea; + if ( boundsRectangle.x > screenArea.x + screenArea.width || boundsRectangle.x < screenArea.x || @@ -87,26 +97,32 @@ const restoreWindowBounds = (mode: WindowMode) => { boundsRectangle.y > screenArea.y + screenArea.height ) { // Reset window into existing screenarea + const computedHeight = mode === 'mini' ? miniModeHeight : defaultWindowSize.height; mainWindow.setBounds({ x: 0, y: 0, width: defaultWindowSize.width, - height: defaultWindowSize.height + height: computedHeight }); } else { - mainWindow.setBounds(boundsRectangle); + const computedHeight = mode === 'mini' ? miniModeHeight : boundsRectangle.height; + mainWindow.setBounds({ + x: boundsRectangle.x, + y: boundsRectangle.y, + width: boundsRectangle.width, + height: computedHeight + }); + + mainWindow.setSize(boundsRectangle.width, computedHeight); } - } - // Covers the case where the window has never been put in mini-mode before - // and the request came from an explicit "enter mini mode action". In that - // situation just set the window size to the default mini-mode size but - // don't move it. - else if (mode === 'mini') { - mainWindow.setSize(defaultMiniModeWidth, 1); + } else if (mode === 'mini') { + // Handle first-time mini mode + mainWindow.setSize(defaultMiniModeWidth, miniModeHeight); + mainWindow.setMinimumSize(250, 42); // Set minimum size after setting initial size } }; -const toggleMiniMode = () => { +const toggleMiniMode = (numOfRadios = 0) => { // Issue 84: If the window is maximized it has to be unmaximized before // setting the window size to mini-mode otherwise nothing happens. if (mainWindow.isMaximized()) { @@ -120,7 +136,9 @@ const toggleMiniMode = () => { if (isInMiniMode()) { restoreWindowBounds('maxi'); } else { - restoreWindowBounds('mini'); + restoreWindowBounds('mini', numOfRadios); + mainWindow.setVibrancy('fullscreen-ui'); + mainWindow.setBackgroundMaterial('mica'); } }; @@ -133,11 +151,14 @@ const createWindow = (): void => { mainWindow = new BrowserWindow({ height: defaultWindowSize.height, width: defaultWindowSize.width, - minWidth: 210, + minWidth: 250, minHeight: 120, icon, trafficLightPosition: { x: 12, y: 10 }, titleBarStyle: 'hidden', + vibrancy: 'fullscreen-ui', // on MacOS + backgroundMaterial: 'acrylic', // on Windows 11 + // backgroundColor: '#2c2f45', webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false @@ -376,8 +397,8 @@ ipcMain.handle('set-audio-api', (_, audioApi: number) => { configManager.updateConfig({ audioApi }); }); -ipcMain.handle('toggle-mini-mode', () => { - toggleMiniMode(); +ipcMain.handle('toggle-mini-mode', (_, numberOfRadios: number) => { + toggleMiniMode(numberOfRadios); }); // @@ -522,6 +543,10 @@ ipcMain.on('minimise-window', () => { mainWindow.minimize(); }); +ipcMain.on('set-minimum-size', (_, width: number, height: number) => { + mainWindow.setMinimumSize(width, height); +}); + ipcMain.on('close-window', () => { mainWindow.close(); }); diff --git a/src/preload/bindings.ts b/src/preload/bindings.ts index 2a3098b..296fa91 100644 --- a/src/preload/bindings.ts +++ b/src/preload/bindings.ts @@ -100,7 +100,8 @@ export const api = { RequestPttKeyName: (pttIndex: number) => ipcRenderer.invoke('request-ptt-key-name', pttIndex), - toggleMiniMode: () => ipcRenderer.invoke('toggle-mini-mode'), + toggleMiniMode: (numberOfRadios: number) => + ipcRenderer.invoke('toggle-mini-mode', numberOfRadios), dialog: ( type: 'none' | 'info' | 'error' | 'question' | 'warning', @@ -136,6 +137,9 @@ export const api = { return api.onIpc('is-window-maximised', (data) => { callback(data); }); + }, + setMinimumSize: (width: number, height: number): void => { + ipcRenderer.send('set-minimum-size', width, height); } } }; diff --git a/src/renderer/src/components/MiniModeToggleButton.tsx b/src/renderer/src/components/MiniModeToggleButton.tsx index 92437d5..631f167 100644 --- a/src/renderer/src/components/MiniModeToggleButton.tsx +++ b/src/renderer/src/components/MiniModeToggleButton.tsx @@ -1,3 +1,4 @@ +import useRadioState from '@renderer/store/radioStore'; import React, { useCallback } from 'react'; import { Fullscreen, FullscreenExit } from 'react-bootstrap-icons'; @@ -6,11 +7,12 @@ interface MiniModeToggleButtonProps { } const MiniModeToggleButton: React.FC = ({ showRestoreButton }) => { + const [radios] = useRadioState((state) => [state.radios]); const toggleMiniMode = useCallback(() => { - window.api.toggleMiniMode().catch((error: unknown) => { + window.api.toggleMiniMode(radios.filter((r) => r.rx).length).catch((error: unknown) => { console.error(error); }); - }, []); + }, [radios]); return ( - - + +
+ + + +
-
+ +
+
- +
); }; diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index add7437..e66ddab 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -303,30 +303,119 @@ button:disabled { } .radio { - margin-right: 10px; - margin-top: 5px; + width: 205px; + height: 90px; + margin: 5px; + padding: 10px; color: $btn-text-color; background-color: lighten($disabled, 5%); - border-color: lighten($disabled, 20%); - outline: lighten($disabled, 25%) solid 2px; - padding: 10px; border-radius: 0.375rem; - height: 90px; - width: 205px; -} + outline: lighten($disabled, 25%) solid 2px; + transition: background-color 0.2s ease; -.radio-list-expanded { - width: 100%; + &:hover { + background-color: lighten($disabled, 10%); + } + + // Main layout container + .radio-content { + height: 100%; + display: flex; + gap: 4%; + } + + // Left side with frequency and controls + .radio-left { + width: 48%; + height: 100%; + display: flex; + flex-direction: column; + gap: 4%; + } + + // Right side with RX/TX + .radio-right { + width: 48%; + height: 100%; + display: flex; + flex-direction: column; + gap: 8%; + } + + // Header button + .radio-header { + width: 100%; + height: 45%; + margin-bottom: 4%; + display: flex; + justify-content: center; + align-items: center; + padding: 0; + background: transparent; + border: none; + outline: none; + cursor: pointer; + + .radio-text-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .frequency { + font-size: 0.9rem; + line-height: 1; + font-weight: 500; + } + + .callsign { + font-size: 0.85rem; + line-height: 1; + font-weight: normal; + } + } + + .radio-controls { + height: 45%; + display: flex; + gap: 10%; + + .control-btn { + flex: 1; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + } + + .radio-button { + width: 100%; + height: 45%; + display: flex; + justify-content: center; + align-items: center; + } } -.radio:hover { - background-color: lighten($disabled, 10%); +// Grid container for multiple radios +.radio-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(205px, 1fr)); + gap: 10px; + padding: 10px; } -.radio-btn { - margin: 5px; - width: 100%; - height: 100%; +// Media queries +@media (max-width: $mini-mode-width-break-point) { + .radio-grid { + grid-template-columns: 1fr; + } + + .radio { + width: 100%; + } } select:disabled { @@ -383,6 +472,22 @@ select:disabled { cursor: pointer; } +.btn-no-interact { + border: none; + outline: none; + cursor: pointer; + background: transparent; + width: 100%; + padding: 0.25rem; + + &:focus, + &:hover, + &:active { + border: none; + outline: none; + } +} + .licenses { font-size: 10px; } From 4112bf77a82488a192e53dfc0cf7fedc6f8b4f05 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Fri, 1 Nov 2024 11:22:11 +0000 Subject: [PATCH 17/37] Tidy everything up --- .../components/radio/global-radio-gain.tsx | 37 +++++++++---------- .../src/components/radio/unicom-guard.tsx | 5 +-- src/renderer/src/style/UnicomGuard.scss | 14 +++++-- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/components/radio/global-radio-gain.tsx b/src/renderer/src/components/radio/global-radio-gain.tsx index b30bb28..8777968 100644 --- a/src/renderer/src/components/radio/global-radio-gain.tsx +++ b/src/renderer/src/components/radio/global-radio-gain.tsx @@ -50,25 +50,24 @@ const GlobalRadioGain = () => { }; return ( -
- - - MAIN - - - - - - +
+
+ +
); }; diff --git a/src/renderer/src/components/radio/unicom-guard.tsx b/src/renderer/src/components/radio/unicom-guard.tsx index ab37d5a..9176c3d 100644 --- a/src/renderer/src/components/radio/unicom-guard.tsx +++ b/src/renderer/src/components/radio/unicom-guard.tsx @@ -13,10 +13,7 @@ const UnicomGuardBar = () => { state.addRadio, state.removeRadio ]); - const [isConnected, isAtc] = useSessionStore((state) => [ - state.isConnected, - state.isAtc - ]); + const [isConnected, isAtc] = useSessionStore((state) => [state.isConnected, state.isAtc]); const [localRadioGain, setLocalRadioGain] = useState(50); diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss index f785096..486bea0 100644 --- a/src/renderer/src/style/UnicomGuard.scss +++ b/src/renderer/src/style/UnicomGuard.scss @@ -9,8 +9,7 @@ } .unicom-bar-container { - height: 28px; - line-height: 25px; + height: 29px; width: fit-content; margin: 10px; background-color: lighten($disabled, 10%); @@ -25,8 +24,7 @@ } .rx-bar-container { - height: 28px; - line-height: 25px; + height: 29px; width: fit-content; margin: 10px 0px; background-color: lighten($disabled, 10%); @@ -41,18 +39,25 @@ } .hide-unicom-container { + display: flex; + align-items: center; + @media screen and (max-width: 465px) { display: none; } } .unicom-line-item { + display: flex; + align-items: center; margin-right: 10px; } .unicom-text { display: inline-block; vertical-align: middle; + height: 100%; + align-items: center; } .rx-text { @@ -91,5 +96,6 @@ } .global-volume-bar { + height: 23px; width: 100px !important; } From df986cac4d03b19079fc9ed0bb421e4ba0f1a8ba Mon Sep 17 00:00:00 2001 From: George Barlow Date: Fri, 1 Nov 2024 11:30:07 +0000 Subject: [PATCH 18/37] Clean up some css --- src/renderer/src/components/radio/rxinfo.tsx | 2 +- src/renderer/src/style/UnicomGuard.scss | 4 ++++ src/renderer/src/style/app.scss | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/radio/rxinfo.tsx b/src/renderer/src/components/radio/rxinfo.tsx index 704cc3c..4133c05 100644 --- a/src/renderer/src/components/radio/rxinfo.tsx +++ b/src/renderer/src/components/radio/rxinfo.tsx @@ -60,7 +60,7 @@ const RxInfo: React.FC = () => {
-
+
+
+ { + closeModal(); + }} + />
-
+
{ closeModal(); diff --git a/src/renderer/src/components/delete-multiple-radios.tsx b/src/renderer/src/components/delete-multiple-radios.tsx index 9a5c699..42fcbaf 100644 --- a/src/renderer/src/components/delete-multiple-radios.tsx +++ b/src/renderer/src/components/delete-multiple-radios.tsx @@ -1,21 +1,33 @@ import useRadioState from '@renderer/store/radioStore'; import useSessionStore from '@renderer/store/sessionStore'; +import useUtilStore from '@renderer/store/utilStore'; import React from 'react'; import { TrashFill } from 'react-bootstrap-icons'; const DeleteMultipleRadios: React.FC = () => { const [isConnected] = useSessionStore((state) => [state.isConnected]); - const [radiosToBeDeleted, removeRadio, setPendingDeletion] = useRadioState((state) => [ + const [radiosToBeDeleted, radios, removeRadio, setPendingDeletion] = useRadioState((state) => [ state.radiosSelected, + state.radios, state.removeRadio, state.setPendingDeletion ]); + const [setIsEditMode] = useUtilStore((state) => [state.setIsEditMode]); + const handleDeleteRadios = () => { - radiosToBeDeleted.forEach((radio) => { - setPendingDeletion(radio.frequency, true); - awaitEndOfRxForDeletion(radio.frequency); - }); + if (radiosToBeDeleted.length == 0) { + radios.forEach((radio) => { + setPendingDeletion(radio.frequency, false); + awaitEndOfRxForDeletion(radio.frequency); + }); + } else { + radiosToBeDeleted.forEach((radio) => { + setPendingDeletion(radio.frequency, true); + awaitEndOfRxForDeletion(radio.frequency); + }); + } + setIsEditMode(false); }; const awaitEndOfRxForDeletion = (frequency: number): void => { diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index f506d1f..13a2b9f 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -6,6 +6,8 @@ import useSessionStore from '@renderer/store/sessionStore'; import useUtilStore from '@renderer/store/utilStore'; import ExpandedRxInfo from './expanded-rx-info'; import { useMediaQuery } from 'react-responsive'; +import AddStation from '../sidebar/add-station'; +import AddFrequency from '../sidebar/add-frequency'; const RadioContainer: React.FC = () => { const radios = useRadioState((state) => state.radios); @@ -49,18 +51,30 @@ const RadioContainer: React.FC = () => { } return ( - <> -
-
- -
+
+
+ +
-
+
+
{filteredRadios.length === 0 ? ( -
+
+
+
+ +
+
+ +
+
+
) : (
{filteredRadios.map((radio) => ( @@ -83,8 +97,7 @@ const RadioContainer: React.FC = () => { )}
- +
); }; - export default RadioContainer; diff --git a/src/renderer/src/components/radio/top-bar-container.tsx b/src/renderer/src/components/radio/top-bar-container.tsx index aaa35a6..23b786b 100644 --- a/src/renderer/src/components/radio/top-bar-container.tsx +++ b/src/renderer/src/components/radio/top-bar-container.tsx @@ -7,7 +7,7 @@ const TopBarContainer = () => { const isWideScreen = useMediaQuery({ minWidth: '790px' }); return ( -
+
{/* Main container with the centered content and right-aligned text */}
{/* Left-aligned element */} diff --git a/src/renderer/src/components/refresh-multiple-radios.tsx b/src/renderer/src/components/refresh-multiple-radios.tsx index 25074a5..a03169e 100644 --- a/src/renderer/src/components/refresh-multiple-radios.tsx +++ b/src/renderer/src/components/refresh-multiple-radios.tsx @@ -1,5 +1,6 @@ import useRadioState from '@renderer/store/radioStore'; import useSessionStore from '@renderer/store/sessionStore'; +import useUtilStore from '@renderer/store/utilStore'; import React from 'react'; import { ArrowClockwise } from 'react-bootstrap-icons'; @@ -7,6 +8,8 @@ const RefreshMultipleRadios: React.FC = () => { const [isConnected] = useSessionStore((state) => [state.isConnected]); const [radiosSelected] = useRadioState((state) => [state.radiosSelected]); + const [setIsEditMode] = useUtilStore((state) => [state.setIsEditMode]); + const refreshMultipleRadios = () => { radiosSelected.forEach((radio) => { if (radio.callsign === 'MANUAL') { @@ -14,6 +17,7 @@ const RefreshMultipleRadios: React.FC = () => { } void window.api.RefreshStation(radio.callsign); }); + setIsEditMode(false); }; return ( diff --git a/src/renderer/src/components/sidebar/add-frequency.tsx b/src/renderer/src/components/sidebar/add-frequency.tsx index af54ccb..1487210 100644 --- a/src/renderer/src/components/sidebar/add-frequency.tsx +++ b/src/renderer/src/components/sidebar/add-frequency.tsx @@ -3,7 +3,7 @@ import useRadioState, { RadioHelper } from '../../store/radioStore'; import useSessionStore from '../../store/sessionStore'; export interface AddFrequencyProps { - onAddFrequency: () => void; + onAddFrequency?: () => void; } const AddFrequency: React.FC = ({ onAddFrequency }) => { @@ -42,7 +42,9 @@ const AddFrequency: React.FC = ({ onAddFrequency }) => { frequencyInputRef.current.value = ''; setPreviousValue(''); setReadyToAdd(false); - onAddFrequency(); + if (onAddFrequency) { + onAddFrequency(); + } }; const checkFrequency = (e: React.ChangeEvent) => { diff --git a/src/renderer/src/components/sidebar/add-station.tsx b/src/renderer/src/components/sidebar/add-station.tsx new file mode 100644 index 0000000..85b2aeb --- /dev/null +++ b/src/renderer/src/components/sidebar/add-station.tsx @@ -0,0 +1,63 @@ +import React, { useRef, useState } from 'react'; +import useSessionStore from '../../store/sessionStore'; + +export interface AddStationProps { + className?: string; + style?: React.CSSProperties; + onAddStation?: () => void; +} + +const AddStation: React.FC = ({ className, style, onAddStation }) => { + const [readyToAdd, setReadyToAdd] = useState(false); + const [isConnected] = useSessionStore((state) => [state.version, state.isConnected]); + + const stationInputRef = useRef(null); + + const addStation = () => { + if (!readyToAdd || !isConnected) { + return; + } + + const callsign = stationInputRef.current?.value.toUpperCase(); + if (!callsign?.match(/^[A-Z0-9_ -]+$/) || !stationInputRef.current) { + return; + } + + void window.api.GetStation(callsign); + stationInputRef.current.value = ''; + setReadyToAdd(false); + + if (onAddStation) { + onAddStation(); + } + }; + + return ( +
+
Add a Station
+ { + e.target.value.length !== 0 ? setReadyToAdd(true) : setReadyToAdd(false); + }} + onKeyDown={(e) => { + e.key === 'Enter' && addStation(); + }} + > + + +
+ ); +}; + +export default AddStation; diff --git a/src/renderer/src/components/titlebar/TitleBar.scss b/src/renderer/src/components/titlebar/TitleBar.scss index b215047..e4550ec 100644 --- a/src/renderer/src/components/titlebar/TitleBar.scss +++ b/src/renderer/src/components/titlebar/TitleBar.scss @@ -37,12 +37,6 @@ $transition-speed: 0.1s; font-weight: 600; } -.rx-text { - font-size: 16px; - font-family: GlobalVars.$font-medium !important; - font-weight: 600 !important; -} - .rx-text-nofont { font-size: 16px; font-family: GlobalVars.$font-medium !important; diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss index 9cdb369..06de135 100644 --- a/src/renderer/src/style/UnicomGuard.scss +++ b/src/renderer/src/style/UnicomGuard.scss @@ -9,7 +9,7 @@ } .unicom-bar-container { - height: 30px; + height: 28px; width: fit-content; // margin: 10px; background-color: lighten($disabled, 10%); @@ -23,11 +23,11 @@ } .rx-info-expand { - height: 30px; + height: 28px; } .rx-bar-container { - height: 30px; + height: 29px; width: fit-content; margin: 10px 0px; background-color: lighten($disabled, 10%); @@ -64,7 +64,7 @@ .rx-text { display: inline-block; - font-size: 14px; + font-size: 15px; font-weight: 600; vertical-align: middle; } diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index ed1435b..81ff303 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -204,11 +204,15 @@ button:disabled { } .structure { - height: calc(100vh - $navbar-height - $focusbar-height - 1rem); + height: calc(100vh - $navbar-height); } .sub-structure { - height: calc(100vh - $navbar-height - $focusbar-height - 35px - 3rem); + height: calc(100% - $focusbar-height); +} + +.sub-sub-structure { + height: calc(100vh - $focusbar-height - $navbar-height - 60px - 1rem); } .text-box-container, From 52f75df436264c4679eab14ce2617a7845bab155 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Fri, 1 Nov 2024 18:07:33 +0000 Subject: [PATCH 24/37] Fix css --- src/renderer/src/components/radio/rxinfo.tsx | 7 ++++++- src/renderer/src/style/UnicomGuard.scss | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/components/radio/rxinfo.tsx b/src/renderer/src/components/radio/rxinfo.tsx index abcf1bd..5e31663 100644 --- a/src/renderer/src/components/radio/rxinfo.tsx +++ b/src/renderer/src/components/radio/rxinfo.tsx @@ -39,7 +39,12 @@ const RxInfo: React.FC = () => {
RX:
-
+
{displayRadio ? (
Date: Fri, 1 Nov 2024 18:08:58 +0000 Subject: [PATCH 25/37] autoFocus goes so hard --- src/renderer/src/components/sidebar/add-station.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/components/sidebar/add-station.tsx b/src/renderer/src/components/sidebar/add-station.tsx index 85b2aeb..c4c5b88 100644 --- a/src/renderer/src/components/sidebar/add-station.tsx +++ b/src/renderer/src/components/sidebar/add-station.tsx @@ -47,6 +47,7 @@ const AddStation: React.FC = ({ className, style, onAddStation onKeyDown={(e) => { e.key === 'Enter' && addStation(); }} + autoFocus > diff --git a/src/renderer/src/components/radio/rxinfo.tsx b/src/renderer/src/components/radio/rxinfo.tsx index 5e31663..1b4de1a 100644 --- a/src/renderer/src/components/radio/rxinfo.tsx +++ b/src/renderer/src/components/radio/rxinfo.tsx @@ -4,6 +4,7 @@ import type { RadioType } from '@renderer/store/radioStore'; import { ArrowsAngleExpand } from 'react-bootstrap-icons'; import useUtilStore from '@renderer/store/utilStore'; import clsx from 'clsx'; +import { useMediaQuery } from 'react-responsive'; const RxInfo: React.FC = () => { const radios = useRadioState((state) => state.radios); @@ -12,6 +13,8 @@ const RxInfo: React.FC = () => { state.showExpandedRxInfo, state.setShowExpandedRxInfo ]); + const isWideScreen = useMediaQuery({ minWidth: '895px' }); + useEffect(() => { const currentlyReceiving = radios.find( (radio) => radio.rx && radio.lastReceivedCallsign && radio.currentlyRx @@ -63,17 +66,19 @@ const RxInfo: React.FC = () => { )}
-
- -
+ {isWideScreen && ( +
+ +
+ )}
); diff --git a/src/renderer/src/components/radio/top-bar-container.tsx b/src/renderer/src/components/radio/top-bar-container.tsx index 23b786b..4ddd456 100644 --- a/src/renderer/src/components/radio/top-bar-container.tsx +++ b/src/renderer/src/components/radio/top-bar-container.tsx @@ -4,14 +4,15 @@ import UnicomGuardBar from './unicom-guard'; import { useMediaQuery } from 'react-responsive'; const TopBarContainer = () => { - const isWideScreen = useMediaQuery({ minWidth: '790px' }); + const isMediumScreen = useMediaQuery({ minWidth: '765px' }); + const isSmallScreen = useMediaQuery({ minWidth: '630px' }); return (
{/* Main container with the centered content and right-aligned text */}
{/* Left-aligned element */} - {isWideScreen && ( + {isMediumScreen && (
@@ -23,7 +24,7 @@ const TopBarContainer = () => {
{/* Right-aligned element */} - {isWideScreen && ( + {isSmallScreen && (
diff --git a/src/renderer/src/components/radio/unicom-guard.tsx b/src/renderer/src/components/radio/unicom-guard.tsx index 25ab264..22b303f 100644 --- a/src/renderer/src/components/radio/unicom-guard.tsx +++ b/src/renderer/src/components/radio/unicom-guard.tsx @@ -5,6 +5,7 @@ import clsx from 'clsx'; import useSessionStore from '@renderer/store/sessionStore'; import useErrorStore from '@renderer/store/errorStore'; import { GuardFrequency, UnicomFrequency } from '../../../../shared/common'; +import { useMediaQuery } from 'react-responsive'; const UnicomGuardBar = () => { const [radios, setRadioState, addRadio, removeRadio] = useRadioState((state) => [ @@ -15,6 +16,8 @@ const UnicomGuardBar = () => { ]); const [isConnected, isAtc] = useSessionStore((state) => [state.isConnected, state.isAtc]); + const isReducedSize = useMediaQuery({ maxWidth: '895px' }); + const [localRadioGain, setLocalRadioGain] = useState(50); const postError = useErrorStore((state) => state.postError); @@ -219,7 +222,7 @@ const UnicomGuardBar = () => { return (
- + UNICOM - + + {!isReducedSize && ( + + VOLUME + + )} { + const [radios] = useRadioState((state) => [state.radios]); + const [isConnected] = useSessionStore((state) => [state.isConnected]); + const [platform] = useUtilStore((state) => [state.platform]); + const [transparencyMiniMode] = useUtilStore((state) => [state.transparentMiniMode]); + + const isMiniMode = useMediaQuery({ maxWidth: '455px' }); + const isApproachingMiniMode = useMediaQuery({ maxWidth: '560px', maxHeight: '240px' }); + + const [previousWindowState, setPreviousWindowState] = useState({ + wasConnected: false, + wasInMiniMode: false, + wasApproachingMini: false + }); + + const REGULAR_SIZE = { width: 530, height: 240 }; + const MINI_SIZE = { width: 250, height: 120 }; + + const calculateMiniModeHeight = () => { + const activeRadios = radios.filter((r) => r.rx).length; + return 22 + 24 * Math.max(activeRadios, 1); + }; + + const updateWindowSize = (width, height) => { + window.api.window.setMinimumSize(width as number, height as number); + }; + + const updateWindowAppearance = (inMiniMode) => { + if (platform === 'darwin') { + window.api.window.setWindowButtonVisibility(!inMiniMode); + } + + document.body.style.backgroundColor = + inMiniMode && transparencyMiniMode ? 'transparent' : '#2c2f45'; + }; + + useEffect(() => { + const handleWindowStateChange = () => { + // Case 1: Connection state change + // if (isConnected !== previousWindowState.wasConnected) { + // console.log('Case 1'); + // updateWindowSize( + // isConnected ? MINI_SIZE.width : REGULAR_SIZE.width, + // isConnected ? MINI_SIZE.height : REGULAR_SIZE.height + // ); + // setPreviousWindowState((prev) => ({ ...prev, wasConnected: isConnected })); + // } + + // // Case 2: User is approaching mini mode // Fix this + // if (isApproachingMiniMode && isConnected && !isMiniMode) { + // console.log('Case 2', MINI_SIZE.width, MINI_SIZE.height); + // updateWindowSize(MINI_SIZE.width, MINI_SIZE.height); + // } + + // Case 3: User has entered mini mode + if (isMiniMode && !previousWindowState.wasInMiniMode) { + const miniHeight = calculateMiniModeHeight(); + + updateWindowSize(MINI_SIZE.width, miniHeight); + updateWindowAppearance(true); + setPreviousWindowState((prev) => ({ ...prev, wasInMiniMode: true })); + } + + // Case 4: User has exited mini mode + if (!isMiniMode && previousWindowState.wasInMiniMode) { + updateWindowSize(MINI_SIZE.width, MINI_SIZE.height); + updateWindowAppearance(false); + setPreviousWindowState((prev) => ({ ...prev, wasInMiniMode: false })); + } + }; + + handleWindowStateChange(); + }, [ + isConnected, + isMiniMode, + isApproachingMiniMode, + radios, + platform, + transparencyMiniMode, + previousWindowState + ]); + + return { + isMiniMode, + isApproachingMiniMode, + isConnected + }; +}; + +export default useMiniModeManager; diff --git a/src/renderer/src/store/sessionStore.ts b/src/renderer/src/store/sessionStore.ts index 65fabde..ad8634c 100644 --- a/src/renderer/src/store/sessionStore.ts +++ b/src/renderer/src/store/sessionStore.ts @@ -10,6 +10,8 @@ interface sessionStore { frequency: number; radioGain: number; stationCallsign: string; + connectTimestamp: number | null; + setCallsign: (callsign: string) => void; setIsAtc: (isAtc: boolean) => void; setIsConnected: (isConnected: boolean) => void; @@ -34,6 +36,8 @@ const useSessionStore = create((set) => ({ pttKeyName: '', radioGain: 50, stationCallsign: '', + connectTimestamp: null, + setCallsign: (callsign) => { set({ callsign }); }, @@ -42,6 +46,7 @@ const useSessionStore = create((set) => ({ }, setIsConnected: (isConnected) => { set({ isConnected }); + set({ connectTimestamp: isConnected ? Date.now() : null }); }, setIsConnecting: (isConnecting) => { set({ isConnecting }); diff --git a/src/renderer/src/store/utilStore.ts b/src/renderer/src/store/utilStore.ts index 0d6904f..cd3f20a 100644 --- a/src/renderer/src/store/utilStore.ts +++ b/src/renderer/src/store/utilStore.ts @@ -14,6 +14,7 @@ interface UtilStore { isEditMode: boolean; pendingRestart: boolean; transparentMiniMode: boolean; + time: Date; setIsEditMode: (isEditMode: boolean) => void; setPtt1KeyName: (ptt1KeyName: string) => void; setPtt2KeyName: (ptt2KeyName: string) => void; @@ -26,6 +27,7 @@ interface UtilStore { setShowExpandedRxInfo: (showExpandedRxInfo: boolean) => void; setTransparentMiniMode: (transparentMiniMode: boolean) => void; setPendingRestart: (pendingRestart: boolean) => void; + setTime: (time: Date) => void; } const useUtilStore = create((set) => ({ @@ -42,6 +44,7 @@ const useUtilStore = create((set) => ({ isEditMode: false, transparentMiniMode: false, pendingRestart: false, + time: new Date(), setIsEditMode: (isEditMode: boolean) => { set({ isEditMode }); }, @@ -77,6 +80,9 @@ const useUtilStore = create((set) => ({ }, setPendingRestart: (pendingRestart: boolean): void => { set({ pendingRestart }); + }, + setTime(time: Date): void { + set({ time }); } })); diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss index 659ed4a..f4f2e38 100644 --- a/src/renderer/src/style/UnicomGuard.scss +++ b/src/renderer/src/style/UnicomGuard.scss @@ -30,7 +30,7 @@ .rx-bar-container { height: 29px; width: fit-content; - margin: 10px 0px; + margin: 7.5px 0px; line-height: 29px; background-color: lighten($disabled, 10%); border-radius: $card-border-radius; @@ -96,7 +96,7 @@ .unicom-volume-bar { margin-right: 10px; - width: 50px !important; + width: 80px !important; } .global-volume-bar { diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 81ff303..fc80c47 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -271,6 +271,7 @@ button:disabled { height: $focusbar-height; width: 100%; position: fixed; + padding: 0 4px; bottom: 0; left: 0; z-index: 1000; @@ -310,10 +311,14 @@ button:disabled { width: 100%; } +.radio-inactive { + background-color: $disabled !important; +} + .radio { width: 205px; height: 90px; - margin: 5px; + // margin: 5px; padding: 10px; color: $btn-text-color; background-color: lighten($disabled, 5%); @@ -329,7 +334,7 @@ button:disabled { .radio-content { height: 100%; display: flex; - gap: 4%; + gap: 5%; } // Left side with frequency and controls @@ -338,7 +343,7 @@ button:disabled { height: 100%; display: flex; flex-direction: column; - gap: 4%; + gap: 5%; } // Right side with RX/TX @@ -347,7 +352,7 @@ button:disabled { height: 100%; display: flex; flex-direction: column; - gap: 8%; + gap: 10%; } // Header button @@ -369,6 +374,7 @@ button:disabled { flex-direction: column; align-items: center; justify-content: center; + gap: 2px; } .frequency { @@ -501,7 +507,11 @@ select:disabled { } .licenses { - font-size: 10px; + font-size: 11px; +} + +.elapsed-time { + font-size: 13px; } .alert-popup { From f4ac5a357686267d86d9bb3d47e633c36610866f Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 11:03:36 +0000 Subject: [PATCH 28/37] Stupid lint --- src/renderer/src/helpers/useMiniModeManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/helpers/useMiniModeManager.ts b/src/renderer/src/helpers/useMiniModeManager.ts index 5d58565..138dd9c 100644 --- a/src/renderer/src/helpers/useMiniModeManager.ts +++ b/src/renderer/src/helpers/useMiniModeManager.ts @@ -19,7 +19,7 @@ const useMiniModeManager = () => { wasApproachingMini: false }); - const REGULAR_SIZE = { width: 530, height: 240 }; + // const REGULAR_SIZE = { width: 530, height: 240 }; const MINI_SIZE = { width: 250, height: 120 }; const calculateMiniModeHeight = () => { From 1baa6768d343229cababaf59f4435bc501b2294f Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 13:06:08 +0000 Subject: [PATCH 29/37] Update config --- src/main/config.ts | 52 +++++++++++++++++++-------------------- src/shared/config.type.ts | 1 - 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/main/config.ts b/src/main/config.ts index e395d9e..3cae1b9 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -5,7 +5,7 @@ import { AlwaysOnTopMode, Configuration, RadioEffects } from '../shared/config.t // Used to check for older settings that need upgrading. This should get // increased any time the Configuration object has a breaking change. -export const currentSettingsVersion = 2; +export const currentSettingsVersion = 3; // Default application configuration. Used as a fallback when any of the properties // are missing from the saved configuration. @@ -136,34 +136,32 @@ class ConfigManager { */ private V1ToV2(config: Configuration) { // Don't migrate v2 or newer configs. - if (config.version && config.version >= 2) { - return config; + if (!config.version || config.version < 2) { + // If the audio api isn't set then it gets wiped out so the user is forced to reset + // the audio settings. + if (config.audioApi !== -1) { + config.audioApi = defaultConfiguration.audioApi; + config.audioInputDeviceId = defaultConfiguration.audioInputDeviceId; + config.headsetOutputDeviceId = defaultConfiguration.headsetOutputDeviceId; + config.speakerOutputDeviceId = defaultConfiguration.speakerOutputDeviceId; + + dialog.showMessageBoxSync({ + type: 'warning', + message: + 'Your audio settings have been reset. Please re-configure your audio devices in the settings.', + buttons: ['OK'] + }); + } + + // Upgrade the alwaysOnTop property from yes/no to the three mode version + if (typeof config.alwaysOnTop === 'boolean') { + config.alwaysOnTop ? (config.alwaysOnTop = 'always') : (config.alwaysOnTop = 'never'); + } + + // Migration complete + config.version = 2; } - // If the audio api isn't set then it gets wiped out so the user is forced to reset - // the audio settings. - if (config.audioApi !== -1) { - config.audioApi = defaultConfiguration.audioApi; - config.audioInputDeviceId = defaultConfiguration.audioInputDeviceId; - config.headsetOutputDeviceId = defaultConfiguration.headsetOutputDeviceId; - config.speakerOutputDeviceId = defaultConfiguration.speakerOutputDeviceId; - - dialog.showMessageBoxSync({ - type: 'warning', - message: - 'Your audio settings have been reset. Please re-configure your audio devices in the settings.', - buttons: ['OK'] - }); - } - - // Upgrade the alwaysOnTop property from yes/no to the three mode version - if (typeof config.alwaysOnTop === 'boolean') { - config.alwaysOnTop ? (config.alwaysOnTop = 'always') : (config.alwaysOnTop = 'never'); - } - - // Migration complete - config.version = 2; - return config; } diff --git a/src/shared/config.type.ts b/src/shared/config.type.ts index 74a7838..b6d8d7a 100644 --- a/src/shared/config.type.ts +++ b/src/shared/config.type.ts @@ -21,6 +21,5 @@ export interface Configuration { radioEffects: RadioEffects; showExpandedRx: boolean; - transparentMiniMode: boolean; } From 01f2f0e7aea93166cf2746b0ae1af850dd2f7911 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 17:25:59 +0000 Subject: [PATCH 30/37] QoL improvements --- .gitignore | 1 + .vscode/launch.json | 6 + .vscode/settings.json | 3 + package-lock.json | 138 ++++++++++++++++-- .../add-station-model/station-modal.tsx | 4 +- src/renderer/src/components/bootstrap.tsx | 2 + src/renderer/src/components/navbar.tsx | 62 ++++---- .../components/radio/global-radio-gain.tsx | 2 +- .../src/components/radio/radio-container.tsx | 13 +- src/renderer/src/components/radio/rxinfo.tsx | 9 +- .../src/components/sidebar/add-frequency.tsx | 2 +- .../session-status/ConnectionStatus.tsx | 2 +- src/renderer/src/style/app.scss | 6 +- 13 files changed, 188 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index 386035b..98d1f19 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ lib-cov coverage *.lcov +.vs # nyc test coverage .nyc_output diff --git a/.vscode/launch.json b/.vscode/launch.json index 0b6b9a6..ad09500 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,6 +25,12 @@ "presentation": { "hidden": true } + }, + { + "type": "lldb", + "request": "attach", + "name": "Debug Electron Renderer", + "pid": "${command:pickProcess}" } ], "compounds": [ diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c05394..adcec78 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,8 @@ }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "files.associations": { + "xstring": "cpp" } } diff --git a/package-lock.json b/package-lock.json index d9750b1..7d7446d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,11 @@ "bootstrap-typeahead": "^2.3.2", "clsx": "^2.1.1", "electron-store": "^8.2.0", + "electron-updater": "^6.3.9", "react": "^18.3.1", "react-bootstrap-icons": "^1.11.4", "react-dom": "^18.3.1", + "react-responsive": "^10.0.0", "scss": "^0.2.4", "trackaudio-afv": "file:backend/trackaudio-afv-1.0.0.tgz", "use-debounce": "^10.0.1", @@ -35,7 +37,7 @@ "@types/react": "^18.3.3", "@vitejs/plugin-react": "^4.0.0", "electron": "^32.0.2", - "electron-builder": "24.13.3", + "electron-builder": "^24.13.3", "electron-vite": "^2.0.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.2", @@ -2634,8 +2636,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", @@ -3613,6 +3614,11 @@ "node": ">= 8" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -4191,6 +4197,65 @@ "dev": true, "license": "ISC" }, + "node_modules/electron-updater": { + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.3.9.tgz", + "integrity": "sha512-2PJNONi+iBidkoC5D1nzT9XqsE8Q1X28Fn6xRQhO3YX8qRRyJ3mkV4F1aQsuRnYPqq6Hw+E51y27W75WgDoofw==", + "dependencies": { + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.6.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.2.10", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz", + "integrity": "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/electron-vite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.0.0.tgz", @@ -5529,6 +5594,11 @@ "node": ">= 6" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -6168,7 +6238,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -6265,8 +6334,7 @@ "node_modules/lazy-val": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==" }, "node_modules/lazystream": { "version": "1.0.1", @@ -6362,6 +6430,11 @@ "dev": true, "peer": true }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -6369,6 +6442,11 @@ "dev": true, "peer": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -6444,6 +6522,14 @@ "node": ">=10" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7277,6 +7363,23 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz", + "integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -7639,8 +7742,7 @@ "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { "version": "0.23.2", @@ -7663,10 +7765,9 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "license": "ISC", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -7729,6 +7830,11 @@ "node": ">= 0.4" } }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8153,6 +8259,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -8196,8 +8307,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-oK6MjM//VaGwM58DCQJl4hf4B2rA3TSM7uzVz/s3ta0tS4r52scjZohZIM7H8k9tGrZDOdoFsVGAAE8jvVapaA==", - "license": "GPL-3.0-only", + "integrity": "sha512-nImUxtANHb8n2YvRHtSja+GPUeAXO+MivFKvjQIzF1r5IkNN2P0lnwFT39xb0WLRa+bWhiGW1ORBP4ubX8npzA==", "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^1.1.0" diff --git a/src/renderer/src/components/add-station-model/station-modal.tsx b/src/renderer/src/components/add-station-model/station-modal.tsx index 779d50c..214a61c 100644 --- a/src/renderer/src/components/add-station-model/station-modal.tsx +++ b/src/renderer/src/components/add-station-model/station-modal.tsx @@ -24,7 +24,7 @@ const AddStationModal: React.FC = ({ closeModal }) => {
-
AFV Configuration
+
New Ration
= ({ closeModal }) => {
diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index 2abed1d..20ec113 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -120,6 +120,7 @@ const Bootsrap: React.FC = () => { }); window.api.on('VoiceDisconnected', () => { + console.log('Voice disconnected'); useSessionStore.getState().setIsConnecting(false); useSessionStore.getState().setIsConnected(false); useRadioState.getState().reset(); @@ -137,6 +138,7 @@ const Bootsrap: React.FC = () => { }); window.api.on('network-disconnected', () => { + console.log('Network connected'); useSessionStore.getState().setNetworkConnected(false); useSessionStore.getState().setCallsign(''); useSessionStore.getState().setIsAtc(false); diff --git a/src/renderer/src/components/navbar.tsx b/src/renderer/src/components/navbar.tsx index 7a13ca0..0c8c0c5 100644 --- a/src/renderer/src/components/navbar.tsx +++ b/src/renderer/src/components/navbar.tsx @@ -83,13 +83,13 @@ const Navbar: React.FC = () => {
@@ -97,15 +97,16 @@ const Navbar: React.FC = () => { {!isEditMode && (
- - {platform === 'linux' && ( - - )} +
)} @@ -122,27 +123,26 @@ const Navbar: React.FC = () => { - {isNetworkConnected && ( - -
+ {/* {isNetworkConnected && ( */} + +
+ + {platform === 'linux' && ( -
-
- )} - {isNetworkConnected && ( - - - - )} + )} +
+
+ {/* )} */} + {/* {isNetworkConnected && ( */} + + + + {/* )} */}
diff --git a/src/renderer/src/components/radio/global-radio-gain.tsx b/src/renderer/src/components/radio/global-radio-gain.tsx index f6ae45d..6214abb 100644 --- a/src/renderer/src/components/radio/global-radio-gain.tsx +++ b/src/renderer/src/components/radio/global-radio-gain.tsx @@ -67,7 +67,7 @@ const GlobalRadioGain = () => { lineHeight: '29px' }} > - MASTER + MAIN
)} diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index cbb1a5a..9bf4873 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -26,12 +26,11 @@ const RadioContainer: React.FC = () => { if (!isNetworkConnected) { return (
-
- No VATSIM connection found! +
+ No VATSIM connection detected!
- Ensure you have established a valid connection to the VATSIM network before attempting to - connect to AFV. + Please ensure your ATC client is running and connected to the VATSIM network.
); @@ -40,11 +39,11 @@ const RadioContainer: React.FC = () => { if (!isConnected) { return (
-
- VATSIM connection found! +
+ VATSIM connection detected!
- Click the connect button to establish a connection to AFV. + Click the connect button to establish a connection to the VATSIM audio network.
); diff --git a/src/renderer/src/components/radio/rxinfo.tsx b/src/renderer/src/components/radio/rxinfo.tsx index 1b4de1a..e782140 100644 --- a/src/renderer/src/components/radio/rxinfo.tsx +++ b/src/renderer/src/components/radio/rxinfo.tsx @@ -1,7 +1,12 @@ import useRadioState from '@renderer/store/radioStore'; import React, { useMemo, useState, useEffect } from 'react'; import type { RadioType } from '@renderer/store/radioStore'; -import { ArrowsAngleExpand } from 'react-bootstrap-icons'; +import { + ArrowsAngleExpand, + LayoutSidebarInset, + LayoutSidebarInsetReverse, + LayoutSidebarReverse +} from 'react-bootstrap-icons'; import useUtilStore from '@renderer/store/utilStore'; import clsx from 'clsx'; import { useMediaQuery } from 'react-responsive'; @@ -75,7 +80,7 @@ const RxInfo: React.FC = () => { setShowExpandedRxInfo(!showExpandedRxInfo); }} > - + {!showExpandedRxInfo ? : }
)} diff --git a/src/renderer/src/components/sidebar/add-frequency.tsx b/src/renderer/src/components/sidebar/add-frequency.tsx index 1487210..d0dbb0b 100644 --- a/src/renderer/src/components/sidebar/add-frequency.tsx +++ b/src/renderer/src/components/sidebar/add-frequency.tsx @@ -73,7 +73,7 @@ const AddFrequency: React.FC = ({ onAddFrequency }) => { return (
-
Add a VHF frequency
+
Add a VHF Frequency
= ({ className = '' }) = ? 'CONNECTING' : callsign ? `CONNECT` - : 'NO ACTIVE CONNECTION'} + : 'CONNECT'} ); diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index fc80c47..02a5321 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -374,17 +374,17 @@ button:disabled { flex-direction: column; align-items: center; justify-content: center; - gap: 2px; + // gap: 1px; } .frequency { - font-size: 0.9rem; + font-size: 15px; line-height: 1; font-weight: 500; } .callsign { - font-size: 0.85rem; + font-size: 14px; line-height: 1; font-weight: normal; } From 5abc33c6e02536ae1b097a6821779297714c7580 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 17:28:17 +0000 Subject: [PATCH 31/37] Remove logs --- src/renderer/src/components/bootstrap.tsx | 2 -- src/renderer/src/components/navbar.tsx | 3 +-- src/renderer/src/components/radio/rxinfo.tsx | 7 +------ src/renderer/src/components/radio/unicom-guard.tsx | 2 -- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index 20ec113..2abed1d 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -120,7 +120,6 @@ const Bootsrap: React.FC = () => { }); window.api.on('VoiceDisconnected', () => { - console.log('Voice disconnected'); useSessionStore.getState().setIsConnecting(false); useSessionStore.getState().setIsConnected(false); useRadioState.getState().reset(); @@ -138,7 +137,6 @@ const Bootsrap: React.FC = () => { }); window.api.on('network-disconnected', () => { - console.log('Network connected'); useSessionStore.getState().setNetworkConnected(false); useSessionStore.getState().setCallsign(''); useSessionStore.getState().setIsAtc(false); diff --git a/src/renderer/src/components/navbar.tsx b/src/renderer/src/components/navbar.tsx index 0c8c0c5..4813216 100644 --- a/src/renderer/src/components/navbar.tsx +++ b/src/renderer/src/components/navbar.tsx @@ -22,10 +22,9 @@ const Navbar: React.FC = () => { state.isEditMode, state.setIsEditMode ]); - const [callsign, isConnected, isNetworkConnected, isConnecting] = useSessionStore((state) => [ + const [callsign, isConnected, isConnecting] = useSessionStore((state) => [ state.callsign, state.isConnected, - state.isNetworkConnected, state.isConnecting ]); diff --git a/src/renderer/src/components/radio/rxinfo.tsx b/src/renderer/src/components/radio/rxinfo.tsx index e782140..ed36f35 100644 --- a/src/renderer/src/components/radio/rxinfo.tsx +++ b/src/renderer/src/components/radio/rxinfo.tsx @@ -1,12 +1,7 @@ import useRadioState from '@renderer/store/radioStore'; import React, { useMemo, useState, useEffect } from 'react'; import type { RadioType } from '@renderer/store/radioStore'; -import { - ArrowsAngleExpand, - LayoutSidebarInset, - LayoutSidebarInsetReverse, - LayoutSidebarReverse -} from 'react-bootstrap-icons'; +import { LayoutSidebarInsetReverse, LayoutSidebarReverse } from 'react-bootstrap-icons'; import useUtilStore from '@renderer/store/utilStore'; import clsx from 'clsx'; import { useMediaQuery } from 'react-responsive'; diff --git a/src/renderer/src/components/radio/unicom-guard.tsx b/src/renderer/src/components/radio/unicom-guard.tsx index 22b303f..d6d24c6 100644 --- a/src/renderer/src/components/radio/unicom-guard.tsx +++ b/src/renderer/src/components/radio/unicom-guard.tsx @@ -172,7 +172,6 @@ const UnicomGuardBar = () => { console.error('Failed to add UNICOM frequency'); return; } - console.log('Adding unicom frequency'); addRadio(UnicomFrequency, 'UNICOM', 'UNICOM'); void window.api.SetFrequencyRadioGain(UnicomFrequency, localRadioGain / 100); }); @@ -181,7 +180,6 @@ const UnicomGuardBar = () => { console.error('Failed to add GUARD frequency'); return; } - console.log('Adding guard frequency'); addRadio(GuardFrequency, 'GUARD', 'GUARD'); void window.api.SetFrequencyRadioGain(GuardFrequency, localRadioGain / 100); }); From 3fcc77332b8ace4674c24a3907ac37f2ca963f7e Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 18:37:12 +0000 Subject: [PATCH 32/37] Fix issue --- .../src/components/add-station-model/station-modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/add-station-model/station-modal.tsx b/src/renderer/src/components/add-station-model/station-modal.tsx index 214a61c..061079d 100644 --- a/src/renderer/src/components/add-station-model/station-modal.tsx +++ b/src/renderer/src/components/add-station-model/station-modal.tsx @@ -24,7 +24,7 @@ const AddStationModal: React.FC = ({ closeModal }) => {
-
New Ration
+
Radio Setup
= ({ closeModal }) => {
From 793f61190bbbc26eec59e97db430ad0a99d995ba Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 18:53:24 +0000 Subject: [PATCH 33/37] Clean up ui --- src/renderer/src/components/connect-timer.tsx | 2 +- src/renderer/src/components/focusBar.tsx | 55 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/components/connect-timer.tsx b/src/renderer/src/components/connect-timer.tsx index f094847..4a16aee 100644 --- a/src/renderer/src/components/connect-timer.tsx +++ b/src/renderer/src/components/connect-timer.tsx @@ -32,7 +32,7 @@ const ConnectTimer = () => { return; } - return {elapsedTime}; + return Connected: {elapsedTime}; }; export default ConnectTimer; diff --git a/src/renderer/src/components/focusBar.tsx b/src/renderer/src/components/focusBar.tsx index b105143..0b6cd57 100644 --- a/src/renderer/src/components/focusBar.tsx +++ b/src/renderer/src/components/focusBar.tsx @@ -2,6 +2,8 @@ import useSessionStore from '@renderer/store/sessionStore'; import RadioStatus from './sidebar/radio-status'; import useUtilStore from '@renderer/store/utilStore'; import ConnectTimer from './connect-timer'; +import { useMediaQuery } from 'react-responsive'; +import clsx from 'clsx'; const FocusBar = () => { const [version, isConnected, connectTimestamp] = useSessionStore((state) => [ @@ -10,6 +12,8 @@ const FocusBar = () => { state.connectTimestamp ]); const [pendingRestart] = useUtilStore((state) => [state.pendingRestart]); + const isWideScreen = useMediaQuery({ minWidth: '740px' }); + const isSmallScreen = useMediaQuery({ maxWidth: '490px' }); const restartApp = () => { if (isConnected) { @@ -34,7 +38,8 @@ const FocusBar = () => {
) : ( - connectTimestamp && ( + connectTimestamp && + isWideScreen && (
{ )} {/* Center Radio Status */} -
-
+ {isWideScreen ? ( +
+
+ +
+
+ ) : ( +
-
+ )} - {/* Right-aligned licenses with z-index to ensure clickability */} -
-
- {version} |  - - Licenses - + {!isSmallScreen && ( +
+
+ {version} |  + + Licenses + +
-
+ )}
From fbe65a26b288386a963265d56efbb6155daffd7d Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 19:25:59 +0000 Subject: [PATCH 34/37] Better alignment --- src/renderer/src/components/focusBar.tsx | 3 +++ src/renderer/src/style/app.scss | 1 + 2 files changed, 4 insertions(+) diff --git a/src/renderer/src/components/focusBar.tsx b/src/renderer/src/components/focusBar.tsx index 0b6cd57..96c11f4 100644 --- a/src/renderer/src/components/focusBar.tsx +++ b/src/renderer/src/components/focusBar.tsx @@ -62,6 +62,9 @@ const FocusBar = () => { 'col-12 d-flex align-items-center position-absolute w-100 h-100', isSmallScreen ? 'justify-content-center ' : 'justify-content-start ' )} + style={{ + lineHeight: 1 + }} >
diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 02a5321..459f992 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -508,6 +508,7 @@ select:disabled { .licenses { font-size: 11px; + line-height: 1; } .elapsed-time { From 8f39c95c76ea8d764d2ddf6623640d21fdef53f5 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sat, 2 Nov 2024 20:31:33 +0000 Subject: [PATCH 35/37] Fix get out of mini mode when disconnected --- src/main/index.ts | 7 +++++++ src/renderer/src/components/MiniModeToggleButton.tsx | 10 +++++++--- src/renderer/src/components/mini.tsx | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 3522144..1edb32a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -706,6 +706,9 @@ TrackAudioAfv.RegisterCallback((arg: string, arg2: string, arg3: string) => { } if (arg == AfvEventTypes.VoiceDisconnected) { + if (isInMiniMode()) { + toggleMiniMode(); + } mainWindow.webContents.send('VoiceDisconnected'); } @@ -714,6 +717,10 @@ TrackAudioAfv.RegisterCallback((arg: string, arg2: string, arg3: string) => { } if (arg == AfvEventTypes.NetworkDisconnected) { + if (isInMiniMode()) { + toggleMiniMode(); + } + mainWindow.webContents.send('network-disconnected'); } diff --git a/src/renderer/src/components/MiniModeToggleButton.tsx b/src/renderer/src/components/MiniModeToggleButton.tsx index 902327a..33906ae 100644 --- a/src/renderer/src/components/MiniModeToggleButton.tsx +++ b/src/renderer/src/components/MiniModeToggleButton.tsx @@ -5,13 +5,17 @@ import { Fullscreen, FullscreenExit } from 'react-bootstrap-icons'; interface MiniModeToggleButtonProps { showRestoreButton: boolean; + alwaysEnabled?: boolean; } -const MiniModeToggleButton: React.FC = ({ showRestoreButton }) => { +const MiniModeToggleButton: React.FC = ({ + showRestoreButton, + alwaysEnabled +}) => { const [radios] = useRadioState((state) => [state.radios]); const [isConnected] = useSessionStore((state) => [state.isConnected]); const toggleMiniMode = useCallback(() => { - if (!isConnected) return; + if (!isConnected && !alwaysEnabled) return; window.api.toggleMiniMode(radios.filter((r) => r.rx).length).catch((error: unknown) => { console.error(error); }); @@ -22,7 +26,7 @@ const MiniModeToggleButton: React.FC = ({ showRestore className="btn btn-primary" style={{ lineHeight: 0, fontSize: '14px' }} onClick={toggleMiniMode} - disabled={!isConnected} + disabled={!isConnected && !alwaysEnabled} > {showRestoreButton ? ( {
{/* Make only the button container no-drag */}
- +
); From 914c37faa9dfb55599f5dfbdbe5e2c355f5cd6d8 Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sun, 3 Nov 2024 21:33:40 +0000 Subject: [PATCH 36/37] More suitable error handling in mini mode --- backend/extern/libuiohook | 1 + src/main/index.ts | 7 ------- src/renderer/src/components/error.tsx | 6 ++++++ 3 files changed, 7 insertions(+), 7 deletions(-) create mode 160000 backend/extern/libuiohook diff --git a/backend/extern/libuiohook b/backend/extern/libuiohook new file mode 160000 index 0000000..d8c3694 --- /dev/null +++ b/backend/extern/libuiohook @@ -0,0 +1 @@ +Subproject commit d8c3694ec01d322fcdf8181a39bddcf2cd347c0f diff --git a/src/main/index.ts b/src/main/index.ts index 1edb32a..3522144 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -706,9 +706,6 @@ TrackAudioAfv.RegisterCallback((arg: string, arg2: string, arg3: string) => { } if (arg == AfvEventTypes.VoiceDisconnected) { - if (isInMiniMode()) { - toggleMiniMode(); - } mainWindow.webContents.send('VoiceDisconnected'); } @@ -717,10 +714,6 @@ TrackAudioAfv.RegisterCallback((arg: string, arg2: string, arg3: string) => { } if (arg == AfvEventTypes.NetworkDisconnected) { - if (isInMiniMode()) { - toggleMiniMode(); - } - mainWindow.webContents.send('network-disconnected'); } diff --git a/src/renderer/src/components/error.tsx b/src/renderer/src/components/error.tsx index 1c1c564..bd5d4c9 100644 --- a/src/renderer/src/components/error.tsx +++ b/src/renderer/src/components/error.tsx @@ -5,9 +5,11 @@ import useSound from 'use-sound'; // @ts-expect-error idk this is weird import errorSfx from '../assets/md80_error.mp3'; +import { useMediaQuery } from 'react-responsive'; const ErrorDialog: React.FC = () => { const errorStore = useErrorStore((state) => state); + const isMiniMode = useMediaQuery({ maxWidth: '455px' }); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const [play] = useSound(errorSfx); @@ -22,6 +24,10 @@ const ErrorDialog: React.FC = () => { return null; } + if (isMiniMode) { + return; + } + return (
From 220659c67ebcabe7e010bc4d8727c0650ffab7ee Mon Sep 17 00:00:00 2001 From: George Barlow Date: Sun, 3 Nov 2024 21:36:42 +0000 Subject: [PATCH 37/37] Don't delete guard and unicom --- src/renderer/src/components/delete-multiple-radios.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/delete-multiple-radios.tsx b/src/renderer/src/components/delete-multiple-radios.tsx index 42fcbaf..cb67659 100644 --- a/src/renderer/src/components/delete-multiple-radios.tsx +++ b/src/renderer/src/components/delete-multiple-radios.tsx @@ -18,8 +18,10 @@ const DeleteMultipleRadios: React.FC = () => { const handleDeleteRadios = () => { if (radiosToBeDeleted.length == 0) { radios.forEach((radio) => { - setPendingDeletion(radio.frequency, false); - awaitEndOfRxForDeletion(radio.frequency); + if (radio.callsign !== 'UNICOM' && radio.callsign !== 'GUARD') { + setPendingDeletion(radio.frequency, false); + awaitEndOfRxForDeletion(radio.frequency); + } }); } else { radiosToBeDeleted.forEach((radio) => { @@ -60,6 +62,7 @@ const DeleteMultipleRadios: React.FC = () => {