|
|
|
@ -1,15 +1,16 @@
|
|
|
|
|
import { useLayoutEffect, useEffect, useState, useRef } from 'react';
|
|
|
|
|
import { useLayoutEffect, useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
|
|
|
|
import { createFilter } from '@vizality/util/filter';
|
|
|
|
|
import { getObjectURL } from '@vizality/util/file';
|
|
|
|
|
|
|
|
|
|
import { get, post, put, del } from '../http';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param {string} requestType Request type. One of get, post, put, or del.
|
|
|
|
|
* @param {url} url URL to call
|
|
|
|
|
* @param {string|object} [headers] Headers
|
|
|
|
|
*/
|
|
|
|
|
export function useFetch (requestType, url, headers) {
|
|
|
|
|
export const useFetch = (requestType, url, headers) => {
|
|
|
|
|
const [ response, setResponse ] = useState(null);
|
|
|
|
|
const [ loading, setLoading ] = useState(true);
|
|
|
|
|
const [ hasError, setHasError ] = useState(false);
|
|
|
|
@ -70,7 +71,7 @@ export function useFetch (requestType, url, headers) {
|
|
|
|
|
}, [ url ]);
|
|
|
|
|
|
|
|
|
|
return [ response, loading, hasError ];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hook that uses `util.file.getObjectURL` to get an async collection of blob object URLs.
|
|
|
|
@ -98,7 +99,7 @@ export function useFetch (requestType, url, headers) {
|
|
|
|
|
* }, [ images ]);
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export function useFetchImageObjectURL (path, allowedExtensions) {
|
|
|
|
|
export const useFetchImageObjectURL = (path, allowedExtensions) => {
|
|
|
|
|
const [ response, setResponse ] = useState(null);
|
|
|
|
|
const [ loading, setLoading ] = useState(true);
|
|
|
|
|
const [ hasError, setHasError ] = useState(false);
|
|
|
|
@ -116,7 +117,7 @@ export function useFetchImageObjectURL (path, allowedExtensions) {
|
|
|
|
|
}, [ path, allowedExtensions ]);
|
|
|
|
|
|
|
|
|
|
return [ response, loading, hasError ];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Simple hook to force a component to rerender.
|
|
|
|
@ -130,10 +131,10 @@ export function useFetchImageObjectURL (path, allowedExtensions) {
|
|
|
|
|
* forceUpdate();
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export function useForceUpdate () {
|
|
|
|
|
export const useForceUpdate = () => {
|
|
|
|
|
const setValue = useState(0)[1];
|
|
|
|
|
return useRef(() => setValue(v => ~v)).current;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* In rare cases you may need to do something right after component forceUpdate finishes.
|
|
|
|
@ -159,7 +160,7 @@ export function useForceUpdate () {
|
|
|
|
|
* );
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export function useForceUpdateWithCallback (callback) {
|
|
|
|
|
export const useForceUpdateWithCallback = callback => {
|
|
|
|
|
const [ value, setValue ] = useState(0);
|
|
|
|
|
const isUpdating = useRef(0);
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
@ -172,6 +173,159 @@ export function useForceUpdateWithCallback (callback) {
|
|
|
|
|
isUpdating.current = 1;
|
|
|
|
|
setValue(v => ~v);
|
|
|
|
|
}).current;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const usePrevious = value => {
|
|
|
|
|
/*
|
|
|
|
|
* The ref object is a generic container whose current property is mutable
|
|
|
|
|
* and can hold any value, similar to an instance property on a class.
|
|
|
|
|
*/
|
|
|
|
|
const ref = useRef();
|
|
|
|
|
/**
|
|
|
|
|
* Store the current value in ref.
|
|
|
|
|
*/
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
ref.current = value;
|
|
|
|
|
}, [ value ]); // Only re-run if value changes
|
|
|
|
|
/**
|
|
|
|
|
* Return previous value (happens before update in useEffect above).
|
|
|
|
|
*/
|
|
|
|
|
return ref.current;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggles a value.
|
|
|
|
|
* @param {boolean} [initialState=false] Initial state
|
|
|
|
|
* @example
|
|
|
|
|
* ```
|
|
|
|
|
* function App() {
|
|
|
|
|
* const [isTextChanged, setIsTextChanged] = useToggle();
|
|
|
|
|
*
|
|
|
|
|
* return (
|
|
|
|
|
* <button onClick={setIsTextChanged}>
|
|
|
|
|
* {isTextChanged ? 'Toggled' : 'Click to Toggle'}
|
|
|
|
|
* </button>
|
|
|
|
|
* );
|
|
|
|
|
* }
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export const useToggle = (initialState = false) => {
|
|
|
|
|
/**
|
|
|
|
|
* Initialize the state.
|
|
|
|
|
*/
|
|
|
|
|
const [ state, setState ] = useState(initialState);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Define and memorize toggler function in case we pass down the comopnent,
|
|
|
|
|
* This function change the boolean value to it's opposite value
|
|
|
|
|
*/
|
|
|
|
|
const toggle = useCallback(() => setState(state => !state), []);
|
|
|
|
|
|
|
|
|
|
return [ state, toggle ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {*} param
|
|
|
|
|
* @returns
|
|
|
|
|
* @example
|
|
|
|
|
* ```
|
|
|
|
|
* const App = () => {
|
|
|
|
|
* const { inputText, setInputText, filtered } = useFilter({
|
|
|
|
|
* keys: [ 'user.name', 'subject', 'dest.name' ],
|
|
|
|
|
* data: sampleData,
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* return (
|
|
|
|
|
* <div className='App'>
|
|
|
|
|
* <input
|
|
|
|
|
* type='text'
|
|
|
|
|
* value={inputText}
|
|
|
|
|
* onChange={evt => setInputText(evt.target.value)}
|
|
|
|
|
* />
|
|
|
|
|
* {JSON.stringify(filtered)}
|
|
|
|
|
* </div>
|
|
|
|
|
* );
|
|
|
|
|
* };
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export const useFilter = ({ keys, data }) => {
|
|
|
|
|
const [ query, setQuery ] = useState('');
|
|
|
|
|
const filtered = useMemo(
|
|
|
|
|
() => data.filter(createFilter(query, keys)),
|
|
|
|
|
[ data, query ]
|
|
|
|
|
);
|
|
|
|
|
return [ query, setQuery, filtered ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useSticky = ({ defaultSticky = false }) => {
|
|
|
|
|
const [ sticky, setSticky ] = useState(defaultSticky);
|
|
|
|
|
const tableRef = useRef(null);
|
|
|
|
|
const toggleSticky = useCallback(
|
|
|
|
|
({ top, bottom }) => {
|
|
|
|
|
if (top <= 0 && bottom > 2 * 68) {
|
|
|
|
|
!sticky && setSticky(true);
|
|
|
|
|
} else {
|
|
|
|
|
sticky && setSticky(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[ sticky ]
|
|
|
|
|
);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
toggleSticky(tableRef.current.getBoundingClientRect());
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener('scroll', handleScroll);
|
|
|
|
|
};
|
|
|
|
|
}, [ toggleSticky ]);
|
|
|
|
|
return { sticky, setSticky };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Sourced from @see {@link https://codesandbox.io/s/animate-height-framer-motion-yn59l?file=/src/use-callback-ref.js:48-209}
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
export const useCallbackRef = () => {
|
|
|
|
|
const [ ref, setRef ] = useState(null);
|
|
|
|
|
const fn = useCallback(node => {
|
|
|
|
|
setRef(node);
|
|
|
|
|
}, []);
|
|
|
|
|
return [ ref, fn ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Sourced from @see {@link https://codesandbox.io/s/animate-height-framer-motion-yn59l?file=/src/use-measure.js}
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
export const useMeasure = ref => {
|
|
|
|
|
const [ element, attachRef ] = useCallbackRef();
|
|
|
|
|
const [ bounds, setBounds ] = useState({});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
function onResize([ entry ]) {
|
|
|
|
|
setBounds({
|
|
|
|
|
height: entry.contentRect.height
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const observer = new ResizeObserver(onResize);
|
|
|
|
|
|
|
|
|
|
if (element && element.current) {
|
|
|
|
|
observer.observe(element.current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => observer.disconnect();
|
|
|
|
|
}, [ element ]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
attachRef(ref);
|
|
|
|
|
}, [ attachRef, ref ]);
|
|
|
|
|
|
|
|
|
|
return bounds;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default this;
|
|
|
|
|