diff --git a/src/common/livefile/store-live-file.ts b/src/common/livefile/store-live-file.ts index a19d91366..fffa682e6 100644 --- a/src/common/livefile/store-live-file.ts +++ b/src/common/livefile/store-live-file.ts @@ -146,7 +146,7 @@ export const useLiveFileStore = create()(persis [fileId]: { ...file, isLoading: false, - error: `Error loading File: ${error?.message || typeof error === 'string' ? error : 'Unknown error'}`, + error: `Error reading: ${error?.message || typeof error === 'string' ? error : 'Unknown error'}`, }, }, })); diff --git a/src/common/livefile/useLiveFileComparison.tsx b/src/common/livefile/useLiveFileComparison.tsx index 2fc117a2f..0c19674da 100644 --- a/src/common/livefile/useLiveFileComparison.tsx +++ b/src/common/livefile/useLiveFileComparison.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { fileOpen } from 'browser-fs-access'; -import { focusManager } from '@tanstack/react-query'; import { cleanupEfficiency, makeDiff } from '@sanity/diff-match-patch'; import { Alert, Box, Button, ColorPaletteProp, IconButton } from '@mui/joy'; @@ -8,6 +7,7 @@ import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import WarningRoundedIcon from '@mui/icons-material/WarningRounded'; import { TooltipOutlined } from '~/common/components/TooltipOutlined'; +import { WindowFocusObserver } from '~/common/util/windowUtils'; import type { LiveFileId } from './liveFile.types'; import { LiveFileChooseIcon, LiveFileIcon, LiveFileReloadIcon, LiveFileSaveIcon } from './liveFile.icons'; @@ -217,16 +217,16 @@ export function useLiveFileComparison( const liveFileActionBox = React.useMemo(() => { if (!status && !fileHasContent) return null; - const statusColor: ColorPaletteProp = status?.mtype === 'error' ? 'danger' - : status?.mtype === 'success' ? 'success' - : status?.mtype === 'changes' ? 'neutral' - : 'neutral'; + const statusColor: ColorPaletteProp = + status?.mtype === 'error' ? 'warning' + : status?.mtype === 'success' ? 'success' + : status?.mtype === 'changes' ? 'neutral' + : 'neutral'; return ( : undefined} sx={{ display: 'flex', flexFlow: 'row wrap', @@ -237,11 +237,17 @@ export function useLiveFileComparison( }} > + + {/* Pair Button */} {isPairingValid && ( )} + + {/* Alert Decorator (startDecorator will have it messy) */} + {status?.mtype === 'error' && } + {' '}{status?.message} @@ -299,7 +305,7 @@ export function useLiveFileComparison( // Auto-click on 'refresh' on window focus React.useEffect(() => { - return focusManager.subscribe(async (focused) => { + return WindowFocusObserver.getInstance().subscribe(async (focused) => { if (focused && shallUpdateOnRefocus) await handleUpdateFileContent(); }); diff --git a/src/common/util/windowUtils.tsx b/src/common/util/windowUtils.tsx new file mode 100644 index 000000000..c2a1f0721 --- /dev/null +++ b/src/common/util/windowUtils.tsx @@ -0,0 +1,76 @@ +import { isBrowser } from './pwaUtils'; + +export class WindowFocusObserver { + private static instance: WindowFocusObserver | null = null; + private listeners: Set<(isWindowFocused: boolean) => void> = new Set(); + private isWindowFocused: boolean = true; + private refCount: number = 0; + + // singleton + + public static getInstance(): WindowFocusObserver { + if (!WindowFocusObserver.instance) + WindowFocusObserver.instance = new WindowFocusObserver(); + return WindowFocusObserver.instance; + } + + private constructor() { + if (!isBrowser) return; + this.isWindowFocused = document.hasFocus(); + window.addEventListener('focus', this._handleWindowFocus); + window.addEventListener('blur', this._handleWindowBlur); + } + + // clients + + get windowFocusState(): boolean { + return this.isWindowFocused; + } + + public subscribe(listener: (isWindowFocused: boolean) => void): () => void { + this.listeners.add(listener); + this.refCount++; + return () => { + this.listeners.delete(listener); + this.refCount--; + if (this.refCount === 0) { + this._cleanup(); + } + }; + } + + // private methods + + private _cleanup(): void { + if (!isBrowser) return; + window.removeEventListener('focus', this._handleWindowFocus); + window.removeEventListener('blur', this._handleWindowBlur); + this.listeners.clear(); + WindowFocusObserver.instance = null; + } + + private _handleWindowFocus = (): void => { + this._updateWindowFocusState(true); + }; + + private _handleWindowBlur = (): void => { + this._updateWindowFocusState(false); + }; + + private _updateWindowFocusState(newState: boolean): void { + if (this.isWindowFocused !== newState) { + this.isWindowFocused = newState; + this.listeners.forEach(listener => listener(this.isWindowFocused)); + } + } +} + + +// // React to window visibility changes. +// export function useWindowFocus(): boolean { +// const [isWindowFocused, setIsWindowFocused] = React.useState(() => +// WindowFocusObserver.getInstance().windowFocusState, +// ); +// React.useEffect(() => WindowFocusObserver.getInstance().subscribe(setIsWindowFocused), []); +// return isWindowFocused; +// }