@useclickly/react 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +185 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +185 -89
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var react = require('react');
|
|
|
5
5
|
var client = require('react-dom/client');
|
|
6
6
|
var core = require('@useclickly/core');
|
|
7
7
|
var zustand = require('zustand');
|
|
8
|
+
var middleware = require('zustand/middleware');
|
|
8
9
|
var shallow = require('zustand/react/shallow');
|
|
9
10
|
var jsxRuntime = require('react/jsx-runtime');
|
|
10
11
|
var nanoid = require('nanoid');
|
|
@@ -146,52 +147,84 @@ async function loadDirHandle() {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
// src/state/annotations.ts
|
|
149
|
-
var
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
150
|
+
var STORAGE_KEY = "clickly:annotations";
|
|
151
|
+
var useAnnotations = zustand.create()(
|
|
152
|
+
middleware.persist(
|
|
153
|
+
(set, get) => ({
|
|
154
|
+
byId: {},
|
|
155
|
+
order: [],
|
|
156
|
+
add: (a) => {
|
|
157
|
+
if (a.strokes && a.strokes.length > 0) void saveStrokes(a.id, a.strokes);
|
|
158
|
+
set((s) => ({
|
|
159
|
+
byId: { ...s.byId, [a.id]: a },
|
|
160
|
+
order: s.order.includes(a.id) ? s.order : [...s.order, a.id]
|
|
161
|
+
}));
|
|
162
|
+
},
|
|
163
|
+
remove: (id) => {
|
|
164
|
+
void deleteStrokes(id);
|
|
165
|
+
set((s) => {
|
|
166
|
+
if (!(id in s.byId)) return s;
|
|
167
|
+
const next = { ...s.byId };
|
|
168
|
+
delete next[id];
|
|
169
|
+
return { byId: next, order: s.order.filter((x) => x !== id) };
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
update: (id, patch) => {
|
|
173
|
+
if (patch.strokes) void saveStrokes(id, patch.strokes);
|
|
174
|
+
set((s) => {
|
|
175
|
+
const cur = s.byId[id];
|
|
176
|
+
if (!cur) return s;
|
|
177
|
+
return { byId: { ...s.byId, [id]: { ...cur, ...patch } } };
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
clear: () => {
|
|
181
|
+
set({ byId: {}, order: [] });
|
|
182
|
+
},
|
|
183
|
+
list: () => {
|
|
184
|
+
const { byId, order } = get();
|
|
185
|
+
return order.map((id) => byId[id]).filter(Boolean);
|
|
186
|
+
},
|
|
187
|
+
hydrateStrokes: (strokesById) => set((s) => {
|
|
188
|
+
let touched = false;
|
|
189
|
+
const next = { ...s.byId };
|
|
190
|
+
for (const [id, strokes] of Object.entries(strokesById)) {
|
|
191
|
+
const cur = next[id];
|
|
192
|
+
if (!cur) continue;
|
|
193
|
+
next[id] = { ...cur, strokes };
|
|
194
|
+
touched = true;
|
|
195
|
+
}
|
|
196
|
+
return touched ? { byId: next } : s;
|
|
197
|
+
})
|
|
198
|
+
}),
|
|
199
|
+
{
|
|
200
|
+
name: STORAGE_KEY,
|
|
201
|
+
storage: middleware.createJSONStorage(() => {
|
|
202
|
+
if (typeof localStorage !== "undefined") return localStorage;
|
|
203
|
+
return {
|
|
204
|
+
getItem: () => null,
|
|
205
|
+
setItem: () => void 0,
|
|
206
|
+
removeItem: () => void 0
|
|
207
|
+
};
|
|
208
|
+
}),
|
|
209
|
+
// Skip strokes — they live in IndexedDB and are reattached by
|
|
210
|
+
// hydrateStrokes(). Persisting them in JSON would blow the
|
|
211
|
+
// localStorage quota (5MB) on a single freehand drawing.
|
|
212
|
+
partialize: (state) => ({
|
|
213
|
+
byId: Object.fromEntries(
|
|
214
|
+
Object.entries(state.byId).map(([id, ann]) => {
|
|
215
|
+
const { strokes: _strokes, ...rest } = ann;
|
|
216
|
+
return [id, rest];
|
|
217
|
+
})
|
|
218
|
+
),
|
|
219
|
+
order: state.order
|
|
220
|
+
}),
|
|
221
|
+
// Bump this if the persisted shape changes; old payloads are
|
|
222
|
+
// ignored and the store starts empty rather than crashing on
|
|
223
|
+
// a mismatched schema.
|
|
224
|
+
version: 1
|
|
191
225
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}));
|
|
226
|
+
)
|
|
227
|
+
);
|
|
195
228
|
function useAnnotationsList() {
|
|
196
229
|
return useAnnotations(
|
|
197
230
|
shallow.useShallow((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
|
|
@@ -207,11 +240,11 @@ var DEFAULTS = {
|
|
|
207
240
|
mcpEndpoint: "http://localhost:4747",
|
|
208
241
|
mcpSessionId: null
|
|
209
242
|
};
|
|
210
|
-
var
|
|
243
|
+
var STORAGE_KEY2 = "clickly:settings";
|
|
211
244
|
function load() {
|
|
212
245
|
if (typeof localStorage === "undefined") return DEFAULTS;
|
|
213
246
|
try {
|
|
214
|
-
const raw = localStorage.getItem(
|
|
247
|
+
const raw = localStorage.getItem(STORAGE_KEY2);
|
|
215
248
|
if (!raw) return DEFAULTS;
|
|
216
249
|
const parsed = JSON.parse(raw);
|
|
217
250
|
return { ...DEFAULTS, ...parsed };
|
|
@@ -219,10 +252,10 @@ function load() {
|
|
|
219
252
|
return DEFAULTS;
|
|
220
253
|
}
|
|
221
254
|
}
|
|
222
|
-
function
|
|
255
|
+
function persist2(s) {
|
|
223
256
|
if (typeof localStorage === "undefined") return;
|
|
224
257
|
try {
|
|
225
|
-
localStorage.setItem(
|
|
258
|
+
localStorage.setItem(STORAGE_KEY2, JSON.stringify(s));
|
|
226
259
|
} catch {
|
|
227
260
|
}
|
|
228
261
|
}
|
|
@@ -230,11 +263,11 @@ var useSettings = zustand.create((set) => ({
|
|
|
230
263
|
...load(),
|
|
231
264
|
set: (patch) => set((cur) => {
|
|
232
265
|
const next = { ...cur, ...patch };
|
|
233
|
-
|
|
266
|
+
persist2(next);
|
|
234
267
|
return next;
|
|
235
268
|
}),
|
|
236
269
|
reset: () => {
|
|
237
|
-
|
|
270
|
+
persist2(DEFAULTS);
|
|
238
271
|
set(DEFAULTS);
|
|
239
272
|
}
|
|
240
273
|
}));
|
|
@@ -1368,6 +1401,11 @@ function downloadViaAnchor(dataUrl, filename) {
|
|
|
1368
1401
|
return false;
|
|
1369
1402
|
}
|
|
1370
1403
|
}
|
|
1404
|
+
var PANEL_W2 = 336;
|
|
1405
|
+
var PANEL_H2 = 420;
|
|
1406
|
+
var PANEL_GAP2 = 12;
|
|
1407
|
+
var VIEWPORT_PAD2 = 8;
|
|
1408
|
+
var TOOLBAR_H2 = 46;
|
|
1371
1409
|
function AnnotationList({ anchor, width, onClose }) {
|
|
1372
1410
|
const items = useAnnotationsList();
|
|
1373
1411
|
const remove = useAnnotations((s) => s.remove);
|
|
@@ -1381,9 +1419,17 @@ function AnnotationList({ anchor, width, onClose }) {
|
|
|
1381
1419
|
window.addEventListener("pointerdown", onDown, true);
|
|
1382
1420
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
1383
1421
|
}, [onClose]);
|
|
1384
|
-
const
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1422
|
+
const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
|
|
1423
|
+
const vh = typeof window !== "undefined" ? window.innerHeight : 768;
|
|
1424
|
+
const spaceAbove = anchor.y - VIEWPORT_PAD2;
|
|
1425
|
+
const spaceBelow = vh - anchor.y - TOOLBAR_H2 - VIEWPORT_PAD2;
|
|
1426
|
+
const placeAbove = spaceAbove >= PANEL_H2 + PANEL_GAP2 || spaceAbove >= spaceBelow;
|
|
1427
|
+
const top = placeAbove ? Math.max(VIEWPORT_PAD2, anchor.y - PANEL_H2 - PANEL_GAP2) : Math.min(vh - PANEL_H2 - VIEWPORT_PAD2, anchor.y + TOOLBAR_H2 + PANEL_GAP2);
|
|
1428
|
+
const desiredLeft = anchor.x + width - PANEL_W2;
|
|
1429
|
+
const left = Math.min(
|
|
1430
|
+
vw - PANEL_W2 - VIEWPORT_PAD2,
|
|
1431
|
+
Math.max(VIEWPORT_PAD2, desiredLeft)
|
|
1432
|
+
);
|
|
1387
1433
|
const [exporting, setExporting] = react.useState(false);
|
|
1388
1434
|
const update = useAnnotations((s) => s.update);
|
|
1389
1435
|
const exportComposite = async () => {
|
|
@@ -1407,36 +1453,55 @@ function AnnotationList({ anchor, width, onClose }) {
|
|
|
1407
1453
|
setExporting(false);
|
|
1408
1454
|
}
|
|
1409
1455
|
};
|
|
1410
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1456
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1457
|
+
"div",
|
|
1458
|
+
{
|
|
1459
|
+
ref,
|
|
1460
|
+
className: "clickly-list",
|
|
1461
|
+
style: { left, top, width: PANEL_W2, height: PANEL_H2 },
|
|
1462
|
+
children: [
|
|
1463
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "list-header", children: [
|
|
1464
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "list-title", children: "Annotations" }),
|
|
1465
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "list-count", children: items.length }),
|
|
1466
|
+
items.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1467
|
+
"button",
|
|
1468
|
+
{
|
|
1469
|
+
className: "list-action-btn",
|
|
1470
|
+
onClick: exportComposite,
|
|
1471
|
+
title: "Export all screenshots as a numbered image strip",
|
|
1472
|
+
disabled: exporting,
|
|
1473
|
+
style: { marginLeft: "auto" },
|
|
1474
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(IconDownload, {})
|
|
1475
|
+
}
|
|
1476
|
+
),
|
|
1477
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1478
|
+
"button",
|
|
1479
|
+
{
|
|
1480
|
+
className: "list-action-btn",
|
|
1481
|
+
onClick: onClose,
|
|
1482
|
+
"aria-label": "Close annotation list",
|
|
1483
|
+
title: "Close",
|
|
1484
|
+
style: items.length > 0 ? void 0 : { marginLeft: "auto" },
|
|
1485
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(IconClose, {})
|
|
1486
|
+
}
|
|
1487
|
+
)
|
|
1488
|
+
] }),
|
|
1489
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "list-scroll", children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "list-empty", children: "No annotations yet." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "list-items", children: items.map((a, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1490
|
+
AnnotationCard,
|
|
1491
|
+
{
|
|
1492
|
+
annotation: a,
|
|
1493
|
+
index: i + 1,
|
|
1494
|
+
outputDetail,
|
|
1495
|
+
onRemove: () => remove(a.id),
|
|
1496
|
+
onCaptured: (data) => update(a.id, {
|
|
1497
|
+
screenshot: { mimeType: "image/jpeg", dataUrl: data, width: 0, height: 0 }
|
|
1498
|
+
})
|
|
1499
|
+
},
|
|
1500
|
+
a.id
|
|
1501
|
+
)) }) })
|
|
1502
|
+
]
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1440
1505
|
}
|
|
1441
1506
|
function AnnotationCard({
|
|
1442
1507
|
annotation: a,
|
|
@@ -5404,9 +5469,11 @@ var REACT_UI_CSS = `
|
|
|
5404
5469
|
/* \u2500\u2500\u2500 Annotation list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5405
5470
|
|
|
5406
5471
|
.clickly-list {
|
|
5472
|
+
/* Fixed-size frame. Width/height set inline by AnnotationList so the
|
|
5473
|
+
values stay in one place (TS source of truth). Header is pinned;
|
|
5474
|
+
.list-scroll fills the remainder and scrolls internally \u2014 matches
|
|
5475
|
+
the SettingsPopover layout. */
|
|
5407
5476
|
position: fixed;
|
|
5408
|
-
width: 320px;
|
|
5409
|
-
max-height: 55vh;
|
|
5410
5477
|
background: #fff;
|
|
5411
5478
|
border-radius: 14px;
|
|
5412
5479
|
box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
|
|
@@ -5420,6 +5487,34 @@ var REACT_UI_CSS = `
|
|
|
5420
5487
|
overflow: hidden;
|
|
5421
5488
|
}
|
|
5422
5489
|
|
|
5490
|
+
/* Scrollable body \u2014 mirrors .settings-scroll. min-height:0 is the
|
|
5491
|
+
classic fix that lets a flex child shrink small enough to overflow
|
|
5492
|
+
inside its parent (otherwise the auto min-content size pushes the
|
|
5493
|
+
layout taller than the frame, defeating overflow:auto). */
|
|
5494
|
+
.list-scroll {
|
|
5495
|
+
flex: 1 1 auto;
|
|
5496
|
+
min-height: 0;
|
|
5497
|
+
overflow-y: auto;
|
|
5498
|
+
overscroll-behavior: contain;
|
|
5499
|
+
scrollbar-width: thin;
|
|
5500
|
+
scrollbar-color: rgba(15,23,42,0.20) transparent;
|
|
5501
|
+
}
|
|
5502
|
+
.list-scroll::-webkit-scrollbar { width: 6px; }
|
|
5503
|
+
.list-scroll::-webkit-scrollbar-thumb {
|
|
5504
|
+
background: rgba(15,23,42,0.18);
|
|
5505
|
+
border-radius: 3px;
|
|
5506
|
+
}
|
|
5507
|
+
.list-scroll::-webkit-scrollbar-thumb:hover { background: rgba(15,23,42,0.32); }
|
|
5508
|
+
:host([data-clickly-theme="dark"]) .list-scroll {
|
|
5509
|
+
scrollbar-color: rgba(255,255,255,0.18) transparent;
|
|
5510
|
+
}
|
|
5511
|
+
:host([data-clickly-theme="dark"]) .list-scroll::-webkit-scrollbar-thumb {
|
|
5512
|
+
background: rgba(255,255,255,0.18);
|
|
5513
|
+
}
|
|
5514
|
+
:host([data-clickly-theme="dark"]) .list-scroll::-webkit-scrollbar-thumb:hover {
|
|
5515
|
+
background: rgba(255,255,255,0.32);
|
|
5516
|
+
}
|
|
5517
|
+
|
|
5423
5518
|
.list-header {
|
|
5424
5519
|
display: flex;
|
|
5425
5520
|
align-items: center;
|
|
@@ -5451,9 +5546,10 @@ var REACT_UI_CSS = `
|
|
|
5451
5546
|
}
|
|
5452
5547
|
|
|
5453
5548
|
.list-items {
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5549
|
+
/* Cards container \u2014 scrolling moved up to .list-scroll so the
|
|
5550
|
+
empty state shares the same scroll region. */
|
|
5551
|
+
display: flex;
|
|
5552
|
+
flex-direction: column;
|
|
5457
5553
|
}
|
|
5458
5554
|
|
|
5459
5555
|
.list-empty {
|