mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-12 18:37:37 +03:00
feat(Hooks):
- useEventListener: add support to listen to events from elememts - useEscakeKey: when multiple components use this hook, and when triggered close the last one opened - useImage: check if image is loaded. Also it checks if the image overflows given a maxWidth feat(useDetectTextOverflow): detect if a text element has an overflow
This commit is contained in:
parent
2bc7c10030
commit
206fead689
@ -4,6 +4,9 @@ import * as Actions from "~/common/actions";
|
||||
import * as Events from "~/common/custom-events";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { last } from "lodash";
|
||||
|
||||
export const useMounted = (callback, depedencies) => {
|
||||
const mountedRef = React.useRef(false);
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
@ -395,12 +398,15 @@ export const useMediaQuery = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useEventListener = (type, handler, dependencies) => {
|
||||
export const useEventListener = ({ type, handler, ref }, dependencies) => {
|
||||
React.useEffect(() => {
|
||||
if (!window) return;
|
||||
let element = window;
|
||||
if (ref) element = ref.current;
|
||||
|
||||
window.addEventListener(type, handler);
|
||||
return () => window.removeEventListener(type, handler);
|
||||
if (!element) return;
|
||||
|
||||
element.addEventListener(type, handler);
|
||||
return () => element.removeEventListener(type, handler);
|
||||
}, dependencies);
|
||||
};
|
||||
|
||||
@ -411,17 +417,28 @@ export const useTimeout = (callback, ms, dependencies) => {
|
||||
}, dependencies);
|
||||
};
|
||||
|
||||
let layers = [];
|
||||
const removeLayer = (id) => (layers = layers.filter((layer) => layer !== id));
|
||||
const isDeepestLayer = (id) => last(layers) === id;
|
||||
|
||||
export const useEscapeKey = (callback) => {
|
||||
const layerIdRef = React.useRef();
|
||||
React.useEffect(() => {
|
||||
layerIdRef.current = uuid();
|
||||
layers.push(layerIdRef.current);
|
||||
return () => removeLayer(layerIdRef.current);
|
||||
}, []);
|
||||
|
||||
const handleKeyUp = React.useCallback(
|
||||
(e) => {
|
||||
if (e.key === "Escape") callback();
|
||||
if (e.key === "Escape" && isDeepestLayer(layerIdRef.current)) callback?.(e);
|
||||
},
|
||||
[callback]
|
||||
);
|
||||
useEventListener("keyup", handleKeyUp, [handleKeyUp]);
|
||||
useEventListener({ type: "keyup", handler: handleKeyUp }, [handleKeyUp]);
|
||||
};
|
||||
|
||||
export const useLockScroll = ({ lock = true } = {}) => {
|
||||
export const useLockScroll = ({ lock = true } = { lock: true }) => {
|
||||
React.useEffect(() => {
|
||||
if (!lock) return;
|
||||
document.body.style.overflow = "hidden";
|
||||
@ -464,3 +481,51 @@ export const useHover = () => {
|
||||
|
||||
return [isHovered, { handleOnMouseEnter, handleOnMouseLeave }];
|
||||
};
|
||||
|
||||
export const useImage = ({ src, maxWidth }) => {
|
||||
const [imgState, setImgState] = React.useState({
|
||||
loaded: false,
|
||||
error: true,
|
||||
overflow: false,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!src) setImgState({ error: true, loaded: true });
|
||||
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
|
||||
img.onload = () => {
|
||||
if (maxWidth && img.naturalWidth < maxWidth) {
|
||||
setImgState((prev) => ({ ...prev, loaded: true, error: false, overflow: true }));
|
||||
} else {
|
||||
setImgState({ loaded: true, error: false });
|
||||
}
|
||||
};
|
||||
img.onerror = () => setImgState({ loaded: true, error: true });
|
||||
}, []);
|
||||
|
||||
return imgState;
|
||||
};
|
||||
|
||||
export const useDetectTextOverflow = ({ ref }, dependencies) => {
|
||||
const [isTextOverflowing, setTextOverflow] = React.useState(false);
|
||||
|
||||
//SOURCE(amine): https://stackoverflow.com/a/60073230
|
||||
const isEllipsisActive = (el) => {
|
||||
const styles = getComputedStyle(el);
|
||||
const widthEl = parseFloat(styles.width);
|
||||
const ctx = document.createElement("canvas").getContext("2d");
|
||||
ctx.font = `${styles.fontSize} ${styles.fontFamily}`;
|
||||
const text = ctx.measureText(el.innerText);
|
||||
return text.width > widthEl;
|
||||
};
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (!ref.current) return;
|
||||
|
||||
setTextOverflow(isEllipsisActive(ref.current));
|
||||
}, dependencies);
|
||||
|
||||
return isTextOverflowing;
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import * as Constants from "~/common/constants";
|
||||
|
||||
import { P3 } from "~/components/system/components/Typography";
|
||||
import { css } from "@emotion/react";
|
||||
import { useImage } from "~/common/hooks";
|
||||
|
||||
import ObjectPreviewPrimitive from "~/components/core/ObjectPreview/ObjectPreviewPrimitive";
|
||||
import LinkPlaceholder from "~/components/core/ObjectPreview/placeholders/Link";
|
||||
@ -120,29 +121,3 @@ export default function LinkObjectPreview({ file, ratio, ...props }) {
|
||||
</ObjectPreviewPrimitive>
|
||||
);
|
||||
}
|
||||
|
||||
const useImage = ({ src, maxWidth }) => {
|
||||
const [imgState, setImgState] = React.useState({
|
||||
loaded: false,
|
||||
error: true,
|
||||
overflow: false,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!src) setImgState({ error: true, loaded: true });
|
||||
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
|
||||
img.onload = () => {
|
||||
if (maxWidth && img.naturalWidth < maxWidth) {
|
||||
setImgState((prev) => ({ ...prev, loaded: true, error: false, overflow: true }));
|
||||
} else {
|
||||
setImgState({ loaded: true, error: false });
|
||||
}
|
||||
};
|
||||
img.onerror = () => setImgState({ loaded: true, error: true });
|
||||
}, []);
|
||||
|
||||
return imgState;
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ export const Provider = ({ children, page, data, viewer }) => {
|
||||
viewer,
|
||||
});
|
||||
|
||||
useEventListener("open-upload-jumper", showUploadJumper);
|
||||
useEventListener({ type: "open-upload-jumper", handler: showUploadModal });
|
||||
|
||||
const providerValue = React.useMemo(
|
||||
() => [
|
||||
@ -223,10 +223,10 @@ const useUploadOnDrop = ({ upload, page, data, viewer }) => {
|
||||
upload({ files, slate });
|
||||
};
|
||||
|
||||
useEventListener("dragenter", handleDragEnter, []);
|
||||
useEventListener("dragleave", handleDragLeave, []);
|
||||
useEventListener("dragover", handleDragOver, []);
|
||||
useEventListener("drop", handleDrop, []);
|
||||
useEventListener({ type: "dragenter", handler: handleDragEnter }, []);
|
||||
useEventListener({ type: "dragleave", handler: handleDragLeave }, []);
|
||||
useEventListener({ type: "dragover", handler: handleDragOver }, []);
|
||||
useEventListener({ type: "drop", handler: handleDrop }, []);
|
||||
};
|
||||
|
||||
const useUploadFromClipboard = ({ upload, uploadLink, page, data, viewer }) => {
|
||||
@ -259,5 +259,5 @@ const useUploadFromClipboard = ({ upload, uploadLink, page, data, viewer }) => {
|
||||
upload({ files, slate });
|
||||
};
|
||||
|
||||
useEventListener("paste", handlePaste, []);
|
||||
useEventListener({ type: "paste", handler: handlePaste }, []);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user