@retor/react-native 0.4.4 → 0.4.6
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/README.md +44 -19
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +46 -9
- package/dist/index.mjs +48 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,24 +111,43 @@ function MyLineCard({ line }: { line: RetorLine }) {
|
|
|
111
111
|
|
|
112
112
|
## Notes
|
|
113
113
|
|
|
114
|
-
`<AddNoteSheet>` triggers when you call `useAddNote().open(tagId?)
|
|
114
|
+
`<AddNoteSheet>` triggers when you call `useAddNote().open(tagId?)`. It collects text + private/public, then either:
|
|
115
|
+
- calls `onNoteSubmit` on the parent `<Viewer>` (if set) — **persistence is your responsibility**
|
|
116
|
+
- or falls back to persisting via Retor's own backend (Convex) when no `onNoteSubmit` is provided
|
|
117
|
+
|
|
118
|
+
Re-pass saved notes back to the 3D scene via `<Notes>`.
|
|
119
|
+
|
|
120
|
+
### Note fields for proper rendering
|
|
121
|
+
|
|
122
|
+
When passing notes via `<Notes>`, each note should include:
|
|
123
|
+
|
|
124
|
+
| Field | Required | Purpose |
|
|
125
|
+
|-------|----------|---------|
|
|
126
|
+
| `_id` | yes | Unique identifier |
|
|
127
|
+
| `name` | yes | The note text (displayed in the tag list and 3D scene) |
|
|
128
|
+
| `position` | yes | `{ x, y, z }` — where the note sits on the line |
|
|
129
|
+
| `objectId` | yes | Set to the `lineId` so the note associates with the correct line |
|
|
130
|
+
| `progress` | yes | `0..1` position along the line (from the submit payload) — used for sort order and scroll-to |
|
|
131
|
+
| `avatarUrl` | recommended | Profile image URL — renders as a circular avatar in the tag pill and list item |
|
|
132
|
+
| `authorName` | recommended | Display name — used as initial-letter fallback when no `avatarUrl` |
|
|
133
|
+
| `tagType` | recommended | Set to `"icon"` for the standard note pill appearance |
|
|
134
|
+
| `userId` | for deletion | The note author's user ID — compared against `Viewer.userId` to show the delete button |
|
|
135
|
+
|
|
136
|
+
### Creating + deleting notes
|
|
115
137
|
|
|
116
138
|
```tsx
|
|
117
139
|
import { useState } from "react";
|
|
118
|
-
import { Viewer, Hud, ProjectSheet, LineDetailSheet, AddNoteSheet, Notes,
|
|
119
|
-
|
|
120
|
-
function AddButton() {
|
|
121
|
-
const { open } = useAddNote();
|
|
122
|
-
return <Pressable onPress={() => open()}><Text>+</Text></Pressable>;
|
|
123
|
-
}
|
|
140
|
+
import { Viewer, Hud, ProjectSheet, LineDetailSheet, AddNoteSheet, Notes, type RetorTag } from "@retor/react-native";
|
|
124
141
|
|
|
125
142
|
export default function Scene() {
|
|
126
143
|
const [notes, setNotes] = useState<RetorTag[]>([]);
|
|
144
|
+
const currentUserId = "user_abc"; // your auth system's user ID
|
|
127
145
|
|
|
128
146
|
return (
|
|
129
147
|
<Viewer
|
|
130
148
|
projectId="abc123"
|
|
131
|
-
|
|
149
|
+
userId={currentUserId}
|
|
150
|
+
onNoteSubmit={({ text, isPrivate, lineId, position, progress }) => {
|
|
132
151
|
if (!position) return;
|
|
133
152
|
setNotes((prev) => [
|
|
134
153
|
...prev,
|
|
@@ -136,24 +155,24 @@ export default function Scene() {
|
|
|
136
155
|
_id: `note-${Date.now()}`,
|
|
137
156
|
name: text,
|
|
138
157
|
position,
|
|
158
|
+
progress,
|
|
139
159
|
objectId: lineId ?? undefined,
|
|
160
|
+
tagType: "icon",
|
|
161
|
+
avatarUrl: "https://example.com/avatar.jpg",
|
|
162
|
+
authorName: "Jane",
|
|
163
|
+
userId: currentUserId,
|
|
140
164
|
},
|
|
141
165
|
]);
|
|
142
166
|
}}
|
|
167
|
+
onNoteDelete={(noteId) => {
|
|
168
|
+
setNotes((prev) => prev.filter((n) => n._id !== noteId));
|
|
169
|
+
// also delete from your backend
|
|
170
|
+
}}
|
|
143
171
|
>
|
|
144
172
|
<Notes notes={notes} />
|
|
145
173
|
<Hud>
|
|
146
174
|
<ProjectSheet />
|
|
147
|
-
<LineDetailSheet
|
|
148
|
-
<LineTagList>
|
|
149
|
-
{(tag, isActive) => (
|
|
150
|
-
<View style={{ flexDirection: "row", padding: 12 }}>
|
|
151
|
-
<Text style={{ flex: 1, color: isActive ? "white" : "gray" }}>{tag.name}</Text>
|
|
152
|
-
{isActive && <AddButton />}
|
|
153
|
-
</View>
|
|
154
|
-
)}
|
|
155
|
-
</LineTagList>
|
|
156
|
-
</LineDetailSheet>
|
|
175
|
+
<LineDetailSheet />
|
|
157
176
|
<AddNoteSheet />
|
|
158
177
|
</Hud>
|
|
159
178
|
</Viewer>
|
|
@@ -161,6 +180,12 @@ export default function Scene() {
|
|
|
161
180
|
}
|
|
162
181
|
```
|
|
163
182
|
|
|
183
|
+
When `onNoteSubmit` is **not** provided, the SDK sends the note to Retor's backend automatically (using the signed-in Clerk session inside the WebView). No `<Notes>` re-injection needed in that case.
|
|
184
|
+
|
|
185
|
+
When a user deletes a note, the SDK:
|
|
186
|
+
1. Optimistically removes it from the local tag list
|
|
187
|
+
2. Fires `onNoteDelete(noteId)` so you can delete from your backend
|
|
188
|
+
|
|
164
189
|
## Hooks
|
|
165
190
|
|
|
166
191
|
All hooks read from the bridge context provided by the parent `<Viewer>`.
|
|
@@ -173,7 +198,7 @@ All hooks read from the bridge context provided by the parent `<Viewer>`.
|
|
|
173
198
|
| `useLineProgress()` | `{ progress, closestTagId }` |
|
|
174
199
|
| `useAutoplay()` | `{ isPlaying, toggle, play, pause }` |
|
|
175
200
|
| `useAddNote()` | `{ isOpen, tagId, open, close, submit }` |
|
|
176
|
-
| `useViewer()` | Imperative controls (`openLine`, `exitLine`, `scrollToTag`, ...) |
|
|
201
|
+
| `useViewer()` | Imperative controls (`openLine`, `exitLine`, `scrollToTag`, `scrollToProgress`, ...) |
|
|
177
202
|
|
|
178
203
|
## Imperative API
|
|
179
204
|
|
package/dist/index.d.mts
CHANGED
|
@@ -23,6 +23,8 @@ interface RetorTag {
|
|
|
23
23
|
avatarUrl?: string;
|
|
24
24
|
/** Display name of the note's author. Used for the initial-letter fallback when `avatarUrl` is absent. */
|
|
25
25
|
authorName?: string;
|
|
26
|
+
/** ID of the user who created this note. Compared against `Viewer.userId` to show the delete button. */
|
|
27
|
+
userId?: string;
|
|
26
28
|
}
|
|
27
29
|
interface RetorLine {
|
|
28
30
|
_id: string;
|
|
@@ -34,6 +36,8 @@ interface RetorLine {
|
|
|
34
36
|
scrollType?: "track" | "observe";
|
|
35
37
|
closed?: boolean;
|
|
36
38
|
notesSupported?: boolean;
|
|
39
|
+
/** User-defined key/value fields set in the line editor. */
|
|
40
|
+
metadata?: Record<string, string | number | boolean>;
|
|
37
41
|
tags: RetorTag[];
|
|
38
42
|
}
|
|
39
43
|
interface RetorProject {
|
|
@@ -103,12 +107,16 @@ interface RetorBridgeContextValue {
|
|
|
103
107
|
lineNotes: RetorTag[];
|
|
104
108
|
/** Notes injected by the consumer via the <Notes> sentinel. */
|
|
105
109
|
externalNotes: RetorTag[];
|
|
110
|
+
/** The current user's ID — notes with a matching `userId` show a delete button. */
|
|
111
|
+
userId: string | null;
|
|
106
112
|
isAddNoteOpen: boolean;
|
|
107
113
|
addNoteTagId: string | null;
|
|
108
114
|
controls: ViewerHandle;
|
|
109
115
|
openAddNote: (tagId?: string) => void;
|
|
110
116
|
closeAddNote: () => void;
|
|
111
117
|
submitNote: (text: string, isPrivate?: boolean) => void;
|
|
118
|
+
/** Optimistically remove a note and fire the consumer's onNoteDelete callback. */
|
|
119
|
+
deleteNote: (noteId: string) => void;
|
|
112
120
|
}
|
|
113
121
|
declare function useRetorBridge(): RetorBridgeContextValue;
|
|
114
122
|
/** Returns the array of all lines in the current project. */
|
|
@@ -187,8 +195,12 @@ interface ViewerProps {
|
|
|
187
195
|
id?: string;
|
|
188
196
|
/** Base URL where Retor is hosted. Defaults to https://retor.app */
|
|
189
197
|
baseUrl?: string;
|
|
198
|
+
/** The current user's ID. When set, notes with a matching `userId` field show a delete button. */
|
|
199
|
+
userId?: string;
|
|
190
200
|
/** Called when a note is submitted via `<AddNoteSheet>`. Receives the text + position. */
|
|
191
201
|
onNoteSubmit?: (note: NoteSubmitPayload) => void;
|
|
202
|
+
/** Called when the user deletes a note. The note is optimistically removed from the list. */
|
|
203
|
+
onNoteDelete?: (noteId: string) => void;
|
|
192
204
|
onInit?: (data: InitPayload) => void;
|
|
193
205
|
onLineOpen?: (data: {
|
|
194
206
|
lineId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ interface RetorTag {
|
|
|
23
23
|
avatarUrl?: string;
|
|
24
24
|
/** Display name of the note's author. Used for the initial-letter fallback when `avatarUrl` is absent. */
|
|
25
25
|
authorName?: string;
|
|
26
|
+
/** ID of the user who created this note. Compared against `Viewer.userId` to show the delete button. */
|
|
27
|
+
userId?: string;
|
|
26
28
|
}
|
|
27
29
|
interface RetorLine {
|
|
28
30
|
_id: string;
|
|
@@ -34,6 +36,8 @@ interface RetorLine {
|
|
|
34
36
|
scrollType?: "track" | "observe";
|
|
35
37
|
closed?: boolean;
|
|
36
38
|
notesSupported?: boolean;
|
|
39
|
+
/** User-defined key/value fields set in the line editor. */
|
|
40
|
+
metadata?: Record<string, string | number | boolean>;
|
|
37
41
|
tags: RetorTag[];
|
|
38
42
|
}
|
|
39
43
|
interface RetorProject {
|
|
@@ -103,12 +107,16 @@ interface RetorBridgeContextValue {
|
|
|
103
107
|
lineNotes: RetorTag[];
|
|
104
108
|
/** Notes injected by the consumer via the <Notes> sentinel. */
|
|
105
109
|
externalNotes: RetorTag[];
|
|
110
|
+
/** The current user's ID — notes with a matching `userId` show a delete button. */
|
|
111
|
+
userId: string | null;
|
|
106
112
|
isAddNoteOpen: boolean;
|
|
107
113
|
addNoteTagId: string | null;
|
|
108
114
|
controls: ViewerHandle;
|
|
109
115
|
openAddNote: (tagId?: string) => void;
|
|
110
116
|
closeAddNote: () => void;
|
|
111
117
|
submitNote: (text: string, isPrivate?: boolean) => void;
|
|
118
|
+
/** Optimistically remove a note and fire the consumer's onNoteDelete callback. */
|
|
119
|
+
deleteNote: (noteId: string) => void;
|
|
112
120
|
}
|
|
113
121
|
declare function useRetorBridge(): RetorBridgeContextValue;
|
|
114
122
|
/** Returns the array of all lines in the current project. */
|
|
@@ -187,8 +195,12 @@ interface ViewerProps {
|
|
|
187
195
|
id?: string;
|
|
188
196
|
/** Base URL where Retor is hosted. Defaults to https://retor.app */
|
|
189
197
|
baseUrl?: string;
|
|
198
|
+
/** The current user's ID. When set, notes with a matching `userId` field show a delete button. */
|
|
199
|
+
userId?: string;
|
|
190
200
|
/** Called when a note is submitted via `<AddNoteSheet>`. Receives the text + position. */
|
|
191
201
|
onNoteSubmit?: (note: NoteSubmitPayload) => void;
|
|
202
|
+
/** Called when the user deletes a note. The note is optimistically removed from the list. */
|
|
203
|
+
onNoteDelete?: (noteId: string) => void;
|
|
192
204
|
onInit?: (data: InitPayload) => void;
|
|
193
205
|
onLineOpen?: (data: {
|
|
194
206
|
lineId: string;
|
package/dist/index.js
CHANGED
|
@@ -78,12 +78,14 @@ var fallback = {
|
|
|
78
78
|
activeLine: null,
|
|
79
79
|
lineNotes: [],
|
|
80
80
|
externalNotes: [],
|
|
81
|
+
userId: null,
|
|
81
82
|
isAddNoteOpen: false,
|
|
82
83
|
addNoteTagId: null,
|
|
83
84
|
controls: noopHandle,
|
|
84
85
|
openAddNote: noop,
|
|
85
86
|
closeAddNote: noop,
|
|
86
|
-
submitNote: noop
|
|
87
|
+
submitNote: noop,
|
|
88
|
+
deleteNote: noop
|
|
87
89
|
};
|
|
88
90
|
function useRetorBridge() {
|
|
89
91
|
return (0, import_react.useContext)(RetorBridgeContext) ?? fallback;
|
|
@@ -205,7 +207,7 @@ function extractNotes(children) {
|
|
|
205
207
|
});
|
|
206
208
|
return notes;
|
|
207
209
|
}
|
|
208
|
-
var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "default", baseUrl = "https://retor.app", onNoteSubmit, onInit, onLineOpen, onLineClose, onLineProgress, onMessage, style, children }, ref) {
|
|
210
|
+
var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "default", baseUrl = "https://retor.app", userId = null, onNoteSubmit, onNoteDelete, onInit, onLineOpen, onLineClose, onLineProgress, onMessage, style, children }, ref) {
|
|
209
211
|
const webviewRef = (0, import_react2.useRef)(null);
|
|
210
212
|
const notes = extractNotes(children);
|
|
211
213
|
const readyRef = (0, import_react2.useRef)(false);
|
|
@@ -260,6 +262,7 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
260
262
|
const addNoteTagIdRef = (0, import_react2.useRef)(addNoteTagId);
|
|
261
263
|
const activeLineIdRef = (0, import_react2.useRef)(activeLineId);
|
|
262
264
|
const onNoteSubmitRef = (0, import_react2.useRef)(onNoteSubmit);
|
|
265
|
+
const onNoteDeleteRef = (0, import_react2.useRef)(onNoteDelete);
|
|
263
266
|
const noteSnapshotRef = (0, import_react2.useRef)({ progress: 0, targetPosition: null, distanceFromStart: null });
|
|
264
267
|
const progressRef = (0, import_react2.useRef)(progress);
|
|
265
268
|
const targetPositionRef = (0, import_react2.useRef)(targetPosition);
|
|
@@ -276,6 +279,9 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
276
279
|
(0, import_react2.useEffect)(() => {
|
|
277
280
|
onNoteSubmitRef.current = onNoteSubmit;
|
|
278
281
|
}, [onNoteSubmit]);
|
|
282
|
+
(0, import_react2.useEffect)(() => {
|
|
283
|
+
onNoteDeleteRef.current = onNoteDelete;
|
|
284
|
+
}, [onNoteDelete]);
|
|
279
285
|
(0, import_react2.useEffect)(() => {
|
|
280
286
|
progressRef.current = progress;
|
|
281
287
|
}, [progress]);
|
|
@@ -317,6 +323,10 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
317
323
|
}
|
|
318
324
|
setIsAddNoteOpen(false);
|
|
319
325
|
}, [send]);
|
|
326
|
+
const deleteNote = (0, import_react2.useCallback)((noteId) => {
|
|
327
|
+
setLineNotes((prev) => prev.filter((n) => n._id !== noteId));
|
|
328
|
+
onNoteDeleteRef.current?.(noteId);
|
|
329
|
+
}, []);
|
|
320
330
|
const handleMessage = (0, import_react2.useCallback)(
|
|
321
331
|
(event) => {
|
|
322
332
|
let data = null;
|
|
@@ -391,14 +401,16 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
|
|
|
391
401
|
activeLine,
|
|
392
402
|
lineNotes,
|
|
393
403
|
externalNotes,
|
|
404
|
+
userId,
|
|
394
405
|
isAddNoteOpen,
|
|
395
406
|
addNoteTagId,
|
|
396
407
|
controls,
|
|
397
408
|
openAddNote,
|
|
398
409
|
closeAddNote,
|
|
399
|
-
submitNote
|
|
410
|
+
submitNote,
|
|
411
|
+
deleteNote
|
|
400
412
|
}),
|
|
401
|
-
[project, lines, activeLineId, activeLine, lineNotes, externalNotes, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote]
|
|
413
|
+
[project, lines, activeLineId, activeLine, lineNotes, externalNotes, userId, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote, deleteNote]
|
|
402
414
|
);
|
|
403
415
|
const progressCtx = (0, import_react2.useMemo)(
|
|
404
416
|
() => ({ progress, closestTagId, targetPosition, distanceFromStart, isPlaying }),
|
|
@@ -655,7 +667,7 @@ function LineDetailSheet({ snapPoints = ["35%", "75%"], topInset, renderHeader,
|
|
|
655
667
|
backgroundComponent: BlurBackground,
|
|
656
668
|
topInset: effectiveTopInset
|
|
657
669
|
},
|
|
658
|
-
activeLine && (children
|
|
670
|
+
activeLine && (children ? /* @__PURE__ */ import_react6.default.createElement(import_bottom_sheet3.BottomSheetScrollView, { contentContainerStyle: styles3.customContent }, children) : /* @__PURE__ */ import_react6.default.createElement(DefaultLineTagList, { listHeader: header }))
|
|
659
671
|
);
|
|
660
672
|
}
|
|
661
673
|
function DefaultHeader({
|
|
@@ -727,14 +739,22 @@ function LineTagList({ children, listHeader }) {
|
|
|
727
739
|
contentContainerStyle: styles3.list
|
|
728
740
|
},
|
|
729
741
|
listHeader,
|
|
730
|
-
tags.map((tag
|
|
742
|
+
tags.map((tag, i) => {
|
|
743
|
+
const isNote = !!tag._isNote;
|
|
744
|
+
const prevIsNote = i > 0 && !!tags[i - 1]._isNote;
|
|
745
|
+
const showSep = isNote && !prevIsNote && i > 0 || !isNote && i > 0 && prevIsNote;
|
|
746
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_react6.Fragment, { key: tag._id }, showSep && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.separator }), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { onLayout: (e) => handleItemLayout(tag._id, e) }, children(tag, tag._id === closestTagId)));
|
|
747
|
+
})
|
|
731
748
|
);
|
|
732
749
|
}
|
|
733
750
|
function DefaultLineTagList({ listHeader }) {
|
|
734
751
|
return /* @__PURE__ */ import_react6.default.createElement(LineTagList, { listHeader }, (tag, isActive) => /* @__PURE__ */ import_react6.default.createElement(DefaultTagItem, { tag, isActive }));
|
|
735
752
|
}
|
|
736
753
|
function DefaultTagItem({ tag, isActive }) {
|
|
737
|
-
const { controls, openAddNote } = useRetorBridge();
|
|
754
|
+
const { controls, openAddNote, userId, deleteNote } = useRetorBridge();
|
|
755
|
+
const isNote = !!tag._isNote;
|
|
756
|
+
const tagUserId = tag.userId;
|
|
757
|
+
const isOwn = !!userId && !!tagUserId && tagUserId === userId;
|
|
738
758
|
const initial = tag.authorName?.trim().charAt(0).toUpperCase();
|
|
739
759
|
const handlePress = () => {
|
|
740
760
|
if (typeof tag.progress === "number") controls.scrollToProgress(tag.progress);
|
|
@@ -747,9 +767,17 @@ function DefaultTagItem({ tag, isActive }) {
|
|
|
747
767
|
style: [styles3.tagItem, isActive && styles3.tagItemActive]
|
|
748
768
|
},
|
|
749
769
|
tag.avatarUrl ? /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Image, { source: { uri: tag.avatarUrl }, style: styles3.tagAvatar }) : initial ? /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.tagInitial }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.tagInitialText }, initial)) : null,
|
|
750
|
-
/* @__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.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
|
|
770
|
+
/* @__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.tagText, isActive && styles3.tagTextActive], numberOfLines: isNote ? 3 : 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
|
|
751
771
|
tag.subtitle && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
|
|
752
|
-
|
|
772
|
+
isNote && isOwn && /* @__PURE__ */ import_react6.default.createElement(
|
|
773
|
+
import_react_native5.Pressable,
|
|
774
|
+
{
|
|
775
|
+
onPress: () => deleteNote(tag._id),
|
|
776
|
+
style: styles3.deleteBtn
|
|
777
|
+
},
|
|
778
|
+
/* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.Trash2, { size: 12, color: "rgba(255,255,255,0.4)" })
|
|
779
|
+
),
|
|
780
|
+
isActive && !isNote && /* @__PURE__ */ import_react6.default.createElement(
|
|
753
781
|
import_react_native5.Pressable,
|
|
754
782
|
{
|
|
755
783
|
onPress: (e) => {
|
|
@@ -764,6 +792,7 @@ function DefaultTagItem({ tag, isActive }) {
|
|
|
764
792
|
}
|
|
765
793
|
var styles3 = import_react_native5.StyleSheet.create({
|
|
766
794
|
handle: { backgroundColor: "rgba(255,255,255,0.3)" },
|
|
795
|
+
customContent: { paddingBottom: 120 },
|
|
767
796
|
header: {
|
|
768
797
|
flexDirection: "row",
|
|
769
798
|
alignItems: "flex-start",
|
|
@@ -806,10 +835,18 @@ var styles3 = import_react_native5.StyleSheet.create({
|
|
|
806
835
|
justifyContent: "center"
|
|
807
836
|
},
|
|
808
837
|
tagInitialText: { color: "white", fontSize: 11, fontWeight: "700" },
|
|
838
|
+
separator: { height: 1, backgroundColor: "rgba(255,255,255,0.1)", marginHorizontal: 20, marginVertical: 4 },
|
|
809
839
|
tagText: { color: "rgba(255,255,255,0.6)", fontSize: 13 },
|
|
810
840
|
tagTextActive: { color: "white", fontWeight: "600" },
|
|
811
841
|
tagDescription: { color: "rgba(255,255,255,0.4)", fontSize: 11, marginTop: 2, lineHeight: 14 },
|
|
812
842
|
tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10 },
|
|
843
|
+
deleteBtn: {
|
|
844
|
+
width: 24,
|
|
845
|
+
height: 24,
|
|
846
|
+
borderRadius: 12,
|
|
847
|
+
alignItems: "center",
|
|
848
|
+
justifyContent: "center"
|
|
849
|
+
},
|
|
813
850
|
plusBtn: {
|
|
814
851
|
width: 24,
|
|
815
852
|
height: 24,
|
package/dist/index.mjs
CHANGED
|
@@ -25,12 +25,14 @@ var fallback = {
|
|
|
25
25
|
activeLine: null,
|
|
26
26
|
lineNotes: [],
|
|
27
27
|
externalNotes: [],
|
|
28
|
+
userId: null,
|
|
28
29
|
isAddNoteOpen: false,
|
|
29
30
|
addNoteTagId: null,
|
|
30
31
|
controls: noopHandle,
|
|
31
32
|
openAddNote: noop,
|
|
32
33
|
closeAddNote: noop,
|
|
33
|
-
submitNote: noop
|
|
34
|
+
submitNote: noop,
|
|
35
|
+
deleteNote: noop
|
|
34
36
|
};
|
|
35
37
|
function useRetorBridge() {
|
|
36
38
|
return useContext(RetorBridgeContext) ?? fallback;
|
|
@@ -161,7 +163,7 @@ function extractNotes(children) {
|
|
|
161
163
|
});
|
|
162
164
|
return notes;
|
|
163
165
|
}
|
|
164
|
-
var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl = "https://retor.app", onNoteSubmit, onInit, onLineOpen, onLineClose, onLineProgress, onMessage, style, children }, ref) {
|
|
166
|
+
var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl = "https://retor.app", userId = null, onNoteSubmit, onNoteDelete, onInit, onLineOpen, onLineClose, onLineProgress, onMessage, style, children }, ref) {
|
|
165
167
|
const webviewRef = useRef(null);
|
|
166
168
|
const notes = extractNotes(children);
|
|
167
169
|
const readyRef = useRef(false);
|
|
@@ -216,6 +218,7 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
216
218
|
const addNoteTagIdRef = useRef(addNoteTagId);
|
|
217
219
|
const activeLineIdRef = useRef(activeLineId);
|
|
218
220
|
const onNoteSubmitRef = useRef(onNoteSubmit);
|
|
221
|
+
const onNoteDeleteRef = useRef(onNoteDelete);
|
|
219
222
|
const noteSnapshotRef = useRef({ progress: 0, targetPosition: null, distanceFromStart: null });
|
|
220
223
|
const progressRef = useRef(progress);
|
|
221
224
|
const targetPositionRef = useRef(targetPosition);
|
|
@@ -232,6 +235,9 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
232
235
|
useEffect(() => {
|
|
233
236
|
onNoteSubmitRef.current = onNoteSubmit;
|
|
234
237
|
}, [onNoteSubmit]);
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
onNoteDeleteRef.current = onNoteDelete;
|
|
240
|
+
}, [onNoteDelete]);
|
|
235
241
|
useEffect(() => {
|
|
236
242
|
progressRef.current = progress;
|
|
237
243
|
}, [progress]);
|
|
@@ -273,6 +279,10 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
273
279
|
}
|
|
274
280
|
setIsAddNoteOpen(false);
|
|
275
281
|
}, [send]);
|
|
282
|
+
const deleteNote = useCallback((noteId) => {
|
|
283
|
+
setLineNotes((prev) => prev.filter((n) => n._id !== noteId));
|
|
284
|
+
onNoteDeleteRef.current?.(noteId);
|
|
285
|
+
}, []);
|
|
276
286
|
const handleMessage = useCallback(
|
|
277
287
|
(event) => {
|
|
278
288
|
let data = null;
|
|
@@ -347,14 +357,16 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
|
|
|
347
357
|
activeLine,
|
|
348
358
|
lineNotes,
|
|
349
359
|
externalNotes,
|
|
360
|
+
userId,
|
|
350
361
|
isAddNoteOpen,
|
|
351
362
|
addNoteTagId,
|
|
352
363
|
controls,
|
|
353
364
|
openAddNote,
|
|
354
365
|
closeAddNote,
|
|
355
|
-
submitNote
|
|
366
|
+
submitNote,
|
|
367
|
+
deleteNote
|
|
356
368
|
}),
|
|
357
|
-
[project, lines, activeLineId, activeLine, lineNotes, externalNotes, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote]
|
|
369
|
+
[project, lines, activeLineId, activeLine, lineNotes, externalNotes, userId, isAddNoteOpen, addNoteTagId, controls, openAddNote, closeAddNote, submitNote, deleteNote]
|
|
358
370
|
);
|
|
359
371
|
const progressCtx = useMemo2(
|
|
360
372
|
() => ({ progress, closestTagId, targetPosition, distanceFromStart, isPlaying }),
|
|
@@ -532,7 +544,7 @@ var styles2 = StyleSheet4.create({
|
|
|
532
544
|
});
|
|
533
545
|
|
|
534
546
|
// src/LineDetailSheet.tsx
|
|
535
|
-
import React6, { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
|
|
547
|
+
import React6, { Fragment, useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
|
|
536
548
|
import { Image, Pressable as Pressable2, StyleSheet as StyleSheet5, Text as Text2, View as View5 } from "react-native";
|
|
537
549
|
import {
|
|
538
550
|
BottomSheetBackdrop as BottomSheetBackdrop2,
|
|
@@ -541,7 +553,7 @@ import {
|
|
|
541
553
|
BottomSheetScrollView
|
|
542
554
|
} from "@gorhom/bottom-sheet";
|
|
543
555
|
import Svg, { Circle } from "react-native-svg";
|
|
544
|
-
import { ArrowDown as ArrowDown2, ArrowUp as ArrowUp2, Pause, Play, Plus } from "lucide-react-native";
|
|
556
|
+
import { ArrowDown as ArrowDown2, ArrowUp as ArrowUp2, Pause, Play, Plus, Trash2 } from "lucide-react-native";
|
|
545
557
|
|
|
546
558
|
// src/lineProgress.ts
|
|
547
559
|
function mergeLineTagsByProgress(controls, notes) {
|
|
@@ -616,7 +628,7 @@ function LineDetailSheet({ snapPoints = ["35%", "75%"], topInset, renderHeader,
|
|
|
616
628
|
backgroundComponent: BlurBackground,
|
|
617
629
|
topInset: effectiveTopInset
|
|
618
630
|
},
|
|
619
|
-
activeLine && (children
|
|
631
|
+
activeLine && (children ? /* @__PURE__ */ React6.createElement(BottomSheetScrollView, { contentContainerStyle: styles3.customContent }, children) : /* @__PURE__ */ React6.createElement(DefaultLineTagList, { listHeader: header }))
|
|
620
632
|
);
|
|
621
633
|
}
|
|
622
634
|
function DefaultHeader({
|
|
@@ -688,14 +700,22 @@ function LineTagList({ children, listHeader }) {
|
|
|
688
700
|
contentContainerStyle: styles3.list
|
|
689
701
|
},
|
|
690
702
|
listHeader,
|
|
691
|
-
tags.map((tag
|
|
703
|
+
tags.map((tag, i) => {
|
|
704
|
+
const isNote = !!tag._isNote;
|
|
705
|
+
const prevIsNote = i > 0 && !!tags[i - 1]._isNote;
|
|
706
|
+
const showSep = isNote && !prevIsNote && i > 0 || !isNote && i > 0 && prevIsNote;
|
|
707
|
+
return /* @__PURE__ */ React6.createElement(Fragment, { key: tag._id }, showSep && /* @__PURE__ */ React6.createElement(View5, { style: styles3.separator }), /* @__PURE__ */ React6.createElement(View5, { onLayout: (e) => handleItemLayout(tag._id, e) }, children(tag, tag._id === closestTagId)));
|
|
708
|
+
})
|
|
692
709
|
);
|
|
693
710
|
}
|
|
694
711
|
function DefaultLineTagList({ listHeader }) {
|
|
695
712
|
return /* @__PURE__ */ React6.createElement(LineTagList, { listHeader }, (tag, isActive) => /* @__PURE__ */ React6.createElement(DefaultTagItem, { tag, isActive }));
|
|
696
713
|
}
|
|
697
714
|
function DefaultTagItem({ tag, isActive }) {
|
|
698
|
-
const { controls, openAddNote } = useRetorBridge();
|
|
715
|
+
const { controls, openAddNote, userId, deleteNote } = useRetorBridge();
|
|
716
|
+
const isNote = !!tag._isNote;
|
|
717
|
+
const tagUserId = tag.userId;
|
|
718
|
+
const isOwn = !!userId && !!tagUserId && tagUserId === userId;
|
|
699
719
|
const initial = tag.authorName?.trim().charAt(0).toUpperCase();
|
|
700
720
|
const handlePress = () => {
|
|
701
721
|
if (typeof tag.progress === "number") controls.scrollToProgress(tag.progress);
|
|
@@ -708,9 +728,17 @@ function DefaultTagItem({ tag, isActive }) {
|
|
|
708
728
|
style: [styles3.tagItem, isActive && styles3.tagItemActive]
|
|
709
729
|
},
|
|
710
730
|
tag.avatarUrl ? /* @__PURE__ */ React6.createElement(Image, { source: { uri: tag.avatarUrl }, style: styles3.tagAvatar }) : initial ? /* @__PURE__ */ React6.createElement(View5, { style: styles3.tagInitial }, /* @__PURE__ */ React6.createElement(Text2, { style: styles3.tagInitialText }, initial)) : null,
|
|
711
|
-
/* @__PURE__ */ React6.createElement(View5, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React6.createElement(Text2, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
|
|
731
|
+
/* @__PURE__ */ React6.createElement(View5, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React6.createElement(Text2, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: isNote ? 3 : 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
|
|
712
732
|
tag.subtitle && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
|
|
713
|
-
|
|
733
|
+
isNote && isOwn && /* @__PURE__ */ React6.createElement(
|
|
734
|
+
Pressable2,
|
|
735
|
+
{
|
|
736
|
+
onPress: () => deleteNote(tag._id),
|
|
737
|
+
style: styles3.deleteBtn
|
|
738
|
+
},
|
|
739
|
+
/* @__PURE__ */ React6.createElement(Trash2, { size: 12, color: "rgba(255,255,255,0.4)" })
|
|
740
|
+
),
|
|
741
|
+
isActive && !isNote && /* @__PURE__ */ React6.createElement(
|
|
714
742
|
Pressable2,
|
|
715
743
|
{
|
|
716
744
|
onPress: (e) => {
|
|
@@ -725,6 +753,7 @@ function DefaultTagItem({ tag, isActive }) {
|
|
|
725
753
|
}
|
|
726
754
|
var styles3 = StyleSheet5.create({
|
|
727
755
|
handle: { backgroundColor: "rgba(255,255,255,0.3)" },
|
|
756
|
+
customContent: { paddingBottom: 120 },
|
|
728
757
|
header: {
|
|
729
758
|
flexDirection: "row",
|
|
730
759
|
alignItems: "flex-start",
|
|
@@ -767,10 +796,18 @@ var styles3 = StyleSheet5.create({
|
|
|
767
796
|
justifyContent: "center"
|
|
768
797
|
},
|
|
769
798
|
tagInitialText: { color: "white", fontSize: 11, fontWeight: "700" },
|
|
799
|
+
separator: { height: 1, backgroundColor: "rgba(255,255,255,0.1)", marginHorizontal: 20, marginVertical: 4 },
|
|
770
800
|
tagText: { color: "rgba(255,255,255,0.6)", fontSize: 13 },
|
|
771
801
|
tagTextActive: { color: "white", fontWeight: "600" },
|
|
772
802
|
tagDescription: { color: "rgba(255,255,255,0.4)", fontSize: 11, marginTop: 2, lineHeight: 14 },
|
|
773
803
|
tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10 },
|
|
804
|
+
deleteBtn: {
|
|
805
|
+
width: 24,
|
|
806
|
+
height: 24,
|
|
807
|
+
borderRadius: 12,
|
|
808
|
+
alignItems: "center",
|
|
809
|
+
justifyContent: "center"
|
|
810
|
+
},
|
|
774
811
|
plusBtn: {
|
|
775
812
|
width: 24,
|
|
776
813
|
height: 24,
|