@retor/react-native 0.3.6 → 0.4.0
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/index.d.mts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +64 -31
- package/dist/index.mjs +64 -31
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -43,6 +43,15 @@ interface InitPayload {
|
|
|
43
43
|
interface LineProgressPayload {
|
|
44
44
|
progress: number;
|
|
45
45
|
closestTagId: string | null;
|
|
46
|
+
/** Point on the line at current progress (project-local space). */
|
|
47
|
+
targetPosition?: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
z: number;
|
|
51
|
+
} | null;
|
|
52
|
+
/** Distance along the line from the start.
|
|
53
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
54
|
+
distanceFromStart?: number | null;
|
|
46
55
|
}
|
|
47
56
|
interface ViewerHandle {
|
|
48
57
|
openLine: (lineId: string) => void;
|
|
@@ -55,29 +64,39 @@ interface ViewerHandle {
|
|
|
55
64
|
interface NoteSubmitPayload {
|
|
56
65
|
text: string;
|
|
57
66
|
isPrivate: boolean;
|
|
67
|
+
/** The tag that was active when the + button was pressed. */
|
|
58
68
|
tagId: string | null;
|
|
59
69
|
lineId: string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Point on the line at the scroll position when the + button was pressed,
|
|
72
|
+
* in project-local space. This is where the note visually anchors.
|
|
73
|
+
*/
|
|
60
74
|
position: {
|
|
61
75
|
x: number;
|
|
62
76
|
y: number;
|
|
63
77
|
z: number;
|
|
64
78
|
} | null;
|
|
79
|
+
/** Scroll progress along the line (0..1) when the + was pressed. */
|
|
80
|
+
progress: number;
|
|
81
|
+
/** Distance from the start of the line when the + was pressed.
|
|
82
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
83
|
+
distanceFromStart: number | null;
|
|
65
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Stable data + commands. Updates only when meaningful things change
|
|
87
|
+
* (line opens/closes, notes loaded, project changes). Does NOT update per frame.
|
|
88
|
+
*/
|
|
66
89
|
interface RetorBridgeContextValue {
|
|
67
90
|
project: RetorProject | null;
|
|
68
91
|
lines: RetorLine[];
|
|
69
92
|
activeLineId: string | null;
|
|
70
93
|
activeLine: RetorLine | null;
|
|
71
|
-
closestTagId: string | null;
|
|
72
|
-
progress: number;
|
|
73
|
-
isPlaying: boolean;
|
|
74
94
|
isAddNoteOpen: boolean;
|
|
75
95
|
addNoteTagId: string | null;
|
|
76
96
|
controls: ViewerHandle;
|
|
77
97
|
openAddNote: (tagId?: string) => void;
|
|
78
98
|
closeAddNote: () => void;
|
|
79
99
|
submitNote: (text: string, isPrivate?: boolean) => void;
|
|
80
|
-
onNoteSubmit?: (payload: NoteSubmitPayload) => void;
|
|
81
100
|
}
|
|
82
101
|
declare function useRetorBridge(): RetorBridgeContextValue;
|
|
83
102
|
/** Returns the array of all lines in the current project. */
|
|
@@ -91,11 +110,18 @@ declare function useProject(): RetorProject | null;
|
|
|
91
110
|
declare function useActiveLine(): RetorLine | null;
|
|
92
111
|
/**
|
|
93
112
|
* Returns scroll progress (0..1) and the id of the closest tag along the
|
|
94
|
-
* currently active line.
|
|
113
|
+
* currently active line. Components using this re-render on every frame
|
|
114
|
+
* during camera scroll — only call from leaves (tag items, progress rings).
|
|
95
115
|
*/
|
|
96
116
|
declare function useLineProgress(): {
|
|
97
117
|
progress: number;
|
|
98
118
|
closestTagId: string | null;
|
|
119
|
+
targetPosition: {
|
|
120
|
+
x: number;
|
|
121
|
+
y: number;
|
|
122
|
+
z: number;
|
|
123
|
+
} | null;
|
|
124
|
+
distanceFromStart: number | null;
|
|
99
125
|
};
|
|
100
126
|
/**
|
|
101
127
|
* Returns the autoplay state and controls.
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,15 @@ interface InitPayload {
|
|
|
43
43
|
interface LineProgressPayload {
|
|
44
44
|
progress: number;
|
|
45
45
|
closestTagId: string | null;
|
|
46
|
+
/** Point on the line at current progress (project-local space). */
|
|
47
|
+
targetPosition?: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
z: number;
|
|
51
|
+
} | null;
|
|
52
|
+
/** Distance along the line from the start.
|
|
53
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
54
|
+
distanceFromStart?: number | null;
|
|
46
55
|
}
|
|
47
56
|
interface ViewerHandle {
|
|
48
57
|
openLine: (lineId: string) => void;
|
|
@@ -55,29 +64,39 @@ interface ViewerHandle {
|
|
|
55
64
|
interface NoteSubmitPayload {
|
|
56
65
|
text: string;
|
|
57
66
|
isPrivate: boolean;
|
|
67
|
+
/** The tag that was active when the + button was pressed. */
|
|
58
68
|
tagId: string | null;
|
|
59
69
|
lineId: string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Point on the line at the scroll position when the + button was pressed,
|
|
72
|
+
* in project-local space. This is where the note visually anchors.
|
|
73
|
+
*/
|
|
60
74
|
position: {
|
|
61
75
|
x: number;
|
|
62
76
|
y: number;
|
|
63
77
|
z: number;
|
|
64
78
|
} | null;
|
|
79
|
+
/** Scroll progress along the line (0..1) when the + was pressed. */
|
|
80
|
+
progress: number;
|
|
81
|
+
/** Distance from the start of the line when the + was pressed.
|
|
82
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
83
|
+
distanceFromStart: number | null;
|
|
65
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Stable data + commands. Updates only when meaningful things change
|
|
87
|
+
* (line opens/closes, notes loaded, project changes). Does NOT update per frame.
|
|
88
|
+
*/
|
|
66
89
|
interface RetorBridgeContextValue {
|
|
67
90
|
project: RetorProject | null;
|
|
68
91
|
lines: RetorLine[];
|
|
69
92
|
activeLineId: string | null;
|
|
70
93
|
activeLine: RetorLine | null;
|
|
71
|
-
closestTagId: string | null;
|
|
72
|
-
progress: number;
|
|
73
|
-
isPlaying: boolean;
|
|
74
94
|
isAddNoteOpen: boolean;
|
|
75
95
|
addNoteTagId: string | null;
|
|
76
96
|
controls: ViewerHandle;
|
|
77
97
|
openAddNote: (tagId?: string) => void;
|
|
78
98
|
closeAddNote: () => void;
|
|
79
99
|
submitNote: (text: string, isPrivate?: boolean) => void;
|
|
80
|
-
onNoteSubmit?: (payload: NoteSubmitPayload) => void;
|
|
81
100
|
}
|
|
82
101
|
declare function useRetorBridge(): RetorBridgeContextValue;
|
|
83
102
|
/** Returns the array of all lines in the current project. */
|
|
@@ -91,11 +110,18 @@ declare function useProject(): RetorProject | null;
|
|
|
91
110
|
declare function useActiveLine(): RetorLine | null;
|
|
92
111
|
/**
|
|
93
112
|
* Returns scroll progress (0..1) and the id of the closest tag along the
|
|
94
|
-
* currently active line.
|
|
113
|
+
* currently active line. Components using this re-render on every frame
|
|
114
|
+
* during camera scroll — only call from leaves (tag items, progress rings).
|
|
95
115
|
*/
|
|
96
116
|
declare function useLineProgress(): {
|
|
97
117
|
progress: number;
|
|
98
118
|
closestTagId: string | null;
|
|
119
|
+
targetPosition: {
|
|
120
|
+
x: number;
|
|
121
|
+
y: number;
|
|
122
|
+
z: number;
|
|
123
|
+
} | null;
|
|
124
|
+
distanceFromStart: number | null;
|
|
99
125
|
};
|
|
100
126
|
/**
|
|
101
127
|
* Returns the autoplay state and controls.
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
54
54
|
// src/context.tsx
|
|
55
55
|
var import_react = __toESM(require("react"));
|
|
56
56
|
var RetorBridgeContext = (0, import_react.createContext)(null);
|
|
57
|
+
var RetorProgressContext = (0, import_react.createContext)({
|
|
58
|
+
progress: 0,
|
|
59
|
+
closestTagId: null,
|
|
60
|
+
targetPosition: null,
|
|
61
|
+
distanceFromStart: null,
|
|
62
|
+
isPlaying: false
|
|
63
|
+
});
|
|
57
64
|
function noop() {
|
|
58
65
|
}
|
|
59
66
|
var noopHandle = {
|
|
@@ -68,9 +75,6 @@ var fallback = {
|
|
|
68
75
|
lines: [],
|
|
69
76
|
activeLineId: null,
|
|
70
77
|
activeLine: null,
|
|
71
|
-
closestTagId: null,
|
|
72
|
-
progress: 0,
|
|
73
|
-
isPlaying: false,
|
|
74
78
|
isAddNoteOpen: false,
|
|
75
79
|
addNoteTagId: null,
|
|
76
80
|
controls: noopHandle,
|
|
@@ -81,6 +85,9 @@ var fallback = {
|
|
|
81
85
|
function useRetorBridge() {
|
|
82
86
|
return (0, import_react.useContext)(RetorBridgeContext) ?? fallback;
|
|
83
87
|
}
|
|
88
|
+
function useRetorProgress() {
|
|
89
|
+
return (0, import_react.useContext)(RetorProgressContext);
|
|
90
|
+
}
|
|
84
91
|
function useLines() {
|
|
85
92
|
return useRetorBridge().lines;
|
|
86
93
|
}
|
|
@@ -91,11 +98,15 @@ function useActiveLine() {
|
|
|
91
98
|
return useRetorBridge().activeLine;
|
|
92
99
|
}
|
|
93
100
|
function useLineProgress() {
|
|
94
|
-
const { progress, closestTagId } =
|
|
95
|
-
return (0, import_react.useMemo)(
|
|
101
|
+
const { progress, closestTagId, targetPosition, distanceFromStart } = useRetorProgress();
|
|
102
|
+
return (0, import_react.useMemo)(
|
|
103
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart }),
|
|
104
|
+
[progress, closestTagId, targetPosition, distanceFromStart]
|
|
105
|
+
);
|
|
96
106
|
}
|
|
97
107
|
function useAutoplay() {
|
|
98
|
-
const { isPlaying
|
|
108
|
+
const { isPlaying } = useRetorProgress();
|
|
109
|
+
const { controls } = useRetorBridge();
|
|
99
110
|
return (0, import_react.useMemo)(() => ({
|
|
100
111
|
isPlaying,
|
|
101
112
|
toggle: () => controls.toggleAutoplay(),
|
|
@@ -116,6 +127,9 @@ function useAddNote() {
|
|
|
116
127
|
function RetorBridgeProvider({ value, children }) {
|
|
117
128
|
return /* @__PURE__ */ import_react.default.createElement(RetorBridgeContext.Provider, { value }, children);
|
|
118
129
|
}
|
|
130
|
+
function RetorProgressProvider({ value, children }) {
|
|
131
|
+
return /* @__PURE__ */ import_react.default.createElement(RetorProgressContext.Provider, { value }, children);
|
|
132
|
+
}
|
|
119
133
|
|
|
120
134
|
// src/Viewer.tsx
|
|
121
135
|
var import_react2 = __toESM(require("react"));
|
|
@@ -194,6 +208,8 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
194
208
|
const [activeLineId, setActiveLineId] = (0, import_react2.useState)(null);
|
|
195
209
|
const [closestTagId, setClosestTagId] = (0, import_react2.useState)(null);
|
|
196
210
|
const [progress, setProgress] = (0, import_react2.useState)(0);
|
|
211
|
+
const [targetPosition, setTargetPosition] = (0, import_react2.useState)(null);
|
|
212
|
+
const [distanceFromStart, setDistanceFromStart] = (0, import_react2.useState)(null);
|
|
197
213
|
const [isPlaying, setIsPlaying] = (0, import_react2.useState)(false);
|
|
198
214
|
const [isAddNoteOpen, setIsAddNoteOpen] = (0, import_react2.useState)(false);
|
|
199
215
|
const [addNoteTagId, setAddNoteTagId] = (0, import_react2.useState)(null);
|
|
@@ -235,8 +251,11 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
235
251
|
const closestTagIdRef = (0, import_react2.useRef)(closestTagId);
|
|
236
252
|
const addNoteTagIdRef = (0, import_react2.useRef)(addNoteTagId);
|
|
237
253
|
const activeLineIdRef = (0, import_react2.useRef)(activeLineId);
|
|
238
|
-
const linesRef = (0, import_react2.useRef)(lines);
|
|
239
254
|
const onNoteSubmitRef = (0, import_react2.useRef)(onNoteSubmit);
|
|
255
|
+
const noteSnapshotRef = (0, import_react2.useRef)({ progress: 0, targetPosition: null, distanceFromStart: null });
|
|
256
|
+
const progressRef = (0, import_react2.useRef)(progress);
|
|
257
|
+
const targetPositionRef = (0, import_react2.useRef)(targetPosition);
|
|
258
|
+
const distanceFromStartRef = (0, import_react2.useRef)(distanceFromStart);
|
|
240
259
|
(0, import_react2.useEffect)(() => {
|
|
241
260
|
closestTagIdRef.current = closestTagId;
|
|
242
261
|
}, [closestTagId]);
|
|
@@ -246,14 +265,25 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
246
265
|
(0, import_react2.useEffect)(() => {
|
|
247
266
|
activeLineIdRef.current = activeLineId;
|
|
248
267
|
}, [activeLineId]);
|
|
249
|
-
(0, import_react2.useEffect)(() => {
|
|
250
|
-
linesRef.current = lines;
|
|
251
|
-
}, [lines]);
|
|
252
268
|
(0, import_react2.useEffect)(() => {
|
|
253
269
|
onNoteSubmitRef.current = onNoteSubmit;
|
|
254
270
|
}, [onNoteSubmit]);
|
|
271
|
+
(0, import_react2.useEffect)(() => {
|
|
272
|
+
progressRef.current = progress;
|
|
273
|
+
}, [progress]);
|
|
274
|
+
(0, import_react2.useEffect)(() => {
|
|
275
|
+
targetPositionRef.current = targetPosition;
|
|
276
|
+
}, [targetPosition]);
|
|
277
|
+
(0, import_react2.useEffect)(() => {
|
|
278
|
+
distanceFromStartRef.current = distanceFromStart;
|
|
279
|
+
}, [distanceFromStart]);
|
|
255
280
|
const openAddNote = (0, import_react2.useCallback)((tagId) => {
|
|
256
281
|
setAddNoteTagId(tagId ?? closestTagIdRef.current ?? null);
|
|
282
|
+
noteSnapshotRef.current = {
|
|
283
|
+
progress: progressRef.current,
|
|
284
|
+
targetPosition: targetPositionRef.current,
|
|
285
|
+
distanceFromStart: distanceFromStartRef.current
|
|
286
|
+
};
|
|
257
287
|
setIsAddNoteOpen(true);
|
|
258
288
|
}, []);
|
|
259
289
|
const closeAddNote = (0, import_react2.useCallback)(() => {
|
|
@@ -262,13 +292,15 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
262
292
|
const submitNote = (0, import_react2.useCallback)((text, isPrivate = true) => {
|
|
263
293
|
const tagId = addNoteTagIdRef.current;
|
|
264
294
|
const lineId = activeLineIdRef.current;
|
|
265
|
-
const
|
|
295
|
+
const snap = noteSnapshotRef.current;
|
|
266
296
|
const payload = {
|
|
267
297
|
text,
|
|
268
298
|
isPrivate,
|
|
269
299
|
tagId,
|
|
270
300
|
lineId,
|
|
271
|
-
position:
|
|
301
|
+
position: snap.targetPosition,
|
|
302
|
+
progress: snap.progress,
|
|
303
|
+
distanceFromStart: snap.distanceFromStart
|
|
272
304
|
};
|
|
273
305
|
onNoteSubmitRef.current?.(payload);
|
|
274
306
|
setIsAddNoteOpen(false);
|
|
@@ -309,6 +341,8 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
309
341
|
const payload = data.payload;
|
|
310
342
|
setProgress(payload.progress);
|
|
311
343
|
setClosestTagId(payload.closestTagId);
|
|
344
|
+
setTargetPosition(payload.targetPosition ?? null);
|
|
345
|
+
setDistanceFromStart(payload.distanceFromStart ?? null);
|
|
312
346
|
onLineProgress?.(payload);
|
|
313
347
|
break;
|
|
314
348
|
}
|
|
@@ -333,20 +367,20 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
333
367
|
lines,
|
|
334
368
|
activeLineId,
|
|
335
369
|
activeLine,
|
|
336
|
-
closestTagId,
|
|
337
|
-
progress,
|
|
338
|
-
isPlaying,
|
|
339
370
|
isAddNoteOpen,
|
|
340
371
|
addNoteTagId,
|
|
341
372
|
controls,
|
|
342
373
|
openAddNote,
|
|
343
374
|
closeAddNote,
|
|
344
|
-
submitNote
|
|
345
|
-
onNoteSubmit
|
|
375
|
+
submitNote
|
|
346
376
|
}),
|
|
347
|
-
[project, lines, activeLineId, activeLine,
|
|
377
|
+
[project, lines, activeLineId, activeLine, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote]
|
|
378
|
+
);
|
|
379
|
+
const progressCtx = (0, import_react2.useMemo)(
|
|
380
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart, isPlaying }),
|
|
381
|
+
[progress, closestTagId, targetPosition, distanceFromStart, isPlaying]
|
|
348
382
|
);
|
|
349
|
-
return /* @__PURE__ */ import_react2.default.createElement(RetorBridgeProvider, { value: ctxValue }, /* @__PURE__ */ import_react2.default.createElement(import_react_native.View, { style: [styles.root, style] }, /* @__PURE__ */ import_react2.default.createElement(
|
|
383
|
+
return /* @__PURE__ */ import_react2.default.createElement(RetorBridgeProvider, { value: ctxValue }, /* @__PURE__ */ import_react2.default.createElement(RetorProgressProvider, { value: progressCtx }, /* @__PURE__ */ import_react2.default.createElement(import_react_native.View, { style: [styles.root, style] }, /* @__PURE__ */ import_react2.default.createElement(
|
|
350
384
|
import_react_native_webview.WebView,
|
|
351
385
|
{
|
|
352
386
|
ref: webviewRef,
|
|
@@ -359,7 +393,7 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
359
393
|
allowsInlineMediaPlayback: true,
|
|
360
394
|
mediaPlaybackRequiresUserAction: false
|
|
361
395
|
}
|
|
362
|
-
), children));
|
|
396
|
+
), children)));
|
|
363
397
|
});
|
|
364
398
|
var styles = import_react_native.StyleSheet.create({
|
|
365
399
|
root: { flex: 1, position: "relative" },
|
|
@@ -593,7 +627,8 @@ function DefaultHeader({
|
|
|
593
627
|
return /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.header }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.headerActions }, /* @__PURE__ */ import_react6.default.createElement(AutoplayButton, null), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))));
|
|
594
628
|
}
|
|
595
629
|
function AutoplayButton() {
|
|
596
|
-
const {
|
|
630
|
+
const { controls } = useRetorBridge();
|
|
631
|
+
const { isPlaying, progress } = useRetorProgress();
|
|
597
632
|
const r = 12.5;
|
|
598
633
|
const c = 2 * Math.PI * r;
|
|
599
634
|
return /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ import_react6.default.createElement(import_react_native_svg.default, { width: 28, height: 28, style: import_react_native5.StyleSheet.absoluteFill }, /* @__PURE__ */ import_react6.default.createElement(
|
|
@@ -613,7 +648,8 @@ function AutoplayButton() {
|
|
|
613
648
|
)), isPlaying ? /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
|
|
614
649
|
}
|
|
615
650
|
function LineTagList({ children, listHeader }) {
|
|
616
|
-
const { activeLine
|
|
651
|
+
const { activeLine } = useRetorBridge();
|
|
652
|
+
const { closestTagId } = useRetorProgress();
|
|
617
653
|
const scrollRef = (0, import_react6.useRef)(null);
|
|
618
654
|
const offsetsRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
|
|
619
655
|
const tags = (0, import_react6.useMemo)(
|
|
@@ -623,20 +659,17 @@ function LineTagList({ children, listHeader }) {
|
|
|
623
659
|
(0, import_react6.useEffect)(() => {
|
|
624
660
|
offsetsRef.current = /* @__PURE__ */ new Map();
|
|
625
661
|
}, [activeLine?._id]);
|
|
662
|
+
const lastScrolledIdRef = (0, import_react6.useRef)(null);
|
|
626
663
|
(0, import_react6.useEffect)(() => {
|
|
627
|
-
if (!closestTagId) return;
|
|
628
|
-
|
|
629
|
-
const tick = () => {
|
|
664
|
+
if (!closestTagId || closestTagId === lastScrolledIdRef.current) return;
|
|
665
|
+
const t = setTimeout(() => {
|
|
630
666
|
const y = offsetsRef.current.get(closestTagId);
|
|
631
667
|
if (y != null) {
|
|
632
668
|
scrollRef.current?.scrollTo?.({ y, animated: true });
|
|
633
|
-
|
|
669
|
+
lastScrolledIdRef.current = closestTagId;
|
|
634
670
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
};
|
|
638
|
-
let raf = requestAnimationFrame(tick);
|
|
639
|
-
return () => cancelAnimationFrame(raf);
|
|
671
|
+
}, 300);
|
|
672
|
+
return () => clearTimeout(t);
|
|
640
673
|
}, [closestTagId]);
|
|
641
674
|
if (!activeLine) return null;
|
|
642
675
|
const handleItemLayout = (id, e) => {
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// src/context.tsx
|
|
2
2
|
import React, { createContext, useContext, useMemo } from "react";
|
|
3
3
|
var RetorBridgeContext = createContext(null);
|
|
4
|
+
var RetorProgressContext = createContext({
|
|
5
|
+
progress: 0,
|
|
6
|
+
closestTagId: null,
|
|
7
|
+
targetPosition: null,
|
|
8
|
+
distanceFromStart: null,
|
|
9
|
+
isPlaying: false
|
|
10
|
+
});
|
|
4
11
|
function noop() {
|
|
5
12
|
}
|
|
6
13
|
var noopHandle = {
|
|
@@ -15,9 +22,6 @@ var fallback = {
|
|
|
15
22
|
lines: [],
|
|
16
23
|
activeLineId: null,
|
|
17
24
|
activeLine: null,
|
|
18
|
-
closestTagId: null,
|
|
19
|
-
progress: 0,
|
|
20
|
-
isPlaying: false,
|
|
21
25
|
isAddNoteOpen: false,
|
|
22
26
|
addNoteTagId: null,
|
|
23
27
|
controls: noopHandle,
|
|
@@ -28,6 +32,9 @@ var fallback = {
|
|
|
28
32
|
function useRetorBridge() {
|
|
29
33
|
return useContext(RetorBridgeContext) ?? fallback;
|
|
30
34
|
}
|
|
35
|
+
function useRetorProgress() {
|
|
36
|
+
return useContext(RetorProgressContext);
|
|
37
|
+
}
|
|
31
38
|
function useLines() {
|
|
32
39
|
return useRetorBridge().lines;
|
|
33
40
|
}
|
|
@@ -38,11 +45,15 @@ function useActiveLine() {
|
|
|
38
45
|
return useRetorBridge().activeLine;
|
|
39
46
|
}
|
|
40
47
|
function useLineProgress() {
|
|
41
|
-
const { progress, closestTagId } =
|
|
42
|
-
return useMemo(
|
|
48
|
+
const { progress, closestTagId, targetPosition, distanceFromStart } = useRetorProgress();
|
|
49
|
+
return useMemo(
|
|
50
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart }),
|
|
51
|
+
[progress, closestTagId, targetPosition, distanceFromStart]
|
|
52
|
+
);
|
|
43
53
|
}
|
|
44
54
|
function useAutoplay() {
|
|
45
|
-
const { isPlaying
|
|
55
|
+
const { isPlaying } = useRetorProgress();
|
|
56
|
+
const { controls } = useRetorBridge();
|
|
46
57
|
return useMemo(() => ({
|
|
47
58
|
isPlaying,
|
|
48
59
|
toggle: () => controls.toggleAutoplay(),
|
|
@@ -63,6 +74,9 @@ function useAddNote() {
|
|
|
63
74
|
function RetorBridgeProvider({ value, children }) {
|
|
64
75
|
return /* @__PURE__ */ React.createElement(RetorBridgeContext.Provider, { value }, children);
|
|
65
76
|
}
|
|
77
|
+
function RetorProgressProvider({ value, children }) {
|
|
78
|
+
return /* @__PURE__ */ React.createElement(RetorProgressContext.Provider, { value }, children);
|
|
79
|
+
}
|
|
66
80
|
|
|
67
81
|
// src/Viewer.tsx
|
|
68
82
|
import React2, {
|
|
@@ -150,6 +164,8 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
150
164
|
const [activeLineId, setActiveLineId] = useState(null);
|
|
151
165
|
const [closestTagId, setClosestTagId] = useState(null);
|
|
152
166
|
const [progress, setProgress] = useState(0);
|
|
167
|
+
const [targetPosition, setTargetPosition] = useState(null);
|
|
168
|
+
const [distanceFromStart, setDistanceFromStart] = useState(null);
|
|
153
169
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
154
170
|
const [isAddNoteOpen, setIsAddNoteOpen] = useState(false);
|
|
155
171
|
const [addNoteTagId, setAddNoteTagId] = useState(null);
|
|
@@ -191,8 +207,11 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
191
207
|
const closestTagIdRef = useRef(closestTagId);
|
|
192
208
|
const addNoteTagIdRef = useRef(addNoteTagId);
|
|
193
209
|
const activeLineIdRef = useRef(activeLineId);
|
|
194
|
-
const linesRef = useRef(lines);
|
|
195
210
|
const onNoteSubmitRef = useRef(onNoteSubmit);
|
|
211
|
+
const noteSnapshotRef = useRef({ progress: 0, targetPosition: null, distanceFromStart: null });
|
|
212
|
+
const progressRef = useRef(progress);
|
|
213
|
+
const targetPositionRef = useRef(targetPosition);
|
|
214
|
+
const distanceFromStartRef = useRef(distanceFromStart);
|
|
196
215
|
useEffect(() => {
|
|
197
216
|
closestTagIdRef.current = closestTagId;
|
|
198
217
|
}, [closestTagId]);
|
|
@@ -202,14 +221,25 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
202
221
|
useEffect(() => {
|
|
203
222
|
activeLineIdRef.current = activeLineId;
|
|
204
223
|
}, [activeLineId]);
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
linesRef.current = lines;
|
|
207
|
-
}, [lines]);
|
|
208
224
|
useEffect(() => {
|
|
209
225
|
onNoteSubmitRef.current = onNoteSubmit;
|
|
210
226
|
}, [onNoteSubmit]);
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
progressRef.current = progress;
|
|
229
|
+
}, [progress]);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
targetPositionRef.current = targetPosition;
|
|
232
|
+
}, [targetPosition]);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
distanceFromStartRef.current = distanceFromStart;
|
|
235
|
+
}, [distanceFromStart]);
|
|
211
236
|
const openAddNote = useCallback((tagId) => {
|
|
212
237
|
setAddNoteTagId(tagId ?? closestTagIdRef.current ?? null);
|
|
238
|
+
noteSnapshotRef.current = {
|
|
239
|
+
progress: progressRef.current,
|
|
240
|
+
targetPosition: targetPositionRef.current,
|
|
241
|
+
distanceFromStart: distanceFromStartRef.current
|
|
242
|
+
};
|
|
213
243
|
setIsAddNoteOpen(true);
|
|
214
244
|
}, []);
|
|
215
245
|
const closeAddNote = useCallback(() => {
|
|
@@ -218,13 +248,15 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
218
248
|
const submitNote = useCallback((text, isPrivate = true) => {
|
|
219
249
|
const tagId = addNoteTagIdRef.current;
|
|
220
250
|
const lineId = activeLineIdRef.current;
|
|
221
|
-
const
|
|
251
|
+
const snap = noteSnapshotRef.current;
|
|
222
252
|
const payload = {
|
|
223
253
|
text,
|
|
224
254
|
isPrivate,
|
|
225
255
|
tagId,
|
|
226
256
|
lineId,
|
|
227
|
-
position:
|
|
257
|
+
position: snap.targetPosition,
|
|
258
|
+
progress: snap.progress,
|
|
259
|
+
distanceFromStart: snap.distanceFromStart
|
|
228
260
|
};
|
|
229
261
|
onNoteSubmitRef.current?.(payload);
|
|
230
262
|
setIsAddNoteOpen(false);
|
|
@@ -265,6 +297,8 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
265
297
|
const payload = data.payload;
|
|
266
298
|
setProgress(payload.progress);
|
|
267
299
|
setClosestTagId(payload.closestTagId);
|
|
300
|
+
setTargetPosition(payload.targetPosition ?? null);
|
|
301
|
+
setDistanceFromStart(payload.distanceFromStart ?? null);
|
|
268
302
|
onLineProgress?.(payload);
|
|
269
303
|
break;
|
|
270
304
|
}
|
|
@@ -289,20 +323,20 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
289
323
|
lines,
|
|
290
324
|
activeLineId,
|
|
291
325
|
activeLine,
|
|
292
|
-
closestTagId,
|
|
293
|
-
progress,
|
|
294
|
-
isPlaying,
|
|
295
326
|
isAddNoteOpen,
|
|
296
327
|
addNoteTagId,
|
|
297
328
|
controls,
|
|
298
329
|
openAddNote,
|
|
299
330
|
closeAddNote,
|
|
300
|
-
submitNote
|
|
301
|
-
onNoteSubmit
|
|
331
|
+
submitNote
|
|
302
332
|
}),
|
|
303
|
-
[project, lines, activeLineId, activeLine,
|
|
333
|
+
[project, lines, activeLineId, activeLine, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote]
|
|
334
|
+
);
|
|
335
|
+
const progressCtx = useMemo2(
|
|
336
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart, isPlaying }),
|
|
337
|
+
[progress, closestTagId, targetPosition, distanceFromStart, isPlaying]
|
|
304
338
|
);
|
|
305
|
-
return /* @__PURE__ */ React2.createElement(RetorBridgeProvider, { value: ctxValue }, /* @__PURE__ */ React2.createElement(View, { style: [styles.root, style] }, /* @__PURE__ */ React2.createElement(
|
|
339
|
+
return /* @__PURE__ */ React2.createElement(RetorBridgeProvider, { value: ctxValue }, /* @__PURE__ */ React2.createElement(RetorProgressProvider, { value: progressCtx }, /* @__PURE__ */ React2.createElement(View, { style: [styles.root, style] }, /* @__PURE__ */ React2.createElement(
|
|
306
340
|
WebView,
|
|
307
341
|
{
|
|
308
342
|
ref: webviewRef,
|
|
@@ -315,7 +349,7 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
315
349
|
allowsInlineMediaPlayback: true,
|
|
316
350
|
mediaPlaybackRequiresUserAction: false
|
|
317
351
|
}
|
|
318
|
-
), children));
|
|
352
|
+
), children)));
|
|
319
353
|
});
|
|
320
354
|
var styles = StyleSheet.create({
|
|
321
355
|
root: { flex: 1, position: "relative" },
|
|
@@ -554,7 +588,8 @@ function DefaultHeader({
|
|
|
554
588
|
return /* @__PURE__ */ React6.createElement(View5, { style: styles3.header }, /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React6.createElement(Text2, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ React6.createElement(View5, { style: styles3.headerActions }, /* @__PURE__ */ React6.createElement(AutoplayButton, null), /* @__PURE__ */ React6.createElement(Pressable2, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ React6.createElement(ArrowUp2, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React6.createElement(ArrowDown2, { size: 14, color: "rgba(255,255,255,0.6)" }))));
|
|
555
589
|
}
|
|
556
590
|
function AutoplayButton() {
|
|
557
|
-
const {
|
|
591
|
+
const { controls } = useRetorBridge();
|
|
592
|
+
const { isPlaying, progress } = useRetorProgress();
|
|
558
593
|
const r = 12.5;
|
|
559
594
|
const c = 2 * Math.PI * r;
|
|
560
595
|
return /* @__PURE__ */ React6.createElement(Pressable2, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ React6.createElement(Svg, { width: 28, height: 28, style: StyleSheet5.absoluteFill }, /* @__PURE__ */ React6.createElement(
|
|
@@ -574,7 +609,8 @@ function AutoplayButton() {
|
|
|
574
609
|
)), isPlaying ? /* @__PURE__ */ React6.createElement(Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ React6.createElement(Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
|
|
575
610
|
}
|
|
576
611
|
function LineTagList({ children, listHeader }) {
|
|
577
|
-
const { activeLine
|
|
612
|
+
const { activeLine } = useRetorBridge();
|
|
613
|
+
const { closestTagId } = useRetorProgress();
|
|
578
614
|
const scrollRef = useRef3(null);
|
|
579
615
|
const offsetsRef = useRef3(/* @__PURE__ */ new Map());
|
|
580
616
|
const tags = useMemo4(
|
|
@@ -584,20 +620,17 @@ function LineTagList({ children, listHeader }) {
|
|
|
584
620
|
useEffect3(() => {
|
|
585
621
|
offsetsRef.current = /* @__PURE__ */ new Map();
|
|
586
622
|
}, [activeLine?._id]);
|
|
623
|
+
const lastScrolledIdRef = useRef3(null);
|
|
587
624
|
useEffect3(() => {
|
|
588
|
-
if (!closestTagId) return;
|
|
589
|
-
|
|
590
|
-
const tick = () => {
|
|
625
|
+
if (!closestTagId || closestTagId === lastScrolledIdRef.current) return;
|
|
626
|
+
const t = setTimeout(() => {
|
|
591
627
|
const y = offsetsRef.current.get(closestTagId);
|
|
592
628
|
if (y != null) {
|
|
593
629
|
scrollRef.current?.scrollTo?.({ y, animated: true });
|
|
594
|
-
|
|
630
|
+
lastScrolledIdRef.current = closestTagId;
|
|
595
631
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
};
|
|
599
|
-
let raf = requestAnimationFrame(tick);
|
|
600
|
-
return () => cancelAnimationFrame(raf);
|
|
632
|
+
}, 300);
|
|
633
|
+
return () => clearTimeout(t);
|
|
601
634
|
}, [closestTagId]);
|
|
602
635
|
if (!activeLine) return null;
|
|
603
636
|
const handleItemLayout = (id, e) => {
|