11import React , { useContext , useEffect , useMemo , useRef , useState } from 'react' ;
22import { useGetGlobal , useGlobal } from '../../context/useGlobal' ;
33import { useLocation , useParams } from 'react-router-dom' ;
4- import {
5- IState ,
6- IMainStrings ,
7- IViewModeStrings ,
8- ISharedStrings ,
9- OfflineProject ,
10- } from '../../model' ;
4+ import { IState , IViewModeStrings } from '../../model' ;
115import { shallowEqual , useSelector } from 'react-redux' ;
126import {
137 AppBar ,
@@ -17,14 +11,11 @@ import {
1711 LinearProgress ,
1812 Tooltip ,
1913 Box ,
20- Button ,
2114 useTheme ,
2215 useMediaQuery ,
2316} from '@mui/material' ;
2417import HomeIcon from '@mui/icons-material/Home' ;
25- import SystemUpdateIcon from '@mui/icons-material/SystemUpdateAlt' ;
2618import TableViewIcon from '@mui/icons-material/TableView' ;
27- import ExitToAppIcon from '@mui/icons-material/ExitToApp' ;
2819import { API_CONFIG , isElectron } from '../../../api-variable' ;
2920import { TokenContext } from '../../context/TokenProvider' ;
3021import { UnsavedContext } from '../../context/UnsavedContext' ;
@@ -40,37 +31,22 @@ import {
4031 useMounted ,
4132 logError ,
4233 Severity ,
43- infoMsg ,
4434 exitApp ,
4535 useMyNavigate ,
4636 useWaitForRemoteQueue ,
47- Online ,
4837} from '../../utils' ;
4938import { withBucket } from '../../hoc/withBucket' ;
50- import {
51- useLoadProjectData ,
52- useOfflineAvailToggle ,
53- useOfflnProjRead ,
54- usePlan ,
55- useVProjectRead ,
56- } from '../../crud' ;
39+ import { usePlan } from '../../crud' ;
5740import Busy from '../Busy' ;
58- import CloudOffIcon from '@mui/icons-material/CloudOff' ;
59- import CloudOnIcon from '@mui/icons-material/Cloud' ;
6041import ProjectDownloadAlert from '../ProjectDownloadAlert' ;
61- import { axiosPost } from '../../utils/axios' ;
62- import { DateTime } from 'luxon' ;
63- import { useSnackBar , AlertSeverity } from '../../hoc/SnackBar' ;
42+ import { useSnackBar } from '../../hoc/SnackBar' ;
6443import PolicyDialog from '../PolicyDialog' ;
6544import JSONAPISource from '@orbit/jsonapi' ;
66- import { mainSelector , sharedSelector , viewModeSelector } from '../../selector' ;
45+ import { viewModeSelector } from '../../selector' ;
6746import { useHome } from '../../utils/useHome' ;
68- import { useOrbitData } from '../../hoc/useOrbitData' ;
69- import packageJson from '../../../package.json' ;
70- import { MainAPI } from '@model/main-api' ;
7147import { ApmLogo } from '../../control/ApmLogo' ;
7248import { OrgHead } from './OrgHead' ;
73- const ipc = window ?. api as MainAPI ;
49+ import { HeadStatus } from './HeadStatus' ;
7450
7551const twoIcon = { minWidth : `calc(${ 48 * 2 } px)` } as React . CSSProperties ;
7652const threeIcon = { minWidth : `calc(${ 48 * 3 } px)` } as React . CSSProperties ;
@@ -130,31 +106,25 @@ interface IProps {
130106 switchTo ?: boolean ;
131107}
132108
133- type DownloadAlertReason = 'cloud' ;
109+ export type DownloadAlertReason = 'cloud' ;
134110
135111export const AppHead = ( props : IProps ) => {
136112 const { resetRequests, switchTo } = props ;
137113 const orbitStatus = useSelector ( ( state : IState ) => state . orbit . status ) ;
138114 const orbitErrorMsg = useSelector ( ( state : IState ) => state . orbit . message ) ;
139- const t : IMainStrings = useSelector ( mainSelector , shallowEqual ) ;
140- const ts : ISharedStrings = useSelector ( sharedSelector , shallowEqual ) ;
141115 const { pathname } = useLocation ( ) ;
142116 const navigate = useMyNavigate ( ) ;
143117 const theme = useTheme ( ) ;
144118 const isMobileWidth = useMediaQuery ( theme . breakpoints . down ( 'sm' ) ) ;
145- const offlineProjects = useOrbitData < OfflineProject [ ] > ( 'offlineproject' ) ;
146- const [ hasOfflineProjects , setHasOfflineProjects ] = useState ( false ) ;
147119 const [ home ] = useGlobal ( 'home' ) ; //verified this is not used in a function 2/18/25
148120 const [ orgRole ] = useGlobal ( 'orgRole' ) ; //verified this is not used in a function 2/18/25
149- const [ connected , setConnected ] = useGlobal ( 'connected' ) ; //verified this is not used in a function 2/18/25
150121 const [ errorReporter ] = useGlobal ( 'errorReporter' ) ;
151122 const [ coordinator ] = useGlobal ( 'coordinator' ) ;
152123 const [ user ] = useGlobal ( 'user' ) ;
153124 const [ , setProject ] = useGlobal ( 'project' ) ;
154- const [ plan , setPlan ] = useGlobal ( 'plan' ) ; //verified this is not used in a function 2/18/25
125+ const [ , setPlan ] = useGlobal ( 'plan' ) ; //verified this is not used in a function 2/18/25
155126 const remote = coordinator ?. getSource ( 'remote' ) as JSONAPISource ;
156127 const [ isOffline ] = useGlobal ( 'offline' ) ; //verified this is not used in a function 2/18/25
157- const [ isOfflineOnly ] = useGlobal ( 'offlineOnly' ) ; //verified this is not used in a function 2/18/25
158128 const tokenCtx = useContext ( TokenContext ) ;
159129 const ctx = useContext ( UnsavedContext ) ;
160130 const { checkSavedFn, startSave, toolsChanged, anySaving } = ctx . state ;
@@ -164,29 +134,19 @@ export const AppHead = (props: IProps) => {
164134 const [ dataChangeCount ] = useGlobal ( 'dataChangeCount' ) ; //verified this is not used in a function 2/18/25
165135 const [ importexportBusy ] = useGlobal ( 'importexportBusy' ) ; //verified this is not used in a function 2/18/25
166136 const [ isChanged ] = useGlobal ( 'changed' ) ; //verified this is only used in a useEffect
167- const [ lang ] = useGlobal ( 'lang' ) ;
168137 const getGlobal = useGetGlobal ( ) ;
169138 const [ doExit , setDoExit ] = useState ( false ) ;
170139 const [ exitAlert , setExitAlert ] = useState ( false ) ;
171140 const isMounted = useMounted ( 'apphead' ) ;
172141 const [ version , setVersion ] = useState ( '' ) ;
173- const [ updates ] = useState (
174- ( localStorage . getItem ( 'updates' ) || 'true' ) === 'true'
175- ) ;
176- const [ latestVersion , setLatestVersion ] = useGlobal ( 'latestVersion' ) ; //verified this is not used in a function 2/18/25
177- const [ latestRelease , setLatestRelease ] = useGlobal ( 'releaseDate' ) ; //verified this is not used in a function 2/18/25
142+ const [ latestVersion , setLatestVersion ] = useState ( '' ) ;
178143 const [ complete ] = useGlobal ( 'progress' ) ; //verified this is not used in a function 2/18/25
179144 const [ downloadAlert , setDownloadAlert ] = useState ( false ) ;
180145 const downloadAlertReason = useRef < DownloadAlertReason | null > ( null ) ;
181146 const [ updateTipOpen , setUpdateTipOpen ] = useState ( false ) ;
182147 const [ showTerms , setShowTerms ] = useState ( '' ) ;
183148 const waitForRemoteQueue = useWaitForRemoteQueue ( ) ;
184149 const waitForDataChangesQueue = useWaitForRemoteQueue ( 'datachanges' ) ;
185- const offlineProjectRead = useOfflnProjRead ( ) ;
186- const LoadData = useLoadProjectData ( ) ;
187- const offlineAvailToggle = useOfflineAvailToggle ( ) ;
188- const { getPlan } = usePlan ( ) ;
189- const vProject = useVProjectRead ( ) ;
190150 const [ mobileView ] = useGlobal ( 'mobileView' ) ;
191151
192152 // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -267,61 +227,6 @@ export const AppHead = (props: IProps) => {
267227 handleMenu ( what ) ;
268228 } ;
269229
270- const cloudAction = ( ) => {
271- localStorage . setItem (
272- 'mode' ,
273- getGlobal ( 'offline' ) ||
274- orbitStatus !== undefined ||
275- ! getGlobal ( 'connected' )
276- ? 'online-cloud'
277- : 'online-local'
278- ) ;
279- localStorage . setItem ( LocalKey . plan , getGlobal ( 'plan' ) ) ;
280- handleMenu ( 'Logout' , ! isOffline ? 'cloud' : null ) ;
281- } ;
282-
283- const handleSetOnline = ( cb ?: ( ) => void ) => {
284- Online ( true , ( isConnected ) => {
285- if ( getGlobal ( 'connected' ) !== isConnected ) {
286- localStorage . setItem ( LocalKey . connected , isConnected . toString ( ) ) ;
287- setConnected ( isConnected ) ;
288- }
289- if ( ! isConnected ) {
290- showMessage ( ts . mustBeOnline ) ;
291- return ;
292- }
293- cb && cb ( ) ;
294- } ) ;
295- } ;
296-
297- useEffect ( ( ) => {
298- const value = offlineProjects . some ( ( p ) => p ?. attributes ?. offlineAvailable ) ;
299- if ( value !== hasOfflineProjects ) setHasOfflineProjects ( value ) ;
300- // eslint-disable-next-line react-hooks/exhaustive-deps
301- } , [ offlineProjects ] ) ;
302-
303- const handleCloud = ( ) => {
304- handleSetOnline ( ( ) => {
305- const planRec = getGlobal ( 'plan' )
306- ? getPlan ( getGlobal ( 'plan' ) )
307- : undefined ;
308- if ( ! planRec ) {
309- if ( hasOfflineProjects ) cloudAction ( ) ;
310- return ;
311- }
312- const offlineProject = offlineProjectRead ( vProject ( planRec ) ) ;
313- if ( offlineProject ?. attributes ?. offlineAvailable ) {
314- cloudAction ( ) ;
315- } else {
316- LoadData ( getGlobal ( 'project' ) , ( ) => {
317- offlineAvailToggle ( getGlobal ( 'project' ) ) . then ( ( ) => {
318- cloudAction ( ) ;
319- } ) ;
320- } ) ;
321- }
322- } ) ;
323- } ;
324-
325230 useEffect ( ( ) => {
326231 if ( tokenCtx . state . expiresAt === - 1 ) {
327232 handleMenu ( 'Logout' ) ;
@@ -345,14 +250,6 @@ export const AppHead = (props: IProps) => {
345250 else setView ( 'Logout' ) ;
346251 } ;
347252
348- const handleDownloadClick = ( ) => {
349- if ( ipc )
350- ipc ?. openExternal (
351- 'https://software.sil.org/audioprojectmanager/download/'
352- ) ;
353- // remote?.getCurrentWindow().close();
354- } ;
355-
356253 const handleUnload = ( e : any ) => {
357254 if ( pathname === '/' ) return true ;
358255 if ( pathname . startsWith ( '/access' ) ) return true ;
@@ -385,9 +282,6 @@ export const AppHead = (props: IProps) => {
385282 setView ( 'Access' ) ;
386283 }
387284 }
388- setHasOfflineProjects (
389- offlineProjects . some ( ( p ) => p ?. attributes ?. offlineAvailable )
390- ) ;
391285 return ( ) => {
392286 window . removeEventListener ( 'beforeunload' , handleUnload ) ;
393287 } ;
@@ -406,59 +300,6 @@ export const AppHead = (props: IProps) => {
406300 // eslint-disable-next-line react-hooks/exhaustive-deps
407301 } , [ exitAlert , isChanged ] ) ;
408302
409- useEffect ( ( ) => {
410- isMounted ( ) && setVersion ( packageJson . version ) ;
411- // eslint-disable-next-line react-hooks/exhaustive-deps
412- } , [ isMounted ] ) ;
413-
414- useEffect ( ( ) => {
415- if (
416- latestVersion === '' &&
417- version !== '' &&
418- updates &&
419- localStorage . getItem ( LocalKey . connected ) !== 'false'
420- ) {
421- const bodyFormData = new FormData ( ) ;
422- bodyFormData . append ( 'env' , navigator . userAgent ) ;
423- axiosPost ( 'userversions/2/' + version , bodyFormData )
424- . then ( ( result ) => {
425- const response = result as {
426- data : { desktopVersion : string ; dateUpdated : string } ;
427- } ;
428- const lv = response ?. data [ 'desktopVersion' ] ;
429- let lr = response ?. data [ 'dateUpdated' ] ;
430- if ( ! lr . endsWith ( 'Z' ) ) lr += 'Z' ;
431- lr = DateTime . fromISO ( lr )
432- . setLocale ( lang )
433- . toLocaleString ( DateTime . DATE_SHORT ) ;
434- setLatestVersion ( lv ) ;
435- setLatestRelease ( lr ) ;
436- if ( isElectron && lv ?. split ( ' ' ) [ 0 ] !== version )
437- showMessage (
438- < span >
439- { t . updateAvailable . replace ( '{0}' , lv ) . replace ( '{1}' , lr ) }
440- < IconButton
441- id = "systemUpdate"
442- onClick = { handleDownloadClick }
443- component = "span"
444- >
445- < SystemUpdateIcon color = "primary" />
446- </ IconButton >
447- </ span > ,
448- AlertSeverity . Warning
449- ) ;
450- } )
451- . catch ( ( err ) => {
452- logError (
453- Severity . error ,
454- errorReporter ,
455- infoMsg ( err , 'userversions failed ' + navigator . userAgent )
456- ) ;
457- } ) ;
458- }
459- // eslint-disable-next-line react-hooks/exhaustive-deps
460- } , [ updates , version , lang ] ) ;
461-
462303 useEffect ( ( ) => {
463304 setCssVars (
464305 latestVersion !== '' && latestVersion !== version && isElectron
@@ -481,15 +322,14 @@ export const AppHead = (props: IProps) => {
481322 // eslint-disable-next-line react-hooks/exhaustive-deps
482323 } , [ orbitStatus , orbitErrorMsg ] ) ;
483324
484- const handleUpdateOpen = ( ) => setUpdateTipOpen ( true ) ;
485- const handleUpdateClose = ( ) => setUpdateTipOpen ( pathname === '/' ) ;
486325 const handleTermsClose = ( ) => setShowTerms ( '' ) ;
487326
488327 if ( view === 'Error' ) navigate ( '/error' ) ;
489328 if ( view === 'Logout' ) setTimeout ( ( ) => navigate ( '/logout' ) , 500 ) ;
490329 if ( view === 'Access' ) setTimeout ( ( ) => navigate ( '/' ) , 200 ) ;
491330 if ( view === 'Terms' ) navigate ( '/terms' ) ;
492331 if ( view === 'Privacy' ) navigate ( '/privacy' ) ;
332+
493333 return ! mobileView && ! isMobileWidth ? (
494334 < AppBar
495335 position = "fixed"
@@ -527,68 +367,12 @@ export const AppHead = (props: IProps) => {
527367 </ >
528368 ) }
529369 { '\u00A0' }
530- { orbitStatus !== undefined || ! connected ? (
531- < IconButton onClick = { ( ) => handleSetOnline ( ) } >
532- < CloudOffIcon color = "action" />
533- </ IconButton >
534- ) : (
535- isElectron &&
536- ! isOfflineOnly &&
537- localStorage . getItem ( LocalKey . userId ) &&
538- ( plan || hasOfflineProjects ) && (
539- < Button
540- onClick = { handleCloud }
541- startIcon = {
542- isOffline ? (
543- < CloudOffIcon color = "action" />
544- ) : (
545- < CloudOnIcon color = "secondary" />
546- )
547- }
548- >
549- { isOffline ? t . goOnline : t . goOffline }
550- </ Button >
551- )
552- ) }
553- { latestVersion !== '' &&
554- isElectron &&
555- latestVersion ?. split ( ' ' ) [ 0 ] !== version && (
556- < Tooltip
557- arrow
558- placement = "bottom-end"
559- open = { updateTipOpen }
560- onOpen = { handleUpdateOpen }
561- onClose = { handleUpdateClose }
562- title = { t . updateAvailable
563- . replace ( '{0}' , latestVersion )
564- . replace ( '{1}' , latestRelease ) }
565- >
566- < IconButton id = "systemUpdate" onClick = { handleDownloadClick } >
567- < SystemUpdateIcon color = "primary" />
568- </ IconButton >
569- </ Tooltip >
570- ) }
571- { latestVersion !== '' &&
572- ! isElectron &&
573- latestVersion . split ( ' ' ) [ 0 ] !== version &&
574- latestVersion ?. split ( ' ' ) . length > 1 && (
575- < Tooltip
576- arrow
577- open = { updateTipOpen }
578- onOpen = { handleUpdateOpen }
579- onClose = { handleUpdateClose }
580- title = { t . updateAvailable
581- . replace ( '{0}' , latestVersion )
582- . replace ( '{1}' , latestRelease ) }
583- >
584- < IconButton
585- id = "systemUpdate"
586- href = "https://www.audioprojectmanager.org"
587- >
588- < ExitToAppIcon color = "primary" />
589- </ IconButton >
590- </ Tooltip >
591- ) }
370+ < HeadStatus
371+ handleMenu = { handleMenu }
372+ onVersion = { setVersion }
373+ onLatestVersion = { setLatestVersion }
374+ onUpdateTipOpen = { setUpdateTipOpen }
375+ />
592376 < HelpMenu
593377 online = { ! isOffline }
594378 sx = { updateTipOpen && isElectron ? { top : '40px' } : { } }
@@ -619,6 +403,14 @@ export const AppHead = (props: IProps) => {
619403 </ IconButton >
620404 < OrgHead />
621405 < GrowingSpacer />
406+ { ! isMobileWidth && (
407+ < HeadStatus
408+ handleMenu = { handleMenu }
409+ onVersion = { setVersion }
410+ onLatestVersion = { setLatestVersion }
411+ onUpdateTipOpen = { setUpdateTipOpen }
412+ />
413+ ) }
622414 < HelpMenu
623415 online = { ! isOffline }
624416 sx = { updateTipOpen && isElectron ? { top : '40px' } : { } }
0 commit comments