@retor/react-native 0.3.6 → 0.4.1
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 +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +114 -38
- package/dist/index.mjs +114 -38
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -17,6 +17,8 @@ interface RetorTag {
|
|
|
17
17
|
tagType?: string;
|
|
18
18
|
iconName?: string;
|
|
19
19
|
objectId?: string;
|
|
20
|
+
/** 0..1 position along the parent line. Set on notes; computed from index for control tags. */
|
|
21
|
+
progress?: number;
|
|
20
22
|
}
|
|
21
23
|
interface RetorLine {
|
|
22
24
|
_id: string;
|
|
@@ -43,6 +45,15 @@ interface InitPayload {
|
|
|
43
45
|
interface LineProgressPayload {
|
|
44
46
|
progress: number;
|
|
45
47
|
closestTagId: string | null;
|
|
48
|
+
/** Point on the line at current progress (project-local space). */
|
|
49
|
+
targetPosition?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
z: number;
|
|
53
|
+
} | null;
|
|
54
|
+
/** Distance along the line from the start.
|
|
55
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
56
|
+
distanceFromStart?: number | null;
|
|
46
57
|
}
|
|
47
58
|
interface ViewerHandle {
|
|
48
59
|
openLine: (lineId: string) => void;
|
|
@@ -55,29 +66,43 @@ interface ViewerHandle {
|
|
|
55
66
|
interface NoteSubmitPayload {
|
|
56
67
|
text: string;
|
|
57
68
|
isPrivate: boolean;
|
|
69
|
+
/** The tag that was active when the + button was pressed. */
|
|
58
70
|
tagId: string | null;
|
|
59
71
|
lineId: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Point on the line at the scroll position when the + button was pressed,
|
|
74
|
+
* in project-local space. This is where the note visually anchors.
|
|
75
|
+
*/
|
|
60
76
|
position: {
|
|
61
77
|
x: number;
|
|
62
78
|
y: number;
|
|
63
79
|
z: number;
|
|
64
80
|
} | null;
|
|
81
|
+
/** Scroll progress along the line (0..1) when the + was pressed. */
|
|
82
|
+
progress: number;
|
|
83
|
+
/** Distance from the start of the line when the + was pressed.
|
|
84
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
85
|
+
distanceFromStart: number | null;
|
|
65
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Stable data + commands. Updates only when meaningful things change
|
|
89
|
+
* (line opens/closes, notes loaded, project changes). Does NOT update per frame.
|
|
90
|
+
*/
|
|
66
91
|
interface RetorBridgeContextValue {
|
|
67
92
|
project: RetorProject | null;
|
|
68
93
|
lines: RetorLine[];
|
|
69
94
|
activeLineId: string | null;
|
|
70
95
|
activeLine: RetorLine | null;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
/** Notes for the active line, pushed by the bridge from Convex. */
|
|
97
|
+
lineNotes: RetorTag[];
|
|
98
|
+
/** Notes injected by the consumer via the <Notes> sentinel. */
|
|
99
|
+
externalNotes: RetorTag[];
|
|
74
100
|
isAddNoteOpen: boolean;
|
|
75
101
|
addNoteTagId: string | null;
|
|
76
102
|
controls: ViewerHandle;
|
|
77
103
|
openAddNote: (tagId?: string) => void;
|
|
78
104
|
closeAddNote: () => void;
|
|
79
105
|
submitNote: (text: string, isPrivate?: boolean) => void;
|
|
80
|
-
onNoteSubmit?: (payload: NoteSubmitPayload) => void;
|
|
81
106
|
}
|
|
82
107
|
declare function useRetorBridge(): RetorBridgeContextValue;
|
|
83
108
|
/** Returns the array of all lines in the current project. */
|
|
@@ -91,11 +116,18 @@ declare function useProject(): RetorProject | null;
|
|
|
91
116
|
declare function useActiveLine(): RetorLine | null;
|
|
92
117
|
/**
|
|
93
118
|
* Returns scroll progress (0..1) and the id of the closest tag along the
|
|
94
|
-
* currently active line.
|
|
119
|
+
* currently active line. Components using this re-render on every frame
|
|
120
|
+
* during camera scroll — only call from leaves (tag items, progress rings).
|
|
95
121
|
*/
|
|
96
122
|
declare function useLineProgress(): {
|
|
97
123
|
progress: number;
|
|
98
124
|
closestTagId: string | null;
|
|
125
|
+
targetPosition: {
|
|
126
|
+
x: number;
|
|
127
|
+
y: number;
|
|
128
|
+
z: number;
|
|
129
|
+
} | null;
|
|
130
|
+
distanceFromStart: number | null;
|
|
99
131
|
};
|
|
100
132
|
/**
|
|
101
133
|
* Returns the autoplay state and controls.
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ interface RetorTag {
|
|
|
17
17
|
tagType?: string;
|
|
18
18
|
iconName?: string;
|
|
19
19
|
objectId?: string;
|
|
20
|
+
/** 0..1 position along the parent line. Set on notes; computed from index for control tags. */
|
|
21
|
+
progress?: number;
|
|
20
22
|
}
|
|
21
23
|
interface RetorLine {
|
|
22
24
|
_id: string;
|
|
@@ -43,6 +45,15 @@ interface InitPayload {
|
|
|
43
45
|
interface LineProgressPayload {
|
|
44
46
|
progress: number;
|
|
45
47
|
closestTagId: string | null;
|
|
48
|
+
/** Point on the line at current progress (project-local space). */
|
|
49
|
+
targetPosition?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
z: number;
|
|
53
|
+
} | null;
|
|
54
|
+
/** Distance along the line from the start.
|
|
55
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
56
|
+
distanceFromStart?: number | null;
|
|
46
57
|
}
|
|
47
58
|
interface ViewerHandle {
|
|
48
59
|
openLine: (lineId: string) => void;
|
|
@@ -55,29 +66,43 @@ interface ViewerHandle {
|
|
|
55
66
|
interface NoteSubmitPayload {
|
|
56
67
|
text: string;
|
|
57
68
|
isPrivate: boolean;
|
|
69
|
+
/** The tag that was active when the + button was pressed. */
|
|
58
70
|
tagId: string | null;
|
|
59
71
|
lineId: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Point on the line at the scroll position when the + button was pressed,
|
|
74
|
+
* in project-local space. This is where the note visually anchors.
|
|
75
|
+
*/
|
|
60
76
|
position: {
|
|
61
77
|
x: number;
|
|
62
78
|
y: number;
|
|
63
79
|
z: number;
|
|
64
80
|
} | null;
|
|
81
|
+
/** Scroll progress along the line (0..1) when the + was pressed. */
|
|
82
|
+
progress: number;
|
|
83
|
+
/** Distance from the start of the line when the + was pressed.
|
|
84
|
+
* In meters if GPS is configured on the project, else project-local units. */
|
|
85
|
+
distanceFromStart: number | null;
|
|
65
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Stable data + commands. Updates only when meaningful things change
|
|
89
|
+
* (line opens/closes, notes loaded, project changes). Does NOT update per frame.
|
|
90
|
+
*/
|
|
66
91
|
interface RetorBridgeContextValue {
|
|
67
92
|
project: RetorProject | null;
|
|
68
93
|
lines: RetorLine[];
|
|
69
94
|
activeLineId: string | null;
|
|
70
95
|
activeLine: RetorLine | null;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
/** Notes for the active line, pushed by the bridge from Convex. */
|
|
97
|
+
lineNotes: RetorTag[];
|
|
98
|
+
/** Notes injected by the consumer via the <Notes> sentinel. */
|
|
99
|
+
externalNotes: RetorTag[];
|
|
74
100
|
isAddNoteOpen: boolean;
|
|
75
101
|
addNoteTagId: string | null;
|
|
76
102
|
controls: ViewerHandle;
|
|
77
103
|
openAddNote: (tagId?: string) => void;
|
|
78
104
|
closeAddNote: () => void;
|
|
79
105
|
submitNote: (text: string, isPrivate?: boolean) => void;
|
|
80
|
-
onNoteSubmit?: (payload: NoteSubmitPayload) => void;
|
|
81
106
|
}
|
|
82
107
|
declare function useRetorBridge(): RetorBridgeContextValue;
|
|
83
108
|
/** Returns the array of all lines in the current project. */
|
|
@@ -91,11 +116,18 @@ declare function useProject(): RetorProject | null;
|
|
|
91
116
|
declare function useActiveLine(): RetorLine | null;
|
|
92
117
|
/**
|
|
93
118
|
* Returns scroll progress (0..1) and the id of the closest tag along the
|
|
94
|
-
* currently active line.
|
|
119
|
+
* currently active line. Components using this re-render on every frame
|
|
120
|
+
* during camera scroll — only call from leaves (tag items, progress rings).
|
|
95
121
|
*/
|
|
96
122
|
declare function useLineProgress(): {
|
|
97
123
|
progress: number;
|
|
98
124
|
closestTagId: string | null;
|
|
125
|
+
targetPosition: {
|
|
126
|
+
x: number;
|
|
127
|
+
y: number;
|
|
128
|
+
z: number;
|
|
129
|
+
} | null;
|
|
130
|
+
distanceFromStart: number | null;
|
|
99
131
|
};
|
|
100
132
|
/**
|
|
101
133
|
* 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,8 @@ var fallback = {
|
|
|
68
75
|
lines: [],
|
|
69
76
|
activeLineId: null,
|
|
70
77
|
activeLine: null,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
isPlaying: false,
|
|
78
|
+
lineNotes: [],
|
|
79
|
+
externalNotes: [],
|
|
74
80
|
isAddNoteOpen: false,
|
|
75
81
|
addNoteTagId: null,
|
|
76
82
|
controls: noopHandle,
|
|
@@ -81,6 +87,9 @@ var fallback = {
|
|
|
81
87
|
function useRetorBridge() {
|
|
82
88
|
return (0, import_react.useContext)(RetorBridgeContext) ?? fallback;
|
|
83
89
|
}
|
|
90
|
+
function useRetorProgress() {
|
|
91
|
+
return (0, import_react.useContext)(RetorProgressContext);
|
|
92
|
+
}
|
|
84
93
|
function useLines() {
|
|
85
94
|
return useRetorBridge().lines;
|
|
86
95
|
}
|
|
@@ -91,11 +100,15 @@ function useActiveLine() {
|
|
|
91
100
|
return useRetorBridge().activeLine;
|
|
92
101
|
}
|
|
93
102
|
function useLineProgress() {
|
|
94
|
-
const { progress, closestTagId } =
|
|
95
|
-
return (0, import_react.useMemo)(
|
|
103
|
+
const { progress, closestTagId, targetPosition, distanceFromStart } = useRetorProgress();
|
|
104
|
+
return (0, import_react.useMemo)(
|
|
105
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart }),
|
|
106
|
+
[progress, closestTagId, targetPosition, distanceFromStart]
|
|
107
|
+
);
|
|
96
108
|
}
|
|
97
109
|
function useAutoplay() {
|
|
98
|
-
const { isPlaying
|
|
110
|
+
const { isPlaying } = useRetorProgress();
|
|
111
|
+
const { controls } = useRetorBridge();
|
|
99
112
|
return (0, import_react.useMemo)(() => ({
|
|
100
113
|
isPlaying,
|
|
101
114
|
toggle: () => controls.toggleAutoplay(),
|
|
@@ -116,6 +129,9 @@ function useAddNote() {
|
|
|
116
129
|
function RetorBridgeProvider({ value, children }) {
|
|
117
130
|
return /* @__PURE__ */ import_react.default.createElement(RetorBridgeContext.Provider, { value }, children);
|
|
118
131
|
}
|
|
132
|
+
function RetorProgressProvider({ value, children }) {
|
|
133
|
+
return /* @__PURE__ */ import_react.default.createElement(RetorProgressContext.Provider, { value }, children);
|
|
134
|
+
}
|
|
119
135
|
|
|
120
136
|
// src/Viewer.tsx
|
|
121
137
|
var import_react2 = __toESM(require("react"));
|
|
@@ -194,9 +210,12 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
194
210
|
const [activeLineId, setActiveLineId] = (0, import_react2.useState)(null);
|
|
195
211
|
const [closestTagId, setClosestTagId] = (0, import_react2.useState)(null);
|
|
196
212
|
const [progress, setProgress] = (0, import_react2.useState)(0);
|
|
213
|
+
const [targetPosition, setTargetPosition] = (0, import_react2.useState)(null);
|
|
214
|
+
const [distanceFromStart, setDistanceFromStart] = (0, import_react2.useState)(null);
|
|
197
215
|
const [isPlaying, setIsPlaying] = (0, import_react2.useState)(false);
|
|
198
216
|
const [isAddNoteOpen, setIsAddNoteOpen] = (0, import_react2.useState)(false);
|
|
199
217
|
const [addNoteTagId, setAddNoteTagId] = (0, import_react2.useState)(null);
|
|
218
|
+
const [lineNotes, setLineNotes] = (0, import_react2.useState)([]);
|
|
200
219
|
const uri = (0, import_react2.useMemo)(() => `${baseUrl}/p/${projectId}?vanilla=true`, [baseUrl, projectId]);
|
|
201
220
|
const send = (0, import_react2.useCallback)((type, payload) => {
|
|
202
221
|
const message = JSON.stringify({ source: "retor-host", type, payload });
|
|
@@ -235,8 +254,11 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
235
254
|
const closestTagIdRef = (0, import_react2.useRef)(closestTagId);
|
|
236
255
|
const addNoteTagIdRef = (0, import_react2.useRef)(addNoteTagId);
|
|
237
256
|
const activeLineIdRef = (0, import_react2.useRef)(activeLineId);
|
|
238
|
-
const linesRef = (0, import_react2.useRef)(lines);
|
|
239
257
|
const onNoteSubmitRef = (0, import_react2.useRef)(onNoteSubmit);
|
|
258
|
+
const noteSnapshotRef = (0, import_react2.useRef)({ progress: 0, targetPosition: null, distanceFromStart: null });
|
|
259
|
+
const progressRef = (0, import_react2.useRef)(progress);
|
|
260
|
+
const targetPositionRef = (0, import_react2.useRef)(targetPosition);
|
|
261
|
+
const distanceFromStartRef = (0, import_react2.useRef)(distanceFromStart);
|
|
240
262
|
(0, import_react2.useEffect)(() => {
|
|
241
263
|
closestTagIdRef.current = closestTagId;
|
|
242
264
|
}, [closestTagId]);
|
|
@@ -246,14 +268,25 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
246
268
|
(0, import_react2.useEffect)(() => {
|
|
247
269
|
activeLineIdRef.current = activeLineId;
|
|
248
270
|
}, [activeLineId]);
|
|
249
|
-
(0, import_react2.useEffect)(() => {
|
|
250
|
-
linesRef.current = lines;
|
|
251
|
-
}, [lines]);
|
|
252
271
|
(0, import_react2.useEffect)(() => {
|
|
253
272
|
onNoteSubmitRef.current = onNoteSubmit;
|
|
254
273
|
}, [onNoteSubmit]);
|
|
274
|
+
(0, import_react2.useEffect)(() => {
|
|
275
|
+
progressRef.current = progress;
|
|
276
|
+
}, [progress]);
|
|
277
|
+
(0, import_react2.useEffect)(() => {
|
|
278
|
+
targetPositionRef.current = targetPosition;
|
|
279
|
+
}, [targetPosition]);
|
|
280
|
+
(0, import_react2.useEffect)(() => {
|
|
281
|
+
distanceFromStartRef.current = distanceFromStart;
|
|
282
|
+
}, [distanceFromStart]);
|
|
255
283
|
const openAddNote = (0, import_react2.useCallback)((tagId) => {
|
|
256
284
|
setAddNoteTagId(tagId ?? closestTagIdRef.current ?? null);
|
|
285
|
+
noteSnapshotRef.current = {
|
|
286
|
+
progress: progressRef.current,
|
|
287
|
+
targetPosition: targetPositionRef.current,
|
|
288
|
+
distanceFromStart: distanceFromStartRef.current
|
|
289
|
+
};
|
|
257
290
|
setIsAddNoteOpen(true);
|
|
258
291
|
}, []);
|
|
259
292
|
const closeAddNote = (0, import_react2.useCallback)(() => {
|
|
@@ -262,17 +295,23 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
262
295
|
const submitNote = (0, import_react2.useCallback)((text, isPrivate = true) => {
|
|
263
296
|
const tagId = addNoteTagIdRef.current;
|
|
264
297
|
const lineId = activeLineIdRef.current;
|
|
265
|
-
const
|
|
298
|
+
const snap = noteSnapshotRef.current;
|
|
266
299
|
const payload = {
|
|
267
300
|
text,
|
|
268
301
|
isPrivate,
|
|
269
302
|
tagId,
|
|
270
303
|
lineId,
|
|
271
|
-
position:
|
|
304
|
+
position: snap.targetPosition,
|
|
305
|
+
progress: snap.progress,
|
|
306
|
+
distanceFromStart: snap.distanceFromStart
|
|
272
307
|
};
|
|
273
|
-
onNoteSubmitRef.current
|
|
308
|
+
if (onNoteSubmitRef.current) {
|
|
309
|
+
onNoteSubmitRef.current(payload);
|
|
310
|
+
} else {
|
|
311
|
+
send("note-submit", payload);
|
|
312
|
+
}
|
|
274
313
|
setIsAddNoteOpen(false);
|
|
275
|
-
}, []);
|
|
314
|
+
}, [send]);
|
|
276
315
|
const handleMessage = (0, import_react2.useCallback)(
|
|
277
316
|
(event) => {
|
|
278
317
|
let data = null;
|
|
@@ -303,15 +342,26 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
303
342
|
case "line-close":
|
|
304
343
|
setActiveLineId(null);
|
|
305
344
|
setIsPlaying(false);
|
|
345
|
+
setLineNotes([]);
|
|
306
346
|
onLineClose?.();
|
|
307
347
|
break;
|
|
308
348
|
case "line-progress": {
|
|
309
349
|
const payload = data.payload;
|
|
310
350
|
setProgress(payload.progress);
|
|
311
351
|
setClosestTagId(payload.closestTagId);
|
|
352
|
+
setTargetPosition(payload.targetPosition ?? null);
|
|
353
|
+
setDistanceFromStart(payload.distanceFromStart ?? null);
|
|
312
354
|
onLineProgress?.(payload);
|
|
313
355
|
break;
|
|
314
356
|
}
|
|
357
|
+
case "line-notes": {
|
|
358
|
+
const payload = data.payload;
|
|
359
|
+
setLineNotes(payload.notes ?? []);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case "request-add-note":
|
|
363
|
+
openAddNote();
|
|
364
|
+
break;
|
|
315
365
|
case "autoplay-state": {
|
|
316
366
|
const payload = data.payload;
|
|
317
367
|
setIsPlaying(!!payload.playing);
|
|
@@ -321,32 +371,35 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
321
371
|
onMessage?.(data.type, data.payload);
|
|
322
372
|
}
|
|
323
373
|
},
|
|
324
|
-
[notes, send, onInit, onLineOpen, onLineClose, onLineProgress, onMessage]
|
|
374
|
+
[notes, send, onInit, onLineOpen, onLineClose, onLineProgress, onMessage, openAddNote]
|
|
325
375
|
);
|
|
326
376
|
const activeLine = (0, import_react2.useMemo)(
|
|
327
377
|
() => lines.find((l) => l._id === activeLineId) ?? null,
|
|
328
378
|
[lines, activeLineId]
|
|
329
379
|
);
|
|
380
|
+
const externalNotes = (0, import_react2.useMemo)(() => notes ?? [], [notes]);
|
|
330
381
|
const ctxValue = (0, import_react2.useMemo)(
|
|
331
382
|
() => ({
|
|
332
383
|
project,
|
|
333
384
|
lines,
|
|
334
385
|
activeLineId,
|
|
335
386
|
activeLine,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
isPlaying,
|
|
387
|
+
lineNotes,
|
|
388
|
+
externalNotes,
|
|
339
389
|
isAddNoteOpen,
|
|
340
390
|
addNoteTagId,
|
|
341
391
|
controls,
|
|
342
392
|
openAddNote,
|
|
343
393
|
closeAddNote,
|
|
344
|
-
submitNote
|
|
345
|
-
onNoteSubmit
|
|
394
|
+
submitNote
|
|
346
395
|
}),
|
|
347
|
-
[project, lines, activeLineId, activeLine,
|
|
396
|
+
[project, lines, activeLineId, activeLine, lineNotes, externalNotes, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote]
|
|
397
|
+
);
|
|
398
|
+
const progressCtx = (0, import_react2.useMemo)(
|
|
399
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart, isPlaying }),
|
|
400
|
+
[progress, closestTagId, targetPosition, distanceFromStart, isPlaying]
|
|
348
401
|
);
|
|
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(
|
|
402
|
+
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
403
|
import_react_native_webview.WebView,
|
|
351
404
|
{
|
|
352
405
|
ref: webviewRef,
|
|
@@ -359,7 +412,7 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
359
412
|
allowsInlineMediaPlayback: true,
|
|
360
413
|
mediaPlaybackRequiresUserAction: false
|
|
361
414
|
}
|
|
362
|
-
), children));
|
|
415
|
+
), children)));
|
|
363
416
|
});
|
|
364
417
|
var styles = import_react_native.StyleSheet.create({
|
|
365
418
|
root: { flex: 1, position: "relative" },
|
|
@@ -523,6 +576,24 @@ var import_react_native5 = require("react-native");
|
|
|
523
576
|
var import_bottom_sheet3 = require("@gorhom/bottom-sheet");
|
|
524
577
|
var import_react_native_svg = __toESM(require("react-native-svg"));
|
|
525
578
|
var import_lucide_react_native2 = require("lucide-react-native");
|
|
579
|
+
|
|
580
|
+
// src/lineProgress.ts
|
|
581
|
+
function mergeLineTagsByProgress(controls, notes) {
|
|
582
|
+
const sortedControls = [...controls].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
583
|
+
const lastIdx = Math.max(1, sortedControls.length - 1);
|
|
584
|
+
const merged = [];
|
|
585
|
+
for (const c of sortedControls) {
|
|
586
|
+
const idx = c.index ?? 0;
|
|
587
|
+
merged.push({ ...c, _isNote: false, progress: idx / lastIdx });
|
|
588
|
+
}
|
|
589
|
+
for (const n of notes) {
|
|
590
|
+
merged.push({ ...n, _isNote: true, progress: n.progress ?? 0 });
|
|
591
|
+
}
|
|
592
|
+
merged.sort((a, b) => a.progress - b.progress);
|
|
593
|
+
return merged;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/LineDetailSheet.tsx
|
|
526
597
|
var renderBackdrop2 = (props) => /* @__PURE__ */ import_react6.default.createElement(
|
|
527
598
|
import_bottom_sheet3.BottomSheetBackdrop,
|
|
528
599
|
{
|
|
@@ -593,7 +664,8 @@ function DefaultHeader({
|
|
|
593
664
|
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
665
|
}
|
|
595
666
|
function AutoplayButton() {
|
|
596
|
-
const {
|
|
667
|
+
const { controls } = useRetorBridge();
|
|
668
|
+
const { isPlaying, progress } = useRetorProgress();
|
|
597
669
|
const r = 12.5;
|
|
598
670
|
const c = 2 * Math.PI * r;
|
|
599
671
|
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,30 +685,34 @@ function AutoplayButton() {
|
|
|
613
685
|
)), 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
686
|
}
|
|
615
687
|
function LineTagList({ children, listHeader }) {
|
|
616
|
-
const { activeLine,
|
|
688
|
+
const { activeLine, lineNotes, externalNotes } = useRetorBridge();
|
|
689
|
+
const { closestTagId } = useRetorProgress();
|
|
617
690
|
const scrollRef = (0, import_react6.useRef)(null);
|
|
618
691
|
const offsetsRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
|
|
619
|
-
const tags = (0, import_react6.useMemo)(
|
|
620
|
-
|
|
621
|
-
[
|
|
622
|
-
|
|
692
|
+
const tags = (0, import_react6.useMemo)(() => {
|
|
693
|
+
if (!activeLine) return [];
|
|
694
|
+
const controls = (activeLine.tags ?? []).filter(
|
|
695
|
+
(t) => t.name && t.name.trim().length > 0
|
|
696
|
+
);
|
|
697
|
+
const allNotes = [...lineNotes, ...externalNotes].filter(
|
|
698
|
+
(n) => n.name && n.name.trim().length > 0
|
|
699
|
+
);
|
|
700
|
+
return mergeLineTagsByProgress(controls, allNotes);
|
|
701
|
+
}, [activeLine, lineNotes, externalNotes]);
|
|
623
702
|
(0, import_react6.useEffect)(() => {
|
|
624
703
|
offsetsRef.current = /* @__PURE__ */ new Map();
|
|
625
704
|
}, [activeLine?._id]);
|
|
705
|
+
const lastScrolledIdRef = (0, import_react6.useRef)(null);
|
|
626
706
|
(0, import_react6.useEffect)(() => {
|
|
627
|
-
if (!closestTagId) return;
|
|
628
|
-
|
|
629
|
-
const tick = () => {
|
|
707
|
+
if (!closestTagId || closestTagId === lastScrolledIdRef.current) return;
|
|
708
|
+
const t = setTimeout(() => {
|
|
630
709
|
const y = offsetsRef.current.get(closestTagId);
|
|
631
710
|
if (y != null) {
|
|
632
711
|
scrollRef.current?.scrollTo?.({ y, animated: true });
|
|
633
|
-
|
|
712
|
+
lastScrolledIdRef.current = closestTagId;
|
|
634
713
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
};
|
|
638
|
-
let raf = requestAnimationFrame(tick);
|
|
639
|
-
return () => cancelAnimationFrame(raf);
|
|
714
|
+
}, 300);
|
|
715
|
+
return () => clearTimeout(t);
|
|
640
716
|
}, [closestTagId]);
|
|
641
717
|
if (!activeLine) return null;
|
|
642
718
|
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,8 @@ var fallback = {
|
|
|
15
22
|
lines: [],
|
|
16
23
|
activeLineId: null,
|
|
17
24
|
activeLine: null,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
isPlaying: false,
|
|
25
|
+
lineNotes: [],
|
|
26
|
+
externalNotes: [],
|
|
21
27
|
isAddNoteOpen: false,
|
|
22
28
|
addNoteTagId: null,
|
|
23
29
|
controls: noopHandle,
|
|
@@ -28,6 +34,9 @@ var fallback = {
|
|
|
28
34
|
function useRetorBridge() {
|
|
29
35
|
return useContext(RetorBridgeContext) ?? fallback;
|
|
30
36
|
}
|
|
37
|
+
function useRetorProgress() {
|
|
38
|
+
return useContext(RetorProgressContext);
|
|
39
|
+
}
|
|
31
40
|
function useLines() {
|
|
32
41
|
return useRetorBridge().lines;
|
|
33
42
|
}
|
|
@@ -38,11 +47,15 @@ function useActiveLine() {
|
|
|
38
47
|
return useRetorBridge().activeLine;
|
|
39
48
|
}
|
|
40
49
|
function useLineProgress() {
|
|
41
|
-
const { progress, closestTagId } =
|
|
42
|
-
return useMemo(
|
|
50
|
+
const { progress, closestTagId, targetPosition, distanceFromStart } = useRetorProgress();
|
|
51
|
+
return useMemo(
|
|
52
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart }),
|
|
53
|
+
[progress, closestTagId, targetPosition, distanceFromStart]
|
|
54
|
+
);
|
|
43
55
|
}
|
|
44
56
|
function useAutoplay() {
|
|
45
|
-
const { isPlaying
|
|
57
|
+
const { isPlaying } = useRetorProgress();
|
|
58
|
+
const { controls } = useRetorBridge();
|
|
46
59
|
return useMemo(() => ({
|
|
47
60
|
isPlaying,
|
|
48
61
|
toggle: () => controls.toggleAutoplay(),
|
|
@@ -63,6 +76,9 @@ function useAddNote() {
|
|
|
63
76
|
function RetorBridgeProvider({ value, children }) {
|
|
64
77
|
return /* @__PURE__ */ React.createElement(RetorBridgeContext.Provider, { value }, children);
|
|
65
78
|
}
|
|
79
|
+
function RetorProgressProvider({ value, children }) {
|
|
80
|
+
return /* @__PURE__ */ React.createElement(RetorProgressContext.Provider, { value }, children);
|
|
81
|
+
}
|
|
66
82
|
|
|
67
83
|
// src/Viewer.tsx
|
|
68
84
|
import React2, {
|
|
@@ -150,9 +166,12 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
150
166
|
const [activeLineId, setActiveLineId] = useState(null);
|
|
151
167
|
const [closestTagId, setClosestTagId] = useState(null);
|
|
152
168
|
const [progress, setProgress] = useState(0);
|
|
169
|
+
const [targetPosition, setTargetPosition] = useState(null);
|
|
170
|
+
const [distanceFromStart, setDistanceFromStart] = useState(null);
|
|
153
171
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
154
172
|
const [isAddNoteOpen, setIsAddNoteOpen] = useState(false);
|
|
155
173
|
const [addNoteTagId, setAddNoteTagId] = useState(null);
|
|
174
|
+
const [lineNotes, setLineNotes] = useState([]);
|
|
156
175
|
const uri = useMemo2(() => `${baseUrl}/p/${projectId}?vanilla=true`, [baseUrl, projectId]);
|
|
157
176
|
const send = useCallback((type, payload) => {
|
|
158
177
|
const message = JSON.stringify({ source: "retor-host", type, payload });
|
|
@@ -191,8 +210,11 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
191
210
|
const closestTagIdRef = useRef(closestTagId);
|
|
192
211
|
const addNoteTagIdRef = useRef(addNoteTagId);
|
|
193
212
|
const activeLineIdRef = useRef(activeLineId);
|
|
194
|
-
const linesRef = useRef(lines);
|
|
195
213
|
const onNoteSubmitRef = useRef(onNoteSubmit);
|
|
214
|
+
const noteSnapshotRef = useRef({ progress: 0, targetPosition: null, distanceFromStart: null });
|
|
215
|
+
const progressRef = useRef(progress);
|
|
216
|
+
const targetPositionRef = useRef(targetPosition);
|
|
217
|
+
const distanceFromStartRef = useRef(distanceFromStart);
|
|
196
218
|
useEffect(() => {
|
|
197
219
|
closestTagIdRef.current = closestTagId;
|
|
198
220
|
}, [closestTagId]);
|
|
@@ -202,14 +224,25 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
202
224
|
useEffect(() => {
|
|
203
225
|
activeLineIdRef.current = activeLineId;
|
|
204
226
|
}, [activeLineId]);
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
linesRef.current = lines;
|
|
207
|
-
}, [lines]);
|
|
208
227
|
useEffect(() => {
|
|
209
228
|
onNoteSubmitRef.current = onNoteSubmit;
|
|
210
229
|
}, [onNoteSubmit]);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
progressRef.current = progress;
|
|
232
|
+
}, [progress]);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
targetPositionRef.current = targetPosition;
|
|
235
|
+
}, [targetPosition]);
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
distanceFromStartRef.current = distanceFromStart;
|
|
238
|
+
}, [distanceFromStart]);
|
|
211
239
|
const openAddNote = useCallback((tagId) => {
|
|
212
240
|
setAddNoteTagId(tagId ?? closestTagIdRef.current ?? null);
|
|
241
|
+
noteSnapshotRef.current = {
|
|
242
|
+
progress: progressRef.current,
|
|
243
|
+
targetPosition: targetPositionRef.current,
|
|
244
|
+
distanceFromStart: distanceFromStartRef.current
|
|
245
|
+
};
|
|
213
246
|
setIsAddNoteOpen(true);
|
|
214
247
|
}, []);
|
|
215
248
|
const closeAddNote = useCallback(() => {
|
|
@@ -218,17 +251,23 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
218
251
|
const submitNote = useCallback((text, isPrivate = true) => {
|
|
219
252
|
const tagId = addNoteTagIdRef.current;
|
|
220
253
|
const lineId = activeLineIdRef.current;
|
|
221
|
-
const
|
|
254
|
+
const snap = noteSnapshotRef.current;
|
|
222
255
|
const payload = {
|
|
223
256
|
text,
|
|
224
257
|
isPrivate,
|
|
225
258
|
tagId,
|
|
226
259
|
lineId,
|
|
227
|
-
position:
|
|
260
|
+
position: snap.targetPosition,
|
|
261
|
+
progress: snap.progress,
|
|
262
|
+
distanceFromStart: snap.distanceFromStart
|
|
228
263
|
};
|
|
229
|
-
onNoteSubmitRef.current
|
|
264
|
+
if (onNoteSubmitRef.current) {
|
|
265
|
+
onNoteSubmitRef.current(payload);
|
|
266
|
+
} else {
|
|
267
|
+
send("note-submit", payload);
|
|
268
|
+
}
|
|
230
269
|
setIsAddNoteOpen(false);
|
|
231
|
-
}, []);
|
|
270
|
+
}, [send]);
|
|
232
271
|
const handleMessage = useCallback(
|
|
233
272
|
(event) => {
|
|
234
273
|
let data = null;
|
|
@@ -259,15 +298,26 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
259
298
|
case "line-close":
|
|
260
299
|
setActiveLineId(null);
|
|
261
300
|
setIsPlaying(false);
|
|
301
|
+
setLineNotes([]);
|
|
262
302
|
onLineClose?.();
|
|
263
303
|
break;
|
|
264
304
|
case "line-progress": {
|
|
265
305
|
const payload = data.payload;
|
|
266
306
|
setProgress(payload.progress);
|
|
267
307
|
setClosestTagId(payload.closestTagId);
|
|
308
|
+
setTargetPosition(payload.targetPosition ?? null);
|
|
309
|
+
setDistanceFromStart(payload.distanceFromStart ?? null);
|
|
268
310
|
onLineProgress?.(payload);
|
|
269
311
|
break;
|
|
270
312
|
}
|
|
313
|
+
case "line-notes": {
|
|
314
|
+
const payload = data.payload;
|
|
315
|
+
setLineNotes(payload.notes ?? []);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case "request-add-note":
|
|
319
|
+
openAddNote();
|
|
320
|
+
break;
|
|
271
321
|
case "autoplay-state": {
|
|
272
322
|
const payload = data.payload;
|
|
273
323
|
setIsPlaying(!!payload.playing);
|
|
@@ -277,32 +327,35 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
277
327
|
onMessage?.(data.type, data.payload);
|
|
278
328
|
}
|
|
279
329
|
},
|
|
280
|
-
[notes, send, onInit, onLineOpen, onLineClose, onLineProgress, onMessage]
|
|
330
|
+
[notes, send, onInit, onLineOpen, onLineClose, onLineProgress, onMessage, openAddNote]
|
|
281
331
|
);
|
|
282
332
|
const activeLine = useMemo2(
|
|
283
333
|
() => lines.find((l) => l._id === activeLineId) ?? null,
|
|
284
334
|
[lines, activeLineId]
|
|
285
335
|
);
|
|
336
|
+
const externalNotes = useMemo2(() => notes ?? [], [notes]);
|
|
286
337
|
const ctxValue = useMemo2(
|
|
287
338
|
() => ({
|
|
288
339
|
project,
|
|
289
340
|
lines,
|
|
290
341
|
activeLineId,
|
|
291
342
|
activeLine,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
isPlaying,
|
|
343
|
+
lineNotes,
|
|
344
|
+
externalNotes,
|
|
295
345
|
isAddNoteOpen,
|
|
296
346
|
addNoteTagId,
|
|
297
347
|
controls,
|
|
298
348
|
openAddNote,
|
|
299
349
|
closeAddNote,
|
|
300
|
-
submitNote
|
|
301
|
-
onNoteSubmit
|
|
350
|
+
submitNote
|
|
302
351
|
}),
|
|
303
|
-
[project, lines, activeLineId, activeLine,
|
|
352
|
+
[project, lines, activeLineId, activeLine, lineNotes, externalNotes, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote]
|
|
353
|
+
);
|
|
354
|
+
const progressCtx = useMemo2(
|
|
355
|
+
() => ({ progress, closestTagId, targetPosition, distanceFromStart, isPlaying }),
|
|
356
|
+
[progress, closestTagId, targetPosition, distanceFromStart, isPlaying]
|
|
304
357
|
);
|
|
305
|
-
return /* @__PURE__ */ React2.createElement(RetorBridgeProvider, { value: ctxValue }, /* @__PURE__ */ React2.createElement(View, { style: [styles.root, style] }, /* @__PURE__ */ React2.createElement(
|
|
358
|
+
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
359
|
WebView,
|
|
307
360
|
{
|
|
308
361
|
ref: webviewRef,
|
|
@@ -315,7 +368,7 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
315
368
|
allowsInlineMediaPlayback: true,
|
|
316
369
|
mediaPlaybackRequiresUserAction: false
|
|
317
370
|
}
|
|
318
|
-
), children));
|
|
371
|
+
), children)));
|
|
319
372
|
});
|
|
320
373
|
var styles = StyleSheet.create({
|
|
321
374
|
root: { flex: 1, position: "relative" },
|
|
@@ -484,6 +537,24 @@ import {
|
|
|
484
537
|
} from "@gorhom/bottom-sheet";
|
|
485
538
|
import Svg, { Circle } from "react-native-svg";
|
|
486
539
|
import { ArrowDown as ArrowDown2, ArrowUp as ArrowUp2, Pause, Play, Plus } from "lucide-react-native";
|
|
540
|
+
|
|
541
|
+
// src/lineProgress.ts
|
|
542
|
+
function mergeLineTagsByProgress(controls, notes) {
|
|
543
|
+
const sortedControls = [...controls].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
544
|
+
const lastIdx = Math.max(1, sortedControls.length - 1);
|
|
545
|
+
const merged = [];
|
|
546
|
+
for (const c of sortedControls) {
|
|
547
|
+
const idx = c.index ?? 0;
|
|
548
|
+
merged.push({ ...c, _isNote: false, progress: idx / lastIdx });
|
|
549
|
+
}
|
|
550
|
+
for (const n of notes) {
|
|
551
|
+
merged.push({ ...n, _isNote: true, progress: n.progress ?? 0 });
|
|
552
|
+
}
|
|
553
|
+
merged.sort((a, b) => a.progress - b.progress);
|
|
554
|
+
return merged;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/LineDetailSheet.tsx
|
|
487
558
|
var renderBackdrop2 = (props) => /* @__PURE__ */ React6.createElement(
|
|
488
559
|
BottomSheetBackdrop2,
|
|
489
560
|
{
|
|
@@ -554,7 +625,8 @@ function DefaultHeader({
|
|
|
554
625
|
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
626
|
}
|
|
556
627
|
function AutoplayButton() {
|
|
557
|
-
const {
|
|
628
|
+
const { controls } = useRetorBridge();
|
|
629
|
+
const { isPlaying, progress } = useRetorProgress();
|
|
558
630
|
const r = 12.5;
|
|
559
631
|
const c = 2 * Math.PI * r;
|
|
560
632
|
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,30 +646,34 @@ function AutoplayButton() {
|
|
|
574
646
|
)), 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
647
|
}
|
|
576
648
|
function LineTagList({ children, listHeader }) {
|
|
577
|
-
const { activeLine,
|
|
649
|
+
const { activeLine, lineNotes, externalNotes } = useRetorBridge();
|
|
650
|
+
const { closestTagId } = useRetorProgress();
|
|
578
651
|
const scrollRef = useRef3(null);
|
|
579
652
|
const offsetsRef = useRef3(/* @__PURE__ */ new Map());
|
|
580
|
-
const tags = useMemo4(
|
|
581
|
-
|
|
582
|
-
[
|
|
583
|
-
|
|
653
|
+
const tags = useMemo4(() => {
|
|
654
|
+
if (!activeLine) return [];
|
|
655
|
+
const controls = (activeLine.tags ?? []).filter(
|
|
656
|
+
(t) => t.name && t.name.trim().length > 0
|
|
657
|
+
);
|
|
658
|
+
const allNotes = [...lineNotes, ...externalNotes].filter(
|
|
659
|
+
(n) => n.name && n.name.trim().length > 0
|
|
660
|
+
);
|
|
661
|
+
return mergeLineTagsByProgress(controls, allNotes);
|
|
662
|
+
}, [activeLine, lineNotes, externalNotes]);
|
|
584
663
|
useEffect3(() => {
|
|
585
664
|
offsetsRef.current = /* @__PURE__ */ new Map();
|
|
586
665
|
}, [activeLine?._id]);
|
|
666
|
+
const lastScrolledIdRef = useRef3(null);
|
|
587
667
|
useEffect3(() => {
|
|
588
|
-
if (!closestTagId) return;
|
|
589
|
-
|
|
590
|
-
const tick = () => {
|
|
668
|
+
if (!closestTagId || closestTagId === lastScrolledIdRef.current) return;
|
|
669
|
+
const t = setTimeout(() => {
|
|
591
670
|
const y = offsetsRef.current.get(closestTagId);
|
|
592
671
|
if (y != null) {
|
|
593
672
|
scrollRef.current?.scrollTo?.({ y, animated: true });
|
|
594
|
-
|
|
673
|
+
lastScrolledIdRef.current = closestTagId;
|
|
595
674
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
};
|
|
599
|
-
let raf = requestAnimationFrame(tick);
|
|
600
|
-
return () => cancelAnimationFrame(raf);
|
|
675
|
+
}, 300);
|
|
676
|
+
return () => clearTimeout(t);
|
|
601
677
|
}, [closestTagId]);
|
|
602
678
|
if (!activeLine) return null;
|
|
603
679
|
const handleItemLayout = (id, e) => {
|