spacedrive/interface/hooks/useCallbackToWatchForm.ts
Vítor Vasconcellos 30e7c9d709
[ENG-528] QuickPreview isn't correctly handling errors for video/audio playback (#815)
* Centralize the file preview logic in `Thumb.tsx`

* Fix useEffect

* Fix Inspector thumb keeping internal state from previous selected files
 - Change video border to follow video aspect-ratio, just like Finder

* Restore memo to Thumb
 - Only add borders to video thumb, not the video itself

* Simplify and improve Thumb logic
 - Add A internal Thumbnail component
 - Rename main component to FileThumb to match mobile naming
 - Move getIcon utility function to assets/icons

* Add new `useCallbackToWatchResize` hook
 - Replace `ResizeObserver` with new resize hook in `Thumb`
 - Simplify and improve `useIsDark` hook by replacing `react-responsive` with direct usage of WebAPI `matchMedia`
 - Fix `Thumb` src not updating correctly
 - Fix video extension incorrectly showing when size <= 80 in `Thumb`

* Fix `Inspector` not updating thumb type
 - Remove superfluous `newThumb` from `getExplorerItemData`

* Remove superfluous `ThumSize`

* Forgot a `?`

* Fix incorrect className check in `Thumb`
 - Updated changed files to use the hooks root import

* Format

* Fix PDF preview compleatly breaking the application
 - Allow Linux to access both the spacedrive:// custom protocol and the workaround webserver
 - On Linux only use the webserver for audio and video, which don't work when requested through a custom protocol
 - Configure tauri IPC to allow API access to the spacedrive://localhost domain, to avoid PDF previews from breaking the security scope and rendering the application unusable
 - Configure CSP to allow the pdf plugin custom protocol used by webkit
 - Fix race condition between Thumb error handler and thumbType useEffect, by using replacing it with a useLayoutEffect
 - Improve Thumb's error handling
2023-05-18 03:31:15 +00:00

66 lines
2.4 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react';
import { EventType, FieldPath, FieldValues, UseFormReturn } from 'react-hook-form';
const noop = () => {};
type Cb<S extends FieldValues> = (
value: S,
info: {
name?: FieldPath<S>;
type?: EventType;
}
) => void | Promise<void>;
export function useCallbackToWatchForm<S extends FieldValues>(
callback: Cb<S>,
deps: [UseFormReturn<S, unknown>, ...React.DependencyList]
): void;
export function useCallbackToWatchForm<S extends FieldValues>(
callback: Cb<S>,
deps: React.DependencyList,
form: UseFormReturn<S, unknown>
): void;
/**
* This hook is an async friendly wrapper for useCallback that enables reacting to any form changes.
*
* The callback will be called on the first render, and whenever the form changes, with the current
* form values and the event info regarding the change (empty on first render). If the callback is
* async, or returns a promise, it will wait for the previous callback to finish before executing
* the next one. Any errors thrown by the callback will be ignored.
*
* @param callback - Callback to be called when form changes
* @param deps - Dependency list for the callback
* @param form - Form to watch. If not provided, it will be taken from the first element of the dependency list
*/
export function useCallbackToWatchForm<S extends FieldValues>(
callback: Cb<S>,
deps: React.DependencyList,
form?: UseFormReturn<S, unknown>
): void {
if (form == null) form = deps[0] as UseFormReturn<S, unknown>;
if (form == null) throw new Error('Form is not provided');
const { getValues, watch } = form;
if (typeof getValues !== 'function' || typeof watch !== 'function')
throw new Error('Form is not provided');
// Create a promise chain to make sure async callbacks are called in order
const chain = useRef<Promise<true | void>>(Promise.resolve(true));
// Disable lint warning because this hook is a wrapper for useCallback
// eslint-disable-next-line react-hooks/exhaustive-deps
const onWatch = useCallback(callback, deps);
useEffect(() => {
chain.current = chain.current
// If this is the first time, we don't need to wait for a form change
.then((initCheck) => initCheck && onWatch(getValues(), {}))
.finally(noop);
return watch((_, info) => {
chain.current = chain.current.then(() => onWatch(getValues(), info)).finally(noop);
}).unsubscribe;
}, [watch, onWatch, getValues]);
}