LiveFile: fix window refocus sync

This commit is contained in:
Enrico Ros
2024-08-06 21:55:41 -07:00
parent e4bb546442
commit a69944c019
3 changed files with 90 additions and 8 deletions
+1 -1
View File
@@ -146,7 +146,7 @@ export const useLiveFileStore = create<LiveFileState & LiveFileActions>()(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'}`,
},
},
}));
+13 -7
View File
@@ -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 (
<Alert
variant='plain'
color={statusColor}
startDecorator={status?.mtype === 'error' ? <WarningRoundedIcon /> : undefined}
sx={{
display: 'flex',
flexFlow: 'row wrap',
@@ -237,11 +237,17 @@ export function useLiveFileComparison(
}}
>
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
{/* Pair Button */}
{isPairingValid && (
<IconButton size='sm' onClick={handleSyncButtonClicked}>
<LiveFileIcon />
</IconButton>
)}
{/* Alert Decorator (startDecorator will have it messy) */}
{status?.mtype === 'error' && <WarningRoundedIcon sx={{ mr: 1 }} />}
{' '}<span>{status?.message}</span>
</Box>
@@ -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();
});
+76
View File
@@ -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<boolean>(() =>
// WindowFocusObserver.getInstance().windowFocusState,
// );
// React.useEffect(() => WindowFocusObserver.getInstance().subscribe(setIsWindowFocused), []);
// return isWindowFocused;
// }