LibraryUnit.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import clsx from "clsx";
  2. import oc from "open-color";
  3. import { useEffect, useRef, useState } from "react";
  4. import { close } from "../components/icons";
  5. import { MIME_TYPES } from "../constants";
  6. import { t } from "../i18n";
  7. import { useIsMobile } from "../components/App";
  8. import { exportToSvg } from "../scene/export";
  9. import { LibraryItem } from "../types";
  10. import "./LibraryUnit.scss";
  11. // fa-plus
  12. const PLUS_ICON = (
  13. <svg viewBox="0 0 1792 1792">
  14. <path
  15. fill="currentColor"
  16. d="M1600 736v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"
  17. />
  18. </svg>
  19. );
  20. export const LibraryUnit = ({
  21. elements,
  22. pendingElements,
  23. onRemoveFromLibrary,
  24. onClick,
  25. }: {
  26. elements?: LibraryItem;
  27. pendingElements?: LibraryItem;
  28. onRemoveFromLibrary: () => void;
  29. onClick: () => void;
  30. }) => {
  31. const ref = useRef<HTMLDivElement | null>(null);
  32. useEffect(() => {
  33. const elementsToRender = elements || pendingElements;
  34. if (!elementsToRender) {
  35. return;
  36. }
  37. let svg: SVGSVGElement;
  38. const current = ref.current!;
  39. (async () => {
  40. svg = await exportToSvg(elementsToRender, {
  41. exportBackground: false,
  42. viewBackgroundColor: oc.white,
  43. });
  44. for (const child of ref.current!.children) {
  45. if (child.tagName !== "svg") {
  46. continue;
  47. }
  48. current!.removeChild(child);
  49. }
  50. current!.appendChild(svg);
  51. })();
  52. return () => {
  53. if (svg) {
  54. current.removeChild(svg);
  55. }
  56. };
  57. }, [elements, pendingElements]);
  58. const [isHovered, setIsHovered] = useState(false);
  59. const isMobile = useIsMobile();
  60. const adder = (isHovered || isMobile) && pendingElements && (
  61. <div className="library-unit__adder">{PLUS_ICON}</div>
  62. );
  63. return (
  64. <div
  65. className={clsx("library-unit", {
  66. "library-unit__active": elements || pendingElements,
  67. })}
  68. onMouseEnter={() => setIsHovered(true)}
  69. onMouseLeave={() => setIsHovered(false)}
  70. >
  71. <div
  72. className={clsx("library-unit__dragger", {
  73. "library-unit__pulse": !!pendingElements,
  74. })}
  75. ref={ref}
  76. draggable={!!elements}
  77. onClick={!!elements || !!pendingElements ? onClick : undefined}
  78. onDragStart={(event) => {
  79. setIsHovered(false);
  80. event.dataTransfer.setData(
  81. MIME_TYPES.excalidrawlib,
  82. JSON.stringify(elements),
  83. );
  84. }}
  85. />
  86. {adder}
  87. {elements && (isHovered || isMobile) && (
  88. <button
  89. className="library-unit__removeFromLibrary"
  90. aria-label={t("labels.removeFromLibrary")}
  91. onClick={onRemoveFromLibrary}
  92. >
  93. {close}
  94. </button>
  95. )}
  96. </div>
  97. );
  98. };