mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
build: decouple package deps and introduce yarn workspaces (#7415)
* feat: decouple package deps and introduce yarn workspaces * update root directory * fix * fix scripts * fix lint * update path in scripts * remove yarn.lock files from packages * ignore workspace * dummy * dummy * remove comment check * revert workflow changes * ignore ws when installing gh actions * remove log * update path * fix * fix typo
This commit is contained in:
parent
b7d7ccc929
commit
d6cd8b78f1
567 changed files with 5066 additions and 8648 deletions
7
packages/excalidraw/hooks/useCallbackRefState.ts
Normal file
7
packages/excalidraw/hooks/useCallbackRefState.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { useCallback, useState } from "react";
|
||||
|
||||
export const useCallbackRefState = <T>() => {
|
||||
const [refValue, setRefValue] = useState<T | null>(null);
|
||||
const refCallback = useCallback((value: T | null) => setRefValue(value), []);
|
||||
return [refValue, refCallback] as const;
|
||||
};
|
46
packages/excalidraw/hooks/useCreatePortalContainer.ts
Normal file
46
packages/excalidraw/hooks/useCreatePortalContainer.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { useState, useLayoutEffect } from "react";
|
||||
import { useDevice, useExcalidrawContainer } from "../components/App";
|
||||
import { useUIAppState } from "../context/ui-appState";
|
||||
|
||||
export const useCreatePortalContainer = (opts?: {
|
||||
className?: string;
|
||||
parentSelector?: string;
|
||||
}) => {
|
||||
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const device = useDevice();
|
||||
const { theme } = useUIAppState();
|
||||
|
||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (div) {
|
||||
div.className = "";
|
||||
div.classList.add("excalidraw", ...(opts?.className?.split(/\s+/) || []));
|
||||
div.classList.toggle("excalidraw--mobile", device.editor.isMobile);
|
||||
div.classList.toggle("theme--dark", theme === "dark");
|
||||
}
|
||||
}, [div, theme, device.editor.isMobile, opts?.className]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = opts?.parentSelector
|
||||
? excalidrawContainer?.querySelector(opts.parentSelector)
|
||||
: document.body;
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const div = document.createElement("div");
|
||||
|
||||
container.appendChild(div);
|
||||
|
||||
setDiv(div);
|
||||
|
||||
return () => {
|
||||
container.removeChild(div);
|
||||
};
|
||||
}, [excalidrawContainer, opts?.parentSelector]);
|
||||
|
||||
return div;
|
||||
};
|
78
packages/excalidraw/hooks/useLibraryItemSvg.ts
Normal file
78
packages/excalidraw/hooks/useLibraryItemSvg.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { atom, useAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { COLOR_PALETTE } from "../colors";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { exportToSvg } from "../../utils";
|
||||
import { LibraryItem } from "../types";
|
||||
|
||||
export type SvgCache = Map<LibraryItem["id"], SVGSVGElement>;
|
||||
|
||||
export const libraryItemSvgsCache = atom<SvgCache>(new Map());
|
||||
|
||||
const exportLibraryItemToSvg = async (elements: LibraryItem["elements"]) => {
|
||||
return await exportToSvg({
|
||||
elements,
|
||||
appState: {
|
||||
exportBackground: false,
|
||||
viewBackgroundColor: COLOR_PALETTE.white,
|
||||
},
|
||||
files: null,
|
||||
renderEmbeddables: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const useLibraryItemSvg = (
|
||||
id: LibraryItem["id"] | null,
|
||||
elements: LibraryItem["elements"] | undefined,
|
||||
svgCache: SvgCache,
|
||||
): SVGSVGElement | undefined => {
|
||||
const [svg, setSvg] = useState<SVGSVGElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (elements) {
|
||||
if (id) {
|
||||
// Try to load cached svg
|
||||
const cachedSvg = svgCache.get(id);
|
||||
|
||||
if (cachedSvg) {
|
||||
setSvg(cachedSvg);
|
||||
} else {
|
||||
// When there is no svg in cache export it and save to cache
|
||||
(async () => {
|
||||
const exportedSvg = await exportLibraryItemToSvg(elements);
|
||||
exportedSvg.querySelector(".style-fonts")?.remove();
|
||||
|
||||
if (exportedSvg) {
|
||||
svgCache.set(id, exportedSvg);
|
||||
setSvg(exportedSvg);
|
||||
}
|
||||
})();
|
||||
}
|
||||
} else {
|
||||
// When we have no id (usualy selected items from canvas) just export the svg
|
||||
(async () => {
|
||||
const exportedSvg = await exportLibraryItemToSvg(elements);
|
||||
setSvg(exportedSvg);
|
||||
})();
|
||||
}
|
||||
}
|
||||
}, [id, elements, svgCache, setSvg]);
|
||||
|
||||
return svg;
|
||||
};
|
||||
|
||||
export const useLibraryCache = () => {
|
||||
const [svgCache] = useAtom(libraryItemSvgsCache, jotaiScope);
|
||||
|
||||
const clearLibraryCache = () => svgCache.clear();
|
||||
|
||||
const deleteItemsFromLibraryCache = (items: LibraryItem["id"][]) => {
|
||||
items.forEach((item) => svgCache.delete(item));
|
||||
};
|
||||
|
||||
return {
|
||||
clearLibraryCache,
|
||||
deleteItemsFromLibraryCache,
|
||||
svgCache,
|
||||
};
|
||||
};
|
86
packages/excalidraw/hooks/useOutsideClick.ts
Normal file
86
packages/excalidraw/hooks/useOutsideClick.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { useEffect } from "react";
|
||||
import { EVENT } from "../constants";
|
||||
|
||||
export function useOutsideClick<T extends HTMLElement>(
|
||||
ref: React.RefObject<T>,
|
||||
/** if performance is of concern, memoize the callback */
|
||||
callback: (event: Event) => void,
|
||||
/**
|
||||
* Optional callback which is called on every click.
|
||||
*
|
||||
* Should return `true` if click should be considered as inside the container,
|
||||
* and `false` if it falls outside and should call the `callback`.
|
||||
*
|
||||
* Returning `true` overrides the default behavior and `callback` won't be
|
||||
* called.
|
||||
*
|
||||
* Returning `undefined` will fallback to the default behavior.
|
||||
*/
|
||||
isInside?: (
|
||||
event: Event & { target: HTMLElement },
|
||||
/** the element of the passed ref */
|
||||
container: T,
|
||||
) => boolean | undefined,
|
||||
) {
|
||||
useEffect(() => {
|
||||
function onOutsideClick(event: Event) {
|
||||
const _event = event as Event & { target: T };
|
||||
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isInsideOverride = isInside?.(_event, ref.current);
|
||||
|
||||
if (isInsideOverride === true) {
|
||||
return;
|
||||
} else if (isInsideOverride === false) {
|
||||
return callback(_event);
|
||||
}
|
||||
|
||||
// clicked element is in the descenendant of the target container
|
||||
if (
|
||||
ref.current.contains(_event.target) ||
|
||||
// target is detached from DOM (happens when the element is removed
|
||||
// on a pointerup event fired *before* this handler's pointerup is
|
||||
// dispatched)
|
||||
!document.documentElement.contains(_event.target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isClickOnRadixPortal =
|
||||
_event.target.closest("[data-radix-portal]") ||
|
||||
// when radix popup is in "modal" mode, it disables pointer events on
|
||||
// the `body` element, so the target element is going to be the `html`
|
||||
// (note: this won't work if we selectively re-enable pointer events on
|
||||
// specific elements as we do with navbar or excalidraw UI elements)
|
||||
(_event.target === document.documentElement &&
|
||||
document.body.style.pointerEvents === "none");
|
||||
|
||||
// if clicking on radix portal, assume it's a popup that
|
||||
// should be considered as part of the UI. Obviously this is a terrible
|
||||
// hack you can end up click on radix popups that outside the tree,
|
||||
// but it works for most cases and the downside is minimal for now
|
||||
if (isClickOnRadixPortal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clicking on a container that ignores outside clicks
|
||||
if (_event.target.closest("[data-prevent-outside-click]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(_event);
|
||||
}
|
||||
|
||||
// note: don't use `click` because it often reports incorrect `event.target`
|
||||
document.addEventListener(EVENT.POINTER_DOWN, onOutsideClick);
|
||||
document.addEventListener(EVENT.TOUCH_START, onOutsideClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(EVENT.POINTER_DOWN, onOutsideClick);
|
||||
document.removeEventListener(EVENT.TOUCH_START, onOutsideClick);
|
||||
};
|
||||
}, [ref, callback, isInside]);
|
||||
}
|
32
packages/excalidraw/hooks/useScrollPosition.ts
Normal file
32
packages/excalidraw/hooks/useScrollPosition.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { useEffect } from "react";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
const scrollPositionAtom = atom<number>(0);
|
||||
|
||||
export const useScrollPosition = <T extends HTMLElement>(
|
||||
elementRef: React.RefObject<T>,
|
||||
) => {
|
||||
const [scrollPosition, setScrollPosition] = useAtom(scrollPositionAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: element } = elementRef;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleScroll = throttle(() => {
|
||||
const { scrollTop } = element;
|
||||
setScrollPosition(scrollTop);
|
||||
}, 200);
|
||||
|
||||
element.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
handleScroll.cancel();
|
||||
element.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [elementRef, setScrollPosition]);
|
||||
|
||||
return scrollPosition;
|
||||
};
|
7
packages/excalidraw/hooks/useStable.ts
Normal file
7
packages/excalidraw/hooks/useStable.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { useRef } from "react";
|
||||
|
||||
export const useStable = <T extends Record<string, any>>(value: T) => {
|
||||
const ref = useRef<T>(value);
|
||||
Object.assign(ref.current, value);
|
||||
return ref.current;
|
||||
};
|
9
packages/excalidraw/hooks/useTransition.ts
Normal file
9
packages/excalidraw/hooks/useTransition.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React, { useCallback } from "react";
|
||||
|
||||
/** noop polyfill for v17. Subset of API available */
|
||||
function useTransitionPolyfill() {
|
||||
const startTransition = useCallback((callback: () => void) => callback(), []);
|
||||
return [false, startTransition] as const;
|
||||
}
|
||||
|
||||
export const useTransition = React.useTransition || useTransitionPolyfill;
|
Loading…
Add table
Add a link
Reference in a new issue