@open-slide/core 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-cUKUY4bh.js → build-pqF4W1Yi.js} +1 -1
- package/dist/cli/bin.js +3 -3
- package/dist/{config-DOcMmFJ7.js → config-CtwxMYv9.js} +375 -45
- package/dist/{dev-Brzmgu64.js → dev-CJX97uiy.js} +1 -1
- package/dist/{preview-Bf8iFXA-.js → preview-IuLPcL5y.js} +1 -1
- package/dist/vite/index.js +1 -1
- package/package.json +3 -1
- package/src/app/App.tsx +2 -0
- package/src/app/components/PdfProgressToast.tsx +23 -0
- package/src/app/components/Player.tsx +18 -3
- package/src/app/components/inspector/CommentWidget.tsx +1 -1
- package/src/app/components/inspector/InspectOverlay.tsx +81 -41
- package/src/app/components/inspector/InspectorPanel.tsx +805 -0
- package/src/app/components/inspector/InspectorProvider.tsx +199 -13
- package/src/app/components/inspector/SaveBar.tsx +77 -0
- package/src/app/components/ui/input.tsx +21 -0
- package/src/app/components/ui/label.tsx +24 -0
- package/src/app/components/ui/progress.tsx +31 -0
- package/src/app/components/ui/select.tsx +190 -0
- package/src/app/components/ui/slider.tsx +61 -0
- package/src/app/components/ui/sonner.tsx +38 -0
- package/src/app/components/ui/textarea.tsx +18 -0
- package/src/app/components/ui/toggle-group.tsx +83 -0
- package/src/app/components/ui/toggle.tsx +45 -0
- package/src/app/components/ui/tooltip.tsx +55 -0
- package/src/app/lib/export-pdf.ts +197 -0
- package/src/app/lib/inspector/fiber.ts +40 -5
- package/src/app/lib/inspector/useEditor.ts +61 -0
- package/src/app/lib/print-ready.ts +58 -0
- package/src/app/lib/useWheelPageNavigation.ts +92 -0
- package/src/app/routes/Slide.tsx +91 -6
- package/src/app/components/inspector/CommentPopover.tsx +0 -94
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import { findSlideSource, type SlideSourceHit } from '@/lib/inspector/fiber';
|
|
3
|
-
import { CommentPopover } from './CommentPopover';
|
|
4
3
|
import { useInspector } from './InspectorProvider';
|
|
5
4
|
|
|
6
5
|
type Highlight = { rect: DOMRect; hit: SlideSourceHit };
|
|
7
6
|
|
|
7
|
+
type RelRect = { left: number; top: number; width: number; height: number };
|
|
8
|
+
|
|
9
|
+
const FRAME_FADE_MS = 150;
|
|
10
|
+
const FRAME_MORPH_MS = 180;
|
|
11
|
+
|
|
8
12
|
export function InspectOverlay() {
|
|
9
|
-
const { active, slideId,
|
|
13
|
+
const { active, slideId, selected, setSelected, cancel } = useInspector();
|
|
10
14
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
11
15
|
const [hover, setHover] = useState<Highlight | null>(null);
|
|
12
16
|
|
|
@@ -25,32 +29,23 @@ export function InspectOverlay() {
|
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
const onMove = (e: PointerEvent) => {
|
|
28
|
-
if (pending) return;
|
|
29
32
|
const el = pickElement(e.clientX, e.clientY);
|
|
30
33
|
if (!el) return setHover(null);
|
|
31
|
-
const hit = findSlideSource(el, slideId);
|
|
34
|
+
const hit = findSlideSource(el, slideId, { hostOnly: true });
|
|
32
35
|
if (!hit) return setHover(null);
|
|
33
36
|
setHover({ rect: hit.anchor.getBoundingClientRect(), hit });
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
const onClick = (e: MouseEvent) => {
|
|
37
|
-
if (pending) return;
|
|
38
40
|
if (e.target instanceof Element && e.target.closest('[data-inspector-ui]')) return;
|
|
39
41
|
const el = pickElement(e.clientX, e.clientY);
|
|
40
42
|
if (!el) return;
|
|
41
|
-
const hit = findSlideSource(el, slideId);
|
|
43
|
+
const hit = findSlideSource(el, slideId, { hostOnly: true });
|
|
42
44
|
if (!hit) return;
|
|
43
45
|
e.preventDefault();
|
|
44
46
|
e.stopPropagation();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
line: hit.line,
|
|
48
|
-
column: hit.column,
|
|
49
|
-
anchorRect,
|
|
50
|
-
clickX: e.clientX,
|
|
51
|
-
clickY: e.clientY,
|
|
52
|
-
});
|
|
53
|
-
setHover({ rect: anchorRect, hit });
|
|
47
|
+
setSelected({ line: hit.line, column: hit.column, anchor: hit.anchor });
|
|
48
|
+
setHover({ rect: hit.anchor.getBoundingClientRect(), hit });
|
|
54
49
|
};
|
|
55
50
|
|
|
56
51
|
window.addEventListener('pointermove', onMove, true);
|
|
@@ -61,36 +56,81 @@ export function InspectOverlay() {
|
|
|
61
56
|
window.removeEventListener('click', onClick, true);
|
|
62
57
|
window.removeEventListener('keydown', onKey, true);
|
|
63
58
|
};
|
|
64
|
-
}, [active, slideId,
|
|
59
|
+
}, [active, slideId, setSelected, cancel]);
|
|
65
60
|
|
|
66
|
-
|
|
61
|
+
return (
|
|
62
|
+
<FrameOverlay
|
|
63
|
+
active={active}
|
|
64
|
+
overlayRef={overlayRef}
|
|
65
|
+
// Pin to the selection so the highlight tracks what the panel
|
|
66
|
+
// is editing even after the cursor moves away.
|
|
67
|
+
targetRect={selected?.anchor.getBoundingClientRect() ?? hover?.rect ?? null}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
67
71
|
|
|
72
|
+
function FrameOverlay({
|
|
73
|
+
active,
|
|
74
|
+
overlayRef,
|
|
75
|
+
targetRect,
|
|
76
|
+
}: {
|
|
77
|
+
active: boolean;
|
|
78
|
+
overlayRef: React.RefObject<HTMLDivElement>;
|
|
79
|
+
targetRect: DOMRect | null;
|
|
80
|
+
}) {
|
|
68
81
|
const overlayRect = overlayRef.current?.getBoundingClientRect();
|
|
69
|
-
const
|
|
82
|
+
const visible = !!(active && targetRect && overlayRect);
|
|
83
|
+
|
|
84
|
+
// Hold the last rect so the frame stays put during fade-out, when
|
|
85
|
+
// `targetRect` has already gone null.
|
|
86
|
+
const lastRectRef = useRef<RelRect | null>(null);
|
|
87
|
+
if (visible && targetRect && overlayRect) {
|
|
88
|
+
lastRectRef.current = {
|
|
89
|
+
left: targetRect.left - overlayRect.left,
|
|
90
|
+
top: targetRect.top - overlayRect.top,
|
|
91
|
+
width: targetRect.width,
|
|
92
|
+
height: targetRect.height,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// First render after appearing: snap to the new rect (no transition).
|
|
97
|
+
// Subsequent rect changes in the same visible session: animate.
|
|
98
|
+
const [morph, setMorph] = useState(false);
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
if (visible) {
|
|
101
|
+
setMorph(true);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const t = setTimeout(() => setMorph(false), FRAME_FADE_MS);
|
|
105
|
+
return () => clearTimeout(t);
|
|
106
|
+
}, [visible]);
|
|
107
|
+
|
|
108
|
+
if (!active) return null;
|
|
109
|
+
const rect = lastRectRef.current;
|
|
110
|
+
const transition = morph
|
|
111
|
+
? `left ${FRAME_MORPH_MS}ms ease-out, top ${FRAME_MORPH_MS}ms ease-out, ` +
|
|
112
|
+
`width ${FRAME_MORPH_MS}ms ease-out, height ${FRAME_MORPH_MS}ms ease-out, ` +
|
|
113
|
+
`opacity ${FRAME_FADE_MS}ms ease-out`
|
|
114
|
+
: `opacity ${FRAME_FADE_MS}ms ease-out`;
|
|
70
115
|
|
|
71
116
|
return (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
/>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
{pending && <CommentPopover />}
|
|
93
|
-
</>
|
|
117
|
+
<div ref={overlayRef} data-inspector-ui className="pointer-events-none absolute inset-0 z-30">
|
|
118
|
+
{rect && (
|
|
119
|
+
<div
|
|
120
|
+
className="absolute"
|
|
121
|
+
style={{
|
|
122
|
+
left: rect.left,
|
|
123
|
+
top: rect.top,
|
|
124
|
+
width: rect.width,
|
|
125
|
+
height: rect.height,
|
|
126
|
+
opacity: visible ? 1 : 0,
|
|
127
|
+
transition,
|
|
128
|
+
outline: '2px solid #3b82f6',
|
|
129
|
+
background: 'rgba(59,130,246,0.1)',
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
94
134
|
);
|
|
95
135
|
}
|
|
96
136
|
|