Item.jsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import React, { memo } from "react";
  2. import styled from "styled-components";
  3. import lockIcon from "../../../images/lock.svg";
  4. const ItemWrapper = styled.div.attrs(({ rotation, locked, selected }) => {
  5. let className = "item";
  6. if (locked) {
  7. className += " locked";
  8. }
  9. if (selected) {
  10. className += " selected";
  11. }
  12. return {
  13. className,
  14. style: {
  15. transform: `rotate(${rotation}deg)`,
  16. },
  17. };
  18. })`
  19. display: inline-block;
  20. transition: transform 150ms;
  21. user-select: none;
  22. & .corner {
  23. position: absolute;
  24. width: 0px;
  25. height: 0px;
  26. }
  27. & .top-left {
  28. top: 0;
  29. left: 0;
  30. }
  31. & .top-right {
  32. top: 0;
  33. right: 0;
  34. }
  35. & .bottom-left {
  36. bottom: 0;
  37. left: 0;
  38. }
  39. & .bottom-right {
  40. bottom: 0;
  41. right: 0;
  42. }
  43. padding: 4px;
  44. &.selected {
  45. border: 2px dashed #ff0000a0;
  46. padding: 2px;
  47. cursor: pointer;
  48. }
  49. &.locked::after {
  50. content: "";
  51. position: absolute;
  52. width: 24px;
  53. height: 30px;
  54. top: 4px;
  55. right: 4px;
  56. opacity: 0.1;
  57. background-image: url(${lockIcon});
  58. background-size: cover;
  59. user-select: none;
  60. }
  61. &.locked:hover::after {
  62. opacity: 0.3;
  63. }
  64. `;
  65. const Item = ({
  66. setState,
  67. state: { type, x, y, rotation = 0, id, locked, layer, ...rest } = {},
  68. animate = "hvr-pop",
  69. isSelected,
  70. getComponent,
  71. }) => {
  72. const itemRef = React.useRef(null);
  73. const [unlock, setUnlock] = React.useState(false);
  74. const isMountedRef = React.useRef(false);
  75. const animateRef = React.useRef(null);
  76. // Allow to operate on locked item if key is pressed
  77. React.useEffect(() => {
  78. const onKeyDown = (e) => {
  79. if (e.key === "u" || e.key === "l") {
  80. setUnlock(true);
  81. }
  82. };
  83. const onKeyUp = (e) => {
  84. if (e.key === "u" || e.key === "l") {
  85. setUnlock(false);
  86. }
  87. };
  88. document.addEventListener("keydown", onKeyDown);
  89. document.addEventListener("keyup", onKeyUp);
  90. return () => {
  91. document.removeEventListener("keydown", onKeyDown);
  92. document.removeEventListener("keyup", onKeyUp);
  93. };
  94. }, []);
  95. const Component = getComponent(type);
  96. const updateState = React.useCallback(
  97. (callbackOrItem, sync = true) => setState(id, callbackOrItem, sync),
  98. [setState, id]
  99. );
  100. // Update actual size when update
  101. React.useEffect(() => {
  102. isMountedRef.current = true;
  103. return () => {
  104. isMountedRef.current = false;
  105. };
  106. }, []);
  107. React.useEffect(() => {
  108. animateRef.current.className = animate;
  109. }, [animate]);
  110. const removeClass = (e) => {
  111. e.target.className = "";
  112. };
  113. return (
  114. <div
  115. style={{
  116. transform: `translate(${x}px, ${y}px)`,
  117. display: "inline-block",
  118. zIndex: (layer || 0) + 3,
  119. position: "absolute",
  120. top: 0,
  121. left: 0,
  122. }}
  123. >
  124. <ItemWrapper
  125. rotation={rotation}
  126. locked={locked && !unlock}
  127. selected={isSelected}
  128. ref={itemRef}
  129. layer={layer}
  130. id={id}
  131. >
  132. <div ref={animateRef} onAnimationEnd={removeClass}>
  133. <Component {...rest} setState={updateState} />
  134. <div className="corner top-left"></div>
  135. <div className="corner top-right"></div>
  136. <div className="corner bottom-left"></div>
  137. <div className="corner bottom-right"></div>
  138. </div>
  139. </ItemWrapper>
  140. </div>
  141. );
  142. };
  143. const MemoizedItem = memo(
  144. Item,
  145. (
  146. { state: prevState, setState: prevSetState, isSelected: prevIsSelected },
  147. { state: nextState, setState: nextSetState, isSelected: nextIsSelected }
  148. ) => {
  149. return (
  150. JSON.stringify(prevState) === JSON.stringify(nextState) &&
  151. prevSetState === nextSetState &&
  152. prevIsSelected === nextIsSelected
  153. );
  154. }
  155. );
  156. export default MemoizedItem;