@washi-ui/react 1.0.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/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/index.d.mts +335 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.js +1014 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +981 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
// src/useWashi.ts
|
|
2
|
+
import { useRef, useState, useEffect, useCallback } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Washi
|
|
5
|
+
} from "@washi-ui/core";
|
|
6
|
+
function useWashi(options) {
|
|
7
|
+
const {
|
|
8
|
+
adapter,
|
|
9
|
+
initialMode = "view",
|
|
10
|
+
mountOptions,
|
|
11
|
+
onPinPlaced,
|
|
12
|
+
onCommentClick,
|
|
13
|
+
onCommentUpdate,
|
|
14
|
+
onCommentDelete
|
|
15
|
+
} = options;
|
|
16
|
+
const iframeRef = useRef(null);
|
|
17
|
+
const washiRef = useRef(null);
|
|
18
|
+
const [mode, setModeState] = useState(initialMode);
|
|
19
|
+
const [comments, setComments] = useState([]);
|
|
20
|
+
const [isReady, setIsReady] = useState(false);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
washiRef.current = new Washi(adapter);
|
|
24
|
+
return () => {
|
|
25
|
+
washiRef.current?.unmount();
|
|
26
|
+
washiRef.current = null;
|
|
27
|
+
};
|
|
28
|
+
}, [adapter]);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const iframe = iframeRef.current;
|
|
31
|
+
const washi = washiRef.current;
|
|
32
|
+
if (!iframe || !washi) return;
|
|
33
|
+
const handleLoad = async () => {
|
|
34
|
+
try {
|
|
35
|
+
await washi.mount(iframe, mountOptions);
|
|
36
|
+
setComments(washi.getComments());
|
|
37
|
+
if (initialMode !== "view") {
|
|
38
|
+
washi.setMode(initialMode);
|
|
39
|
+
}
|
|
40
|
+
setIsReady(true);
|
|
41
|
+
setError(null);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
44
|
+
setIsReady(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
if (iframe.contentDocument?.readyState === "complete") {
|
|
48
|
+
handleLoad();
|
|
49
|
+
} else {
|
|
50
|
+
iframe.addEventListener("load", handleLoad);
|
|
51
|
+
}
|
|
52
|
+
return () => {
|
|
53
|
+
iframe.removeEventListener("load", handleLoad);
|
|
54
|
+
};
|
|
55
|
+
}, [mountOptions, initialMode]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const washi = washiRef.current;
|
|
58
|
+
if (!washi || !isReady) return;
|
|
59
|
+
const unsubscribers = [];
|
|
60
|
+
if (onPinPlaced) {
|
|
61
|
+
unsubscribers.push(
|
|
62
|
+
washi.on("pin:placed", (event) => {
|
|
63
|
+
onPinPlaced(event);
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (onCommentClick) {
|
|
68
|
+
unsubscribers.push(
|
|
69
|
+
washi.on("comment:clicked", (comment) => {
|
|
70
|
+
onCommentClick(comment);
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (onCommentUpdate) {
|
|
75
|
+
unsubscribers.push(
|
|
76
|
+
washi.on(
|
|
77
|
+
"comment:updated",
|
|
78
|
+
(data) => {
|
|
79
|
+
onCommentUpdate(data);
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (onCommentDelete) {
|
|
85
|
+
unsubscribers.push(
|
|
86
|
+
washi.on("comment:deleted", (id) => {
|
|
87
|
+
onCommentDelete(id);
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return () => {
|
|
92
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
93
|
+
};
|
|
94
|
+
}, [isReady, onPinPlaced, onCommentClick, onCommentUpdate, onCommentDelete]);
|
|
95
|
+
const setMode = useCallback((newMode) => {
|
|
96
|
+
const washi = washiRef.current;
|
|
97
|
+
if (!washi) return;
|
|
98
|
+
try {
|
|
99
|
+
washi.setMode(newMode);
|
|
100
|
+
setModeState(newMode);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
103
|
+
}
|
|
104
|
+
}, []);
|
|
105
|
+
const addComment = useCallback(async (input) => {
|
|
106
|
+
const washi = washiRef.current;
|
|
107
|
+
if (!washi) throw new Error("Washi is not initialized");
|
|
108
|
+
const comment = await washi.addComment(input);
|
|
109
|
+
setComments(washi.getComments());
|
|
110
|
+
return comment;
|
|
111
|
+
}, []);
|
|
112
|
+
const updateComment = useCallback(
|
|
113
|
+
async (id, updates) => {
|
|
114
|
+
const washi = washiRef.current;
|
|
115
|
+
if (!washi) throw new Error("Washi is not initialized");
|
|
116
|
+
await washi.updateComment(id, updates);
|
|
117
|
+
setComments(washi.getComments());
|
|
118
|
+
},
|
|
119
|
+
[]
|
|
120
|
+
);
|
|
121
|
+
const deleteComment = useCallback(async (id) => {
|
|
122
|
+
const washi = washiRef.current;
|
|
123
|
+
if (!washi) throw new Error("Washi is not initialized");
|
|
124
|
+
await washi.deleteComment(id);
|
|
125
|
+
setComments(washi.getComments());
|
|
126
|
+
}, []);
|
|
127
|
+
return {
|
|
128
|
+
iframeRef,
|
|
129
|
+
mode,
|
|
130
|
+
setMode,
|
|
131
|
+
comments,
|
|
132
|
+
addComment,
|
|
133
|
+
updateComment,
|
|
134
|
+
deleteComment,
|
|
135
|
+
isReady,
|
|
136
|
+
error
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/WashiProvider.tsx
|
|
141
|
+
import {
|
|
142
|
+
createContext,
|
|
143
|
+
useContext,
|
|
144
|
+
useRef as useRef2,
|
|
145
|
+
useState as useState2,
|
|
146
|
+
useEffect as useEffect2,
|
|
147
|
+
useCallback as useCallback2,
|
|
148
|
+
useMemo
|
|
149
|
+
} from "react";
|
|
150
|
+
import {
|
|
151
|
+
Washi as Washi2
|
|
152
|
+
} from "@washi-ui/core";
|
|
153
|
+
import { jsx } from "react/jsx-runtime";
|
|
154
|
+
var WashiContext = createContext(null);
|
|
155
|
+
function WashiProvider({
|
|
156
|
+
adapter,
|
|
157
|
+
initialMode = "view",
|
|
158
|
+
mountOptions,
|
|
159
|
+
children
|
|
160
|
+
}) {
|
|
161
|
+
const washiRef = useRef2(null);
|
|
162
|
+
if (!washiRef.current) {
|
|
163
|
+
washiRef.current = new Washi2(adapter);
|
|
164
|
+
}
|
|
165
|
+
const iframeRef = useRef2(null);
|
|
166
|
+
const iframeLoadCleanupRef = useRef2(null);
|
|
167
|
+
const washiEventCleanupsRef = useRef2([]);
|
|
168
|
+
const pinPlacedCallbacksRef = useRef2(
|
|
169
|
+
/* @__PURE__ */ new Set()
|
|
170
|
+
);
|
|
171
|
+
const clickCallbacksRef = useRef2(/* @__PURE__ */ new Set());
|
|
172
|
+
const [mode, setModeState] = useState2(initialMode);
|
|
173
|
+
const [comments, setComments] = useState2([]);
|
|
174
|
+
const [isReady, setIsReady] = useState2(false);
|
|
175
|
+
const [error, setError] = useState2(null);
|
|
176
|
+
const [activeComment, setActiveCommentState] = useState2(null);
|
|
177
|
+
const [iframeElState, setIframeElState] = useState2(null);
|
|
178
|
+
useEffect2(() => {
|
|
179
|
+
return () => {
|
|
180
|
+
washiRef.current?.unmount();
|
|
181
|
+
};
|
|
182
|
+
}, []);
|
|
183
|
+
const refreshComments = useCallback2(() => {
|
|
184
|
+
const washi = washiRef.current;
|
|
185
|
+
if (washi) {
|
|
186
|
+
setComments(washi.getComments());
|
|
187
|
+
}
|
|
188
|
+
}, []);
|
|
189
|
+
const registerIframe = useCallback2(
|
|
190
|
+
(iframe) => {
|
|
191
|
+
iframeLoadCleanupRef.current?.();
|
|
192
|
+
iframeLoadCleanupRef.current = null;
|
|
193
|
+
washiEventCleanupsRef.current.forEach((fn) => fn());
|
|
194
|
+
washiEventCleanupsRef.current = [];
|
|
195
|
+
iframeRef.current = iframe;
|
|
196
|
+
setIframeElState(iframe);
|
|
197
|
+
if (!iframe) {
|
|
198
|
+
if (washiRef.current) {
|
|
199
|
+
washiRef.current.unmount();
|
|
200
|
+
setIsReady(false);
|
|
201
|
+
setComments([]);
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!washiRef.current) return;
|
|
206
|
+
const washi = washiRef.current;
|
|
207
|
+
const handleLoad = async () => {
|
|
208
|
+
try {
|
|
209
|
+
await washi.mount(iframe, mountOptions);
|
|
210
|
+
if (iframeRef.current !== iframe) return;
|
|
211
|
+
setComments(washi.getComments());
|
|
212
|
+
if (initialMode !== "view") {
|
|
213
|
+
washi.setMode(initialMode);
|
|
214
|
+
}
|
|
215
|
+
setIsReady(true);
|
|
216
|
+
setError(null);
|
|
217
|
+
washiEventCleanupsRef.current.push(
|
|
218
|
+
washi.on("pin:placed", (event) => {
|
|
219
|
+
pinPlacedCallbacksRef.current.forEach((cb) => cb(event));
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
washiEventCleanupsRef.current.push(
|
|
223
|
+
washi.on("comment:clicked", (comment) => {
|
|
224
|
+
clickCallbacksRef.current.forEach((cb) => cb(comment));
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
washiEventCleanupsRef.current.push(
|
|
228
|
+
washi.on("comment:updated", () => {
|
|
229
|
+
setComments(washi.getComments());
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
washiEventCleanupsRef.current.push(
|
|
233
|
+
washi.on("comment:deleted", () => {
|
|
234
|
+
setComments(washi.getComments());
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
239
|
+
setIsReady(false);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
if (iframe.contentDocument?.readyState === "complete") {
|
|
243
|
+
handleLoad();
|
|
244
|
+
} else {
|
|
245
|
+
iframe.addEventListener("load", handleLoad);
|
|
246
|
+
iframeLoadCleanupRef.current = () => iframe.removeEventListener("load", handleLoad);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
[mountOptions, initialMode]
|
|
250
|
+
);
|
|
251
|
+
const setMode = useCallback2((newMode) => {
|
|
252
|
+
const washi = washiRef.current;
|
|
253
|
+
if (!washi) return;
|
|
254
|
+
try {
|
|
255
|
+
washi.setMode(newMode);
|
|
256
|
+
setModeState(newMode);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
259
|
+
}
|
|
260
|
+
}, []);
|
|
261
|
+
const addComment = useCallback2(async (input) => {
|
|
262
|
+
const washi = washiRef.current;
|
|
263
|
+
if (!washi) throw new Error("Washi is not initialized");
|
|
264
|
+
const comment = await washi.addComment(input);
|
|
265
|
+
setComments(washi.getComments());
|
|
266
|
+
return comment;
|
|
267
|
+
}, []);
|
|
268
|
+
const updateComment = useCallback2(
|
|
269
|
+
async (id, updates) => {
|
|
270
|
+
const washi = washiRef.current;
|
|
271
|
+
if (!washi) throw new Error("Washi is not initialized");
|
|
272
|
+
await washi.updateComment(id, updates);
|
|
273
|
+
setComments(washi.getComments());
|
|
274
|
+
},
|
|
275
|
+
[]
|
|
276
|
+
);
|
|
277
|
+
const deleteComment = useCallback2(async (id) => {
|
|
278
|
+
const washi = washiRef.current;
|
|
279
|
+
if (!washi) throw new Error("Washi is not initialized");
|
|
280
|
+
await washi.deleteComment(id);
|
|
281
|
+
setComments(washi.getComments());
|
|
282
|
+
}, []);
|
|
283
|
+
const onPinPlaced = useCallback2(
|
|
284
|
+
(callback) => {
|
|
285
|
+
pinPlacedCallbacksRef.current.add(callback);
|
|
286
|
+
return () => {
|
|
287
|
+
pinPlacedCallbacksRef.current.delete(callback);
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
[]
|
|
291
|
+
);
|
|
292
|
+
const onCommentClick = useCallback2(
|
|
293
|
+
(callback) => {
|
|
294
|
+
clickCallbacksRef.current.add(callback);
|
|
295
|
+
return () => {
|
|
296
|
+
clickCallbacksRef.current.delete(callback);
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
[]
|
|
300
|
+
);
|
|
301
|
+
const setActivePin = useCallback2((commentId) => {
|
|
302
|
+
const washi = washiRef.current;
|
|
303
|
+
if (washi) {
|
|
304
|
+
washi.setActivePin(commentId);
|
|
305
|
+
}
|
|
306
|
+
}, []);
|
|
307
|
+
const getCommentIndex = useCallback2((commentId) => {
|
|
308
|
+
const washi = washiRef.current;
|
|
309
|
+
if (washi) {
|
|
310
|
+
return washi.getCommentIndex(commentId);
|
|
311
|
+
}
|
|
312
|
+
return -1;
|
|
313
|
+
}, []);
|
|
314
|
+
const setActiveComment = useCallback2((comment) => {
|
|
315
|
+
setActiveCommentState(comment);
|
|
316
|
+
const washi = washiRef.current;
|
|
317
|
+
if (washi) {
|
|
318
|
+
washi.setActivePin(comment?.id ?? null);
|
|
319
|
+
}
|
|
320
|
+
}, []);
|
|
321
|
+
const value = useMemo(
|
|
322
|
+
() => ({
|
|
323
|
+
washi: washiRef.current,
|
|
324
|
+
iframeEl: iframeElState,
|
|
325
|
+
mode,
|
|
326
|
+
setMode,
|
|
327
|
+
comments,
|
|
328
|
+
addComment,
|
|
329
|
+
updateComment,
|
|
330
|
+
deleteComment,
|
|
331
|
+
refreshComments,
|
|
332
|
+
isReady,
|
|
333
|
+
error,
|
|
334
|
+
registerIframe,
|
|
335
|
+
onPinPlaced,
|
|
336
|
+
onCommentClick,
|
|
337
|
+
setActivePin,
|
|
338
|
+
getCommentIndex,
|
|
339
|
+
activeComment,
|
|
340
|
+
setActiveComment
|
|
341
|
+
}),
|
|
342
|
+
[
|
|
343
|
+
iframeElState,
|
|
344
|
+
mode,
|
|
345
|
+
setMode,
|
|
346
|
+
comments,
|
|
347
|
+
addComment,
|
|
348
|
+
updateComment,
|
|
349
|
+
deleteComment,
|
|
350
|
+
refreshComments,
|
|
351
|
+
isReady,
|
|
352
|
+
error,
|
|
353
|
+
registerIframe,
|
|
354
|
+
onPinPlaced,
|
|
355
|
+
onCommentClick,
|
|
356
|
+
setActivePin,
|
|
357
|
+
getCommentIndex,
|
|
358
|
+
activeComment,
|
|
359
|
+
setActiveComment
|
|
360
|
+
]
|
|
361
|
+
);
|
|
362
|
+
return /* @__PURE__ */ jsx(WashiContext.Provider, { value, children });
|
|
363
|
+
}
|
|
364
|
+
function useWashiContext() {
|
|
365
|
+
const context = useContext(WashiContext);
|
|
366
|
+
if (!context) {
|
|
367
|
+
throw new Error("useWashiContext must be used within a WashiProvider");
|
|
368
|
+
}
|
|
369
|
+
return context;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/WashiFrame.tsx
|
|
373
|
+
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
374
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
375
|
+
function WashiFrame({ src, className, style, ...props }) {
|
|
376
|
+
const { registerIframe } = useWashiContext();
|
|
377
|
+
const iframeRef = useRef3(null);
|
|
378
|
+
useEffect3(() => {
|
|
379
|
+
if (iframeRef.current) {
|
|
380
|
+
registerIframe(iframeRef.current);
|
|
381
|
+
}
|
|
382
|
+
return () => {
|
|
383
|
+
registerIframe(null);
|
|
384
|
+
};
|
|
385
|
+
}, [registerIframe]);
|
|
386
|
+
return /* @__PURE__ */ jsx2(
|
|
387
|
+
"iframe",
|
|
388
|
+
{
|
|
389
|
+
ref: iframeRef,
|
|
390
|
+
src,
|
|
391
|
+
className,
|
|
392
|
+
style,
|
|
393
|
+
...props
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/CommentList.tsx
|
|
399
|
+
import React2 from "react";
|
|
400
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
401
|
+
function CommentList({
|
|
402
|
+
renderComment,
|
|
403
|
+
filter,
|
|
404
|
+
sort,
|
|
405
|
+
emptyState,
|
|
406
|
+
className,
|
|
407
|
+
style
|
|
408
|
+
}) {
|
|
409
|
+
const { comments, updateComment, deleteComment } = useWashiContext();
|
|
410
|
+
let displayComments = [...comments];
|
|
411
|
+
if (filter) {
|
|
412
|
+
displayComments = displayComments.filter(filter);
|
|
413
|
+
}
|
|
414
|
+
if (sort) {
|
|
415
|
+
displayComments.sort(sort);
|
|
416
|
+
}
|
|
417
|
+
if (displayComments.length === 0 && emptyState) {
|
|
418
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: emptyState });
|
|
419
|
+
}
|
|
420
|
+
return /* @__PURE__ */ jsx3("div", { className, style, children: displayComments.map((comment) => {
|
|
421
|
+
const actions = {
|
|
422
|
+
onResolve: async () => {
|
|
423
|
+
await updateComment(comment.id, { resolved: !comment.resolved });
|
|
424
|
+
},
|
|
425
|
+
onDelete: async () => {
|
|
426
|
+
await deleteComment(comment.id);
|
|
427
|
+
},
|
|
428
|
+
onUpdate: async (updates) => {
|
|
429
|
+
await updateComment(comment.id, updates);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return /* @__PURE__ */ jsx3(React2.Fragment, { children: renderComment(comment, actions) }, comment.id);
|
|
433
|
+
}) });
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/WashiToolBubble.tsx
|
|
437
|
+
import { useState as useState3 } from "react";
|
|
438
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
439
|
+
var positionStyles = {
|
|
440
|
+
"bottom-right": { bottom: 24, right: 24 },
|
|
441
|
+
"bottom-left": { bottom: 24, left: 24 },
|
|
442
|
+
"top-right": { top: 24, right: 24 },
|
|
443
|
+
"top-left": { top: 24, left: 24 }
|
|
444
|
+
};
|
|
445
|
+
function PencilIcon() {
|
|
446
|
+
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
447
|
+
/* @__PURE__ */ jsx4("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
448
|
+
/* @__PURE__ */ jsx4("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
449
|
+
] });
|
|
450
|
+
}
|
|
451
|
+
function ChatIcon() {
|
|
452
|
+
return /* @__PURE__ */ jsx4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
|
|
453
|
+
}
|
|
454
|
+
function WashiToolBubble({
|
|
455
|
+
position = "bottom-right",
|
|
456
|
+
sidebarOpen = false,
|
|
457
|
+
onSidebarToggle,
|
|
458
|
+
accentColor = "#667eea"
|
|
459
|
+
}) {
|
|
460
|
+
const { mode, setMode, isReady, comments } = useWashiContext();
|
|
461
|
+
const [hoveredBtn, setHoveredBtn] = useState3(null);
|
|
462
|
+
const isAnnotate = mode === "annotate";
|
|
463
|
+
const commentCount = comments.length;
|
|
464
|
+
const badgeLabel = commentCount > 99 ? "99+" : String(commentCount);
|
|
465
|
+
const pillStyle = {
|
|
466
|
+
position: "fixed",
|
|
467
|
+
...positionStyles[position],
|
|
468
|
+
zIndex: 9998,
|
|
469
|
+
display: "flex",
|
|
470
|
+
alignItems: "center",
|
|
471
|
+
gap: "4px",
|
|
472
|
+
padding: "6px",
|
|
473
|
+
borderRadius: "999px",
|
|
474
|
+
backgroundColor: "rgba(255,255,255,0.92)",
|
|
475
|
+
backdropFilter: "blur(8px)",
|
|
476
|
+
WebkitBackdropFilter: "blur(8px)",
|
|
477
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12), 0 1px 4px rgba(0,0,0,0.08)",
|
|
478
|
+
border: "1px solid rgba(0,0,0,0.06)"
|
|
479
|
+
};
|
|
480
|
+
const btnBase = {
|
|
481
|
+
position: "relative",
|
|
482
|
+
display: "flex",
|
|
483
|
+
alignItems: "center",
|
|
484
|
+
justifyContent: "center",
|
|
485
|
+
width: 40,
|
|
486
|
+
height: 40,
|
|
487
|
+
borderRadius: "50%",
|
|
488
|
+
border: "none",
|
|
489
|
+
cursor: "pointer",
|
|
490
|
+
transition: "background-color 0.15s, box-shadow 0.15s, color 0.15s",
|
|
491
|
+
outline: "none"
|
|
492
|
+
};
|
|
493
|
+
const pencilActive = isAnnotate;
|
|
494
|
+
const pencilDisabled = !isReady;
|
|
495
|
+
const pencilStyle = {
|
|
496
|
+
...btnBase,
|
|
497
|
+
backgroundColor: pencilActive ? accentColor : hoveredBtn === "pencil" ? "rgba(0,0,0,0.06)" : "transparent",
|
|
498
|
+
color: pencilActive ? "#fff" : "#374151",
|
|
499
|
+
opacity: pencilDisabled ? 0.5 : 1,
|
|
500
|
+
cursor: pencilDisabled ? "not-allowed" : "pointer",
|
|
501
|
+
boxShadow: pencilActive ? `0 0 0 3px ${accentColor}33` : "none"
|
|
502
|
+
};
|
|
503
|
+
const chatStyle = {
|
|
504
|
+
...btnBase,
|
|
505
|
+
backgroundColor: sidebarOpen ? "rgba(0,0,0,0.06)" : hoveredBtn === "chat" ? "rgba(0,0,0,0.06)" : "transparent",
|
|
506
|
+
color: "#374151"
|
|
507
|
+
};
|
|
508
|
+
const badgeStyle = {
|
|
509
|
+
position: "absolute",
|
|
510
|
+
top: 4,
|
|
511
|
+
right: 4,
|
|
512
|
+
minWidth: 16,
|
|
513
|
+
height: 16,
|
|
514
|
+
borderRadius: "999px",
|
|
515
|
+
backgroundColor: accentColor,
|
|
516
|
+
color: "#fff",
|
|
517
|
+
fontSize: "10px",
|
|
518
|
+
fontWeight: 700,
|
|
519
|
+
display: "flex",
|
|
520
|
+
alignItems: "center",
|
|
521
|
+
justifyContent: "center",
|
|
522
|
+
padding: "0 3px",
|
|
523
|
+
lineHeight: 1,
|
|
524
|
+
pointerEvents: "none"
|
|
525
|
+
};
|
|
526
|
+
return /* @__PURE__ */ jsxs("div", { style: pillStyle, children: [
|
|
527
|
+
/* @__PURE__ */ jsx4(
|
|
528
|
+
"button",
|
|
529
|
+
{
|
|
530
|
+
style: pencilStyle,
|
|
531
|
+
disabled: pencilDisabled,
|
|
532
|
+
title: isAnnotate ? "Exit annotate mode" : "Enter annotate mode",
|
|
533
|
+
onClick: () => !pencilDisabled && setMode(isAnnotate ? "view" : "annotate"),
|
|
534
|
+
onMouseEnter: () => setHoveredBtn("pencil"),
|
|
535
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
536
|
+
children: /* @__PURE__ */ jsx4(PencilIcon, {})
|
|
537
|
+
}
|
|
538
|
+
),
|
|
539
|
+
/* @__PURE__ */ jsxs(
|
|
540
|
+
"button",
|
|
541
|
+
{
|
|
542
|
+
style: chatStyle,
|
|
543
|
+
title: sidebarOpen ? "Close comments" : "Open comments",
|
|
544
|
+
onClick: onSidebarToggle,
|
|
545
|
+
onMouseEnter: () => setHoveredBtn("chat"),
|
|
546
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
547
|
+
children: [
|
|
548
|
+
/* @__PURE__ */ jsx4(ChatIcon, {}),
|
|
549
|
+
commentCount > 0 && /* @__PURE__ */ jsx4("span", { style: badgeStyle, children: badgeLabel })
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
] });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/WashiCommentsSidebar.tsx
|
|
557
|
+
import { useState as useState4, useEffect as useEffect4 } from "react";
|
|
558
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
559
|
+
var BUBBLE_EDGE = 24;
|
|
560
|
+
var BUBBLE_HEIGHT = 52;
|
|
561
|
+
var GAP = 10;
|
|
562
|
+
function getPanelBounds(position) {
|
|
563
|
+
const reserved = BUBBLE_EDGE + BUBBLE_HEIGHT + GAP;
|
|
564
|
+
switch (position) {
|
|
565
|
+
case "bottom-right":
|
|
566
|
+
return { top: 0, right: 0, bottom: reserved };
|
|
567
|
+
case "bottom-left":
|
|
568
|
+
return { top: 0, left: 0, bottom: reserved };
|
|
569
|
+
case "top-right":
|
|
570
|
+
return { top: reserved, right: 0, bottom: 0 };
|
|
571
|
+
case "top-left":
|
|
572
|
+
return { top: reserved, left: 0, bottom: 0 };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
var KEYFRAMES_ID = "__washi-panel-kf__";
|
|
576
|
+
if (typeof document !== "undefined" && !document.getElementById(KEYFRAMES_ID)) {
|
|
577
|
+
const style = document.createElement("style");
|
|
578
|
+
style.id = KEYFRAMES_ID;
|
|
579
|
+
style.textContent = `
|
|
580
|
+
@keyframes washi-panel-in-right { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
581
|
+
@keyframes washi-panel-out-right { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
|
|
582
|
+
@keyframes washi-panel-in-left { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
583
|
+
@keyframes washi-panel-out-left { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-100%); opacity: 0; } }
|
|
584
|
+
`;
|
|
585
|
+
document.head.appendChild(style);
|
|
586
|
+
}
|
|
587
|
+
var ANIM_DURATION = 240;
|
|
588
|
+
function WashiCommentsSidebar({
|
|
589
|
+
open,
|
|
590
|
+
onClose,
|
|
591
|
+
position = "bottom-right",
|
|
592
|
+
accentColor = "#667eea"
|
|
593
|
+
}) {
|
|
594
|
+
const { comments, updateComment, deleteComment } = useWashiContext();
|
|
595
|
+
const [visible, setVisible] = useState4(open);
|
|
596
|
+
const [phase, setPhase] = useState4(open ? "in" : "out");
|
|
597
|
+
useEffect4(() => {
|
|
598
|
+
if (open) {
|
|
599
|
+
setVisible(true);
|
|
600
|
+
const raf = requestAnimationFrame(() => setPhase("in"));
|
|
601
|
+
return () => cancelAnimationFrame(raf);
|
|
602
|
+
} else {
|
|
603
|
+
setPhase("out");
|
|
604
|
+
const t = setTimeout(() => setVisible(false), ANIM_DURATION);
|
|
605
|
+
return () => clearTimeout(t);
|
|
606
|
+
}
|
|
607
|
+
}, [open]);
|
|
608
|
+
if (!visible) return null;
|
|
609
|
+
const sortedComments = [...comments].sort((a, b) => a.createdAt - b.createdAt);
|
|
610
|
+
const side = position.endsWith("right") ? "right" : "left";
|
|
611
|
+
const animName = phase === "in" ? `washi-panel-in-${side}` : `washi-panel-out-${side}`;
|
|
612
|
+
const panelStyle = {
|
|
613
|
+
position: "fixed",
|
|
614
|
+
...getPanelBounds(position),
|
|
615
|
+
width: 320,
|
|
616
|
+
zIndex: 9999,
|
|
617
|
+
display: "flex",
|
|
618
|
+
flexDirection: "column",
|
|
619
|
+
backgroundColor: "#f9fafb",
|
|
620
|
+
boxShadow: side === "right" ? "-4px 0 24px rgba(0,0,0,0.10)" : "4px 0 24px rgba(0,0,0,0.10)",
|
|
621
|
+
borderLeft: side === "right" ? "1px solid #e5e7eb" : "none",
|
|
622
|
+
borderRight: side === "left" ? "1px solid #e5e7eb" : "none",
|
|
623
|
+
animation: `${animName} ${ANIM_DURATION}ms cubic-bezier(0.4,0,0.2,1) forwards`,
|
|
624
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
625
|
+
};
|
|
626
|
+
return /* @__PURE__ */ jsxs2("div", { style: panelStyle, children: [
|
|
627
|
+
/* @__PURE__ */ jsxs2("div", { style: {
|
|
628
|
+
display: "flex",
|
|
629
|
+
alignItems: "center",
|
|
630
|
+
justifyContent: "space-between",
|
|
631
|
+
padding: "16px 20px",
|
|
632
|
+
borderBottom: "1px solid #e5e7eb",
|
|
633
|
+
backgroundColor: "#fff",
|
|
634
|
+
flexShrink: 0
|
|
635
|
+
}, children: [
|
|
636
|
+
/* @__PURE__ */ jsxs2("span", { style: { fontSize: "1rem", fontWeight: 600, color: "#1f2937" }, children: [
|
|
637
|
+
"Comments (",
|
|
638
|
+
comments.length,
|
|
639
|
+
")"
|
|
640
|
+
] }),
|
|
641
|
+
/* @__PURE__ */ jsx5(
|
|
642
|
+
"button",
|
|
643
|
+
{
|
|
644
|
+
onClick: onClose,
|
|
645
|
+
title: "Close",
|
|
646
|
+
style: {
|
|
647
|
+
display: "flex",
|
|
648
|
+
alignItems: "center",
|
|
649
|
+
justifyContent: "center",
|
|
650
|
+
width: 30,
|
|
651
|
+
height: 30,
|
|
652
|
+
border: "none",
|
|
653
|
+
borderRadius: 6,
|
|
654
|
+
backgroundColor: "transparent",
|
|
655
|
+
cursor: "pointer",
|
|
656
|
+
color: "#9ca3af",
|
|
657
|
+
fontSize: 14,
|
|
658
|
+
padding: 0
|
|
659
|
+
},
|
|
660
|
+
children: "\u2715"
|
|
661
|
+
}
|
|
662
|
+
)
|
|
663
|
+
] }),
|
|
664
|
+
/* @__PURE__ */ jsx5("div", { style: { flex: 1, overflowY: "auto", padding: "12px" }, children: sortedComments.length === 0 ? /* @__PURE__ */ jsxs2("div", { style: {
|
|
665
|
+
textAlign: "center",
|
|
666
|
+
padding: "40px 20px",
|
|
667
|
+
color: "#9ca3af",
|
|
668
|
+
fontSize: "0.875rem"
|
|
669
|
+
}, children: [
|
|
670
|
+
/* @__PURE__ */ jsx5("p", { style: { marginBottom: 8 }, children: "No comments yet." }),
|
|
671
|
+
/* @__PURE__ */ jsx5("p", { children: "Use the annotate tool to add one." })
|
|
672
|
+
] }) : sortedComments.map((comment, index) => {
|
|
673
|
+
const color = comment.color || accentColor;
|
|
674
|
+
const borderColor = comment.resolved ? "#10b981" : color;
|
|
675
|
+
const badgeBg = comment.resolved ? "#10b981" : color;
|
|
676
|
+
return /* @__PURE__ */ jsxs2(
|
|
677
|
+
"div",
|
|
678
|
+
{
|
|
679
|
+
style: {
|
|
680
|
+
backgroundColor: "#fff",
|
|
681
|
+
border: "1px solid #e5e7eb",
|
|
682
|
+
borderLeft: `3px solid ${borderColor}`,
|
|
683
|
+
borderRadius: 8,
|
|
684
|
+
padding: "12px",
|
|
685
|
+
marginBottom: 10,
|
|
686
|
+
opacity: comment.resolved ? 0.7 : 1
|
|
687
|
+
},
|
|
688
|
+
children: [
|
|
689
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 8 }, children: [
|
|
690
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center" }, children: [
|
|
691
|
+
/* @__PURE__ */ jsx5("span", { style: {
|
|
692
|
+
display: "inline-flex",
|
|
693
|
+
alignItems: "center",
|
|
694
|
+
justifyContent: "center",
|
|
695
|
+
width: 20,
|
|
696
|
+
height: 20,
|
|
697
|
+
borderRadius: "50%",
|
|
698
|
+
backgroundColor: badgeBg,
|
|
699
|
+
color: "#fff",
|
|
700
|
+
fontSize: "0.7rem",
|
|
701
|
+
fontWeight: 700,
|
|
702
|
+
marginRight: 8,
|
|
703
|
+
flexShrink: 0
|
|
704
|
+
}, children: comment.resolved ? "\u2713" : index + 1 }),
|
|
705
|
+
/* @__PURE__ */ jsxs2("span", { style: { fontSize: "0.75rem", color: "#6b7280" }, children: [
|
|
706
|
+
"(",
|
|
707
|
+
comment.x.toFixed(1),
|
|
708
|
+
"%, ",
|
|
709
|
+
comment.y.toFixed(1),
|
|
710
|
+
"%)"
|
|
711
|
+
] })
|
|
712
|
+
] }),
|
|
713
|
+
comment.resolved && /* @__PURE__ */ jsx5("span", { style: { fontSize: "0.75rem", color: "#059669", fontWeight: 500 }, children: "Resolved" })
|
|
714
|
+
] }),
|
|
715
|
+
/* @__PURE__ */ jsx5("p", { style: { fontSize: "0.875rem", color: "#374151", marginBottom: 10, lineHeight: 1.5 }, children: comment.text }),
|
|
716
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
|
|
717
|
+
/* @__PURE__ */ jsx5(
|
|
718
|
+
"button",
|
|
719
|
+
{
|
|
720
|
+
style: {
|
|
721
|
+
padding: "4px 10px",
|
|
722
|
+
fontSize: "0.75rem",
|
|
723
|
+
border: "none",
|
|
724
|
+
borderRadius: 4,
|
|
725
|
+
cursor: "pointer",
|
|
726
|
+
backgroundColor: "#e0e7ff",
|
|
727
|
+
color: "#4f46e5"
|
|
728
|
+
},
|
|
729
|
+
onClick: () => updateComment(comment.id, { resolved: !comment.resolved }),
|
|
730
|
+
children: comment.resolved ? "Unresolve" : "Resolve"
|
|
731
|
+
}
|
|
732
|
+
),
|
|
733
|
+
/* @__PURE__ */ jsx5(
|
|
734
|
+
"button",
|
|
735
|
+
{
|
|
736
|
+
style: {
|
|
737
|
+
padding: "4px 10px",
|
|
738
|
+
fontSize: "0.75rem",
|
|
739
|
+
border: "none",
|
|
740
|
+
borderRadius: 4,
|
|
741
|
+
cursor: "pointer",
|
|
742
|
+
backgroundColor: "#fee2e2",
|
|
743
|
+
color: "#dc2626"
|
|
744
|
+
},
|
|
745
|
+
onClick: () => deleteComment(comment.id),
|
|
746
|
+
children: "Delete"
|
|
747
|
+
}
|
|
748
|
+
)
|
|
749
|
+
] })
|
|
750
|
+
]
|
|
751
|
+
},
|
|
752
|
+
comment.id
|
|
753
|
+
);
|
|
754
|
+
}) })
|
|
755
|
+
] });
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/WashiPinDialog.tsx
|
|
759
|
+
import { useState as useState5, useEffect as useEffect5 } from "react";
|
|
760
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
761
|
+
function WashiPinDialog({ accentColor = "#667eea", onComment }) {
|
|
762
|
+
const { onPinPlaced, addComment, setMode, iframeEl } = useWashiContext();
|
|
763
|
+
const [pending, setPending] = useState5(null);
|
|
764
|
+
const [text, setText] = useState5("");
|
|
765
|
+
useEffect5(() => {
|
|
766
|
+
return onPinPlaced((event) => {
|
|
767
|
+
const containerW = iframeEl?.clientWidth ?? window.innerWidth;
|
|
768
|
+
const containerH = iframeEl?.clientHeight ?? window.innerHeight;
|
|
769
|
+
setText("");
|
|
770
|
+
setPending({
|
|
771
|
+
x: event.x,
|
|
772
|
+
y: event.y,
|
|
773
|
+
pixelX: event.x / 100 * containerW,
|
|
774
|
+
pixelY: event.y / 100 * containerH,
|
|
775
|
+
containerW,
|
|
776
|
+
containerH
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
}, [onPinPlaced, iframeEl]);
|
|
780
|
+
if (!pending) return null;
|
|
781
|
+
const handleSubmit = async () => {
|
|
782
|
+
if (!text.trim()) return;
|
|
783
|
+
const comment = await addComment({ x: pending.x, y: pending.y, text: text.trim(), color: accentColor });
|
|
784
|
+
setPending(null);
|
|
785
|
+
setMode("view");
|
|
786
|
+
onComment?.(comment);
|
|
787
|
+
};
|
|
788
|
+
const handleCancel = () => setPending(null);
|
|
789
|
+
const handleKeyDown = (e) => {
|
|
790
|
+
if (e.key === "Enter" && e.metaKey) handleSubmit();
|
|
791
|
+
if (e.key === "Escape") handleCancel();
|
|
792
|
+
};
|
|
793
|
+
const POPOVER_WIDTH = 280;
|
|
794
|
+
const left = Math.min(
|
|
795
|
+
Math.max(pending.pixelX - POPOVER_WIDTH / 2, 8),
|
|
796
|
+
pending.containerW - POPOVER_WIDTH - 8
|
|
797
|
+
);
|
|
798
|
+
const showAbove = pending.pixelY > pending.containerH * 0.65;
|
|
799
|
+
const top = showAbove ? pending.pixelY - 172 : pending.pixelY + 16;
|
|
800
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
801
|
+
/* @__PURE__ */ jsx6("div", { style: { position: "fixed", inset: 0, zIndex: 9999 }, onClick: handleCancel }),
|
|
802
|
+
/* @__PURE__ */ jsxs3(
|
|
803
|
+
"div",
|
|
804
|
+
{
|
|
805
|
+
style: {
|
|
806
|
+
position: "fixed",
|
|
807
|
+
left,
|
|
808
|
+
top,
|
|
809
|
+
width: POPOVER_WIDTH,
|
|
810
|
+
zIndex: 1e4,
|
|
811
|
+
backgroundColor: "#fff",
|
|
812
|
+
borderRadius: 12,
|
|
813
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.10)",
|
|
814
|
+
border: "1px solid rgba(0,0,0,0.06)",
|
|
815
|
+
padding: "14px 16px",
|
|
816
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
817
|
+
},
|
|
818
|
+
children: [
|
|
819
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10 }, children: [
|
|
820
|
+
/* @__PURE__ */ jsx6("span", { style: { fontSize: "0.875rem", fontWeight: 600, color: "#1f2937" }, children: "Add comment" }),
|
|
821
|
+
/* @__PURE__ */ jsx6(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
onClick: handleCancel,
|
|
825
|
+
style: {
|
|
826
|
+
border: "none",
|
|
827
|
+
background: "none",
|
|
828
|
+
cursor: "pointer",
|
|
829
|
+
color: "#9ca3af",
|
|
830
|
+
fontSize: 14,
|
|
831
|
+
padding: "2px 4px",
|
|
832
|
+
borderRadius: 4
|
|
833
|
+
},
|
|
834
|
+
children: "\u2715"
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
] }),
|
|
838
|
+
/* @__PURE__ */ jsx6(
|
|
839
|
+
"textarea",
|
|
840
|
+
{
|
|
841
|
+
style: {
|
|
842
|
+
width: "100%",
|
|
843
|
+
height: 80,
|
|
844
|
+
padding: "8px 10px",
|
|
845
|
+
border: "1px solid #e5e7eb",
|
|
846
|
+
borderRadius: 8,
|
|
847
|
+
fontSize: "0.8125rem",
|
|
848
|
+
resize: "none",
|
|
849
|
+
boxSizing: "border-box",
|
|
850
|
+
lineHeight: 1.5,
|
|
851
|
+
fontFamily: "inherit",
|
|
852
|
+
color: "#374151",
|
|
853
|
+
outline: "none"
|
|
854
|
+
},
|
|
855
|
+
placeholder: "Leave a comment...",
|
|
856
|
+
value: text,
|
|
857
|
+
onChange: (e) => setText(e.target.value),
|
|
858
|
+
onKeyDown: handleKeyDown,
|
|
859
|
+
autoFocus: true
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 10 }, children: [
|
|
863
|
+
/* @__PURE__ */ jsx6(
|
|
864
|
+
"button",
|
|
865
|
+
{
|
|
866
|
+
style: {
|
|
867
|
+
padding: "6px 12px",
|
|
868
|
+
fontSize: "0.8125rem",
|
|
869
|
+
border: "1px solid #e5e7eb",
|
|
870
|
+
borderRadius: 6,
|
|
871
|
+
backgroundColor: "#fff",
|
|
872
|
+
cursor: "pointer",
|
|
873
|
+
color: "#6b7280"
|
|
874
|
+
},
|
|
875
|
+
onClick: handleCancel,
|
|
876
|
+
children: "Cancel"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ jsx6(
|
|
880
|
+
"button",
|
|
881
|
+
{
|
|
882
|
+
style: {
|
|
883
|
+
padding: "6px 12px",
|
|
884
|
+
fontSize: "0.8125rem",
|
|
885
|
+
border: "none",
|
|
886
|
+
borderRadius: 6,
|
|
887
|
+
backgroundColor: accentColor,
|
|
888
|
+
color: "#fff",
|
|
889
|
+
cursor: text.trim() ? "pointer" : "not-allowed",
|
|
890
|
+
opacity: text.trim() ? 1 : 0.5
|
|
891
|
+
},
|
|
892
|
+
onClick: handleSubmit,
|
|
893
|
+
disabled: !text.trim(),
|
|
894
|
+
children: "Add"
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] })
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
)
|
|
901
|
+
] });
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/WashiUI.tsx
|
|
905
|
+
import { useState as useState6 } from "react";
|
|
906
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
907
|
+
var SPINNER_ID = "__washi-ui-spinner__";
|
|
908
|
+
if (typeof document !== "undefined" && !document.getElementById(SPINNER_ID)) {
|
|
909
|
+
const style = document.createElement("style");
|
|
910
|
+
style.id = SPINNER_ID;
|
|
911
|
+
style.textContent = `@keyframes __washi-spin { to { transform: rotate(360deg); } }`;
|
|
912
|
+
document.head.appendChild(style);
|
|
913
|
+
}
|
|
914
|
+
function WashiUI({
|
|
915
|
+
position = "bottom-right",
|
|
916
|
+
accentColor = "#667eea",
|
|
917
|
+
showLoader = true
|
|
918
|
+
}) {
|
|
919
|
+
const { isReady } = useWashiContext();
|
|
920
|
+
const [sidebarOpen, setSidebarOpen] = useState6(false);
|
|
921
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
922
|
+
showLoader && !isReady && /* @__PURE__ */ jsx7(
|
|
923
|
+
"div",
|
|
924
|
+
{
|
|
925
|
+
style: {
|
|
926
|
+
position: "fixed",
|
|
927
|
+
inset: 0,
|
|
928
|
+
zIndex: 100,
|
|
929
|
+
display: "flex",
|
|
930
|
+
alignItems: "center",
|
|
931
|
+
justifyContent: "center",
|
|
932
|
+
backgroundColor: "rgba(255,255,255,0.9)"
|
|
933
|
+
},
|
|
934
|
+
children: /* @__PURE__ */ jsx7(
|
|
935
|
+
"div",
|
|
936
|
+
{
|
|
937
|
+
style: {
|
|
938
|
+
width: 40,
|
|
939
|
+
height: 40,
|
|
940
|
+
borderRadius: "50%",
|
|
941
|
+
border: "3px solid #e5e7eb",
|
|
942
|
+
borderTopColor: accentColor,
|
|
943
|
+
animation: "__washi-spin 1s linear infinite"
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
),
|
|
949
|
+
/* @__PURE__ */ jsx7(
|
|
950
|
+
WashiToolBubble,
|
|
951
|
+
{
|
|
952
|
+
position,
|
|
953
|
+
accentColor,
|
|
954
|
+
sidebarOpen,
|
|
955
|
+
onSidebarToggle: () => setSidebarOpen((o) => !o)
|
|
956
|
+
}
|
|
957
|
+
),
|
|
958
|
+
/* @__PURE__ */ jsx7(
|
|
959
|
+
WashiCommentsSidebar,
|
|
960
|
+
{
|
|
961
|
+
open: sidebarOpen,
|
|
962
|
+
onClose: () => setSidebarOpen(false),
|
|
963
|
+
position,
|
|
964
|
+
accentColor
|
|
965
|
+
}
|
|
966
|
+
),
|
|
967
|
+
/* @__PURE__ */ jsx7(WashiPinDialog, { accentColor })
|
|
968
|
+
] });
|
|
969
|
+
}
|
|
970
|
+
export {
|
|
971
|
+
CommentList,
|
|
972
|
+
WashiCommentsSidebar,
|
|
973
|
+
WashiFrame,
|
|
974
|
+
WashiPinDialog,
|
|
975
|
+
WashiProvider,
|
|
976
|
+
WashiToolBubble,
|
|
977
|
+
WashiUI,
|
|
978
|
+
useWashi,
|
|
979
|
+
useWashiContext
|
|
980
|
+
};
|
|
981
|
+
//# sourceMappingURL=index.mjs.map
|