Ruler.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import { useMemo, useEffect, useState } from 'react';
  2. import { useCanvasStore } from '@/lib/store';
  3. import { getElementListRange } from '@/lib/utils/element';
  4. import type { PPTElement } from '@/lib/types/slides';
  5. import type { ViewportStyles } from './hooks/useViewportSize';
  6. interface RulerProps {
  7. viewportStyles: ViewportStyles;
  8. elementList: PPTElement[];
  9. }
  10. export function Ruler({ viewportStyles, elementList }: RulerProps) {
  11. const canvasScale = useCanvasStore.use.canvasScale();
  12. const activeElementIdList = useCanvasStore.use.activeElementIdList();
  13. const viewportRatio = useCanvasStore.use.viewportRatio();
  14. const viewportSize = useCanvasStore.use.viewportSize();
  15. const [elementListRange, setElementListRange] = useState<ReturnType<
  16. typeof getElementListRange
  17. > | null>(null);
  18. useEffect(() => {
  19. const els = elementList.filter((el) => activeElementIdList.includes(el.id));
  20. if (!els.length) {
  21. // eslint-disable-next-line react-hooks/set-state-in-effect -- DOM measurement requires effect
  22. setElementListRange(null);
  23. } else {
  24. setElementListRange(getElementListRange(els));
  25. }
  26. }, [elementList, activeElementIdList]);
  27. const markerSize = useMemo(() => {
  28. return (viewportStyles.width * canvasScale) / (viewportSize / 100);
  29. }, [viewportStyles.width, canvasScale, viewportSize]);
  30. const markers = Array.from({ length: 20 }, (_, i) => i + 1);
  31. return (
  32. <div className="ruler text-xs">
  33. {/* Ruler corner */}
  34. <div
  35. className="corner absolute bg-white border border-gray-200 w-5 h-5"
  36. style={{
  37. left: viewportStyles.left - 25 + 'px',
  38. top: viewportStyles.top - 25 + 'px',
  39. }}
  40. />
  41. {/* Horizontal ruler */}
  42. <div
  43. className="h absolute bg-white border border-gray-200 h-5 flex justify-between items-center overflow-hidden"
  44. style={{
  45. width: viewportStyles.width * canvasScale + 'px',
  46. left: viewportStyles.left + 'px',
  47. top: viewportStyles.top - 25 + 'px',
  48. }}
  49. >
  50. {markers.map((marker) => (
  51. <div
  52. key={`h-marker-100-${marker}`}
  53. className={`ruler-marker-100 h-full leading-5 text-right flex-shrink-0 pr-[5px] relative ${
  54. markerSize < 36 ? '[&>span]:hidden' : ''
  55. } ${markerSize < 72 ? 'before:hidden' : ''}`}
  56. style={{ width: markerSize + 'px' }}
  57. >
  58. {marker * 100 <= viewportSize && <span>{marker * 100}</span>}
  59. {/* Major tick mark */}
  60. <div className="absolute right-0 bottom-0 w-[0.1px] h-3 bg-gray-600 last:content-none" />
  61. {/* Minor tick mark (50) */}
  62. <div className="absolute right-1/2 bottom-0 w-[0.1px] h-2 bg-gray-600" />
  63. </div>
  64. ))}
  65. {elementListRange && (
  66. <div
  67. className="range absolute top-0 bottom-0 bg-primary/10"
  68. style={{
  69. left: elementListRange.minX * canvasScale + 'px',
  70. width: (elementListRange.maxX - elementListRange.minX) * canvasScale + 'px',
  71. }}
  72. />
  73. )}
  74. </div>
  75. {/* Vertical ruler */}
  76. <div
  77. className="v absolute bg-white border border-gray-200 w-5 overflow-hidden"
  78. style={{
  79. height: viewportStyles.height * canvasScale + 'px',
  80. top: viewportStyles.top + 'px',
  81. left: viewportStyles.left - 25 + 'px',
  82. }}
  83. >
  84. {markers.map((marker) => (
  85. <div
  86. key={`v-marker-100-${marker}`}
  87. className={`ruler-marker-100 w-full leading-5 text-right pb-[5px] relative [writing-mode:vertical-rl] ${
  88. markerSize < 36 ? '[&>span]:hidden' : ''
  89. } ${markerSize < 72 ? 'before:hidden' : ''}`}
  90. style={{ height: markerSize + 'px' }}
  91. >
  92. {marker * 100 <= viewportSize * viewportRatio && <span>{marker * 100}</span>}
  93. {/* Major tick mark */}
  94. <div className="absolute bottom-0 right-0 h-[0.1px] w-3 bg-gray-600 last:content-none" />
  95. {/* Minor tick mark (50) */}
  96. <div className="absolute bottom-1/2 right-0 h-[0.1px] w-2 bg-gray-600" />
  97. </div>
  98. ))}
  99. {elementListRange && (
  100. <div
  101. className="range absolute left-0 right-0 bg-primary/10"
  102. style={{
  103. top: elementListRange.minY * canvasScale + 'px',
  104. height: (elementListRange.maxY - elementListRange.minY) * canvasScale + 'px',
  105. }}
  106. />
  107. )}
  108. </div>
  109. </div>
  110. );
  111. }