@open-slide/core 0.0.11 → 0.0.12
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/{build-DHiRlpjn.js → build-aiY_8kwE.js} +2 -1
- package/dist/cli/bin.js +43 -4
- package/dist/{config-LZM903FE.js → config-CVqRAagl.js} +592 -63
- package/dist/design-CROQh0AA.js +35 -0
- package/dist/{dev-B3JzCYn7.js → dev-R2we2iaF.js} +2 -1
- package/dist/index.d.ts +55 -4
- package/dist/index.js +110 -1
- package/dist/{preview-UikovHEt.js → preview-CU4zSyGp.js} +2 -1
- package/dist/sync-3oqN1WyK.js +139 -0
- package/dist/sync-B4eLo2H6.js +3 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +2 -1
- package/package.json +2 -1
- package/skills/apply-comments/SKILL.md +83 -0
- package/skills/create-slide/SKILL.md +81 -0
- package/skills/create-theme/SKILL.md +194 -0
- package/skills/slide-authoring/SKILL.md +288 -0
- package/src/app/{App.tsx → app.tsx} +8 -6
- package/src/app/components/{AssetView.tsx → asset-view.tsx} +41 -33
- package/src/app/components/{ClickNavZones.tsx → click-nav-zones.tsx} +1 -1
- package/src/app/components/history-provider.tsx +120 -0
- package/src/app/components/image-placeholder.tsx +121 -0
- package/src/app/components/inspector/{CommentWidget.tsx → comment-widget.tsx} +1 -1
- package/src/app/components/inspector/{InspectOverlay.tsx → inspect-overlay.tsx} +1 -1
- package/src/app/components/inspector/{InspectorPanel.tsx → inspector-panel.tsx} +164 -212
- package/src/app/components/inspector/{InspectorProvider.tsx → inspector-provider.tsx} +186 -18
- package/src/app/components/inspector/save-bar.tsx +47 -0
- package/src/app/components/panel/panel-fields.tsx +60 -0
- package/src/app/components/panel/panel-shell.tsx +78 -0
- package/src/app/components/panel/save-card.tsx +139 -0
- package/src/app/components/pdf-progress-toast.tsx +25 -0
- package/src/app/components/player.tsx +341 -0
- package/src/app/components/present/blackout-overlay.tsx +18 -0
- package/src/app/components/present/control-bar.tsx +204 -0
- package/src/app/components/present/help-overlay.tsx +56 -0
- package/src/app/components/present/jump-input.tsx +74 -0
- package/src/app/components/present/laser-pointer.tsx +40 -0
- package/src/app/components/present/overview-grid.tsx +184 -0
- package/src/app/components/present/progress-bar.tsx +26 -0
- package/src/app/components/present/use-idle.ts +44 -0
- package/src/app/components/present/use-pointer-near-bottom.ts +34 -0
- package/src/app/components/present/use-presenter-channel.ts +71 -0
- package/src/app/components/present/use-touch-swipe.ts +63 -0
- package/src/app/components/sidebar/{FolderItem.tsx → folder-item.tsx} +62 -27
- package/src/app/components/sidebar/{IconPicker.tsx → icon-picker.tsx} +13 -10
- package/src/app/components/sidebar/{Sidebar.tsx → sidebar.tsx} +40 -34
- package/src/app/components/{SlideCanvas.tsx → slide-canvas.tsx} +35 -10
- package/src/app/components/style-panel/design-provider.tsx +139 -0
- package/src/app/components/style-panel/style-panel.tsx +326 -0
- package/src/app/components/style-panel/use-design.ts +112 -0
- package/src/app/components/theme-toggle.tsx +57 -0
- package/src/app/components/thumbnail-rail.tsx +151 -0
- package/src/app/components/ui/button.tsx +51 -19
- package/src/app/components/ui/card.tsx +1 -1
- package/src/app/components/ui/dialog.tsx +25 -9
- package/src/app/components/ui/dropdown-menu.tsx +29 -12
- package/src/app/components/ui/input.tsx +13 -9
- package/src/app/components/ui/popover.tsx +5 -2
- package/src/app/components/ui/progress.tsx +2 -2
- package/src/app/components/ui/select.tsx +11 -5
- package/src/app/components/ui/separator.tsx +1 -1
- package/src/app/components/ui/slider.tsx +4 -4
- package/src/app/components/ui/sonner.tsx +11 -1
- package/src/app/components/ui/tabs.tsx +6 -6
- package/src/app/components/ui/textarea.tsx +11 -7
- package/src/app/components/ui/toggle-group.tsx +2 -2
- package/src/app/components/ui/toggle.tsx +6 -6
- package/src/app/components/ui/tooltip.tsx +5 -2
- package/src/app/lib/export-html.ts +10 -1
- package/src/app/lib/export-pdf.ts +7 -0
- package/src/app/lib/folders.ts +1 -1
- package/src/app/lib/inspector/{useEditor.ts → use-editor.ts} +2 -1
- package/src/app/lib/sdk.ts +5 -0
- package/src/app/lib/slides.ts +1 -1
- package/src/app/lib/utils.ts +1 -1
- package/src/app/main.tsx +5 -2
- package/src/app/routes/{Home.tsx → home.tsx} +266 -97
- package/src/app/routes/presenter.tsx +400 -0
- package/src/app/routes/slide.tsx +519 -0
- package/src/app/styles.css +338 -67
- package/src/app/components/PdfProgressToast.tsx +0 -23
- package/src/app/components/Player.tsx +0 -100
- package/src/app/components/ThumbnailRail.tsx +0 -68
- package/src/app/components/inspector/SaveBar.tsx +0 -77
- package/src/app/routes/Slide.tsx +0 -478
- /package/dist/{config-SXL5qIl6.d.ts → config-DweCbRkQ.d.ts} +0 -0
- /package/src/app/lib/inspector/{useComments.ts → use-comments.ts} +0 -0
- /package/src/app/lib/{useWheelPageNavigation.ts → use-wheel-page-navigation.ts} +0 -0
|
@@ -129,39 +129,44 @@ export function AssetView({ slideId }: Props) {
|
|
|
129
129
|
}
|
|
130
130
|
}}
|
|
131
131
|
>
|
|
132
|
-
<div className="flex shrink-0 items-center justify-between gap-3 border-b bg-
|
|
132
|
+
<div className="flex shrink-0 items-center justify-between gap-3 border-b border-hairline bg-sidebar px-6 py-3">
|
|
133
133
|
<div className="min-w-0">
|
|
134
|
-
<
|
|
135
|
-
<p className="truncate text-
|
|
136
|
-
<span className="font-mono">slides/{slideId}/assets/</span>
|
|
134
|
+
<span className="eyebrow">Assets</span>
|
|
135
|
+
<p className="mt-0.5 truncate text-[12px] text-muted-foreground">
|
|
136
|
+
<span className="font-mono text-[11.5px]">slides/{slideId}/assets/</span>
|
|
137
137
|
{!loading && (
|
|
138
138
|
<>
|
|
139
|
-
<span className="mx-
|
|
140
|
-
<span
|
|
139
|
+
<span className="mx-2 opacity-50">·</span>
|
|
140
|
+
<span className="folio">
|
|
141
|
+
{assets.length.toString().padStart(2, '0')}
|
|
142
|
+
<span className="opacity-40"> </span>
|
|
143
|
+
{assets.length === 1 ? 'file' : 'files'}
|
|
144
|
+
</span>
|
|
141
145
|
</>
|
|
142
146
|
)}
|
|
143
147
|
</p>
|
|
144
148
|
</div>
|
|
145
|
-
<div className="flex shrink-0 items-center gap-
|
|
149
|
+
<div className="flex shrink-0 items-center gap-1.5">
|
|
146
150
|
<button
|
|
147
151
|
type="button"
|
|
148
152
|
onClick={() => setLogoSearchOpen(true)}
|
|
149
153
|
className={cn(
|
|
150
|
-
'inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-
|
|
151
|
-
'hover:bg-muted active:translate-y-px',
|
|
154
|
+
'inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-[5px] border border-border bg-card px-2.5 text-[12.5px] font-medium transition-colors',
|
|
155
|
+
'hover:bg-muted/60 hover:border-foreground/20 active:translate-y-px',
|
|
152
156
|
)}
|
|
153
157
|
>
|
|
154
|
-
<Search className="size-
|
|
158
|
+
<Search className="size-3.5" />
|
|
155
159
|
<span>Search logos</span>
|
|
156
160
|
</button>
|
|
157
161
|
<label
|
|
158
162
|
htmlFor={inputId}
|
|
159
163
|
className={cn(
|
|
160
|
-
'inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-
|
|
161
|
-
'
|
|
164
|
+
'inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-[5px] bg-foreground px-3 text-[12.5px] font-medium text-background transition-colors',
|
|
165
|
+
'shadow-[inset_0_1px_0_oklch(1_0_0/0.12),0_1px_0_oklch(0_0_0/0.12)]',
|
|
166
|
+
'hover:bg-foreground/90 active:translate-y-px',
|
|
162
167
|
)}
|
|
163
168
|
>
|
|
164
|
-
<Upload className="size-
|
|
169
|
+
<Upload className="size-3.5" />
|
|
165
170
|
<span>Upload</span>
|
|
166
171
|
</label>
|
|
167
172
|
<input
|
|
@@ -231,11 +236,11 @@ export function AssetView({ slideId }: Props) {
|
|
|
231
236
|
className="pointer-events-none absolute inset-0 z-30 animate-in fade-in-0 duration-200"
|
|
232
237
|
aria-hidden="true"
|
|
233
238
|
>
|
|
234
|
-
<div className="absolute inset-0 bg-
|
|
235
|
-
<div className="absolute inset-2 rounded-
|
|
239
|
+
<div className="absolute inset-0 bg-brand/5" />
|
|
240
|
+
<div className="absolute inset-2 rounded-[10px] border border-dashed border-brand/40" />
|
|
236
241
|
<div className="absolute inset-x-0 bottom-8 flex justify-center">
|
|
237
|
-
<div className="flex animate-in items-center gap-2 rounded-
|
|
238
|
-
<ArrowDownToLine className="size-3.5 text-
|
|
242
|
+
<div className="flex animate-in items-center gap-2 rounded-[6px] border border-border bg-card px-3 py-1.5 text-[12px] font-medium shadow-floating fade-in-0 slide-in-from-bottom-1 duration-300">
|
|
243
|
+
<ArrowDownToLine className="size-3.5 text-brand" />
|
|
239
244
|
<span>Drop to upload</span>
|
|
240
245
|
</div>
|
|
241
246
|
</div>
|
|
@@ -280,14 +285,15 @@ export function AssetView({ slideId }: Props) {
|
|
|
280
285
|
|
|
281
286
|
function EmptyState() {
|
|
282
287
|
return (
|
|
283
|
-
<div className="flex h-full flex-col items-center justify-center gap-
|
|
284
|
-
<div className="flex size-
|
|
285
|
-
<ImageIcon className="size-
|
|
288
|
+
<div className="flex h-full flex-col items-center justify-center gap-4 px-6 py-16 text-center">
|
|
289
|
+
<div className="flex size-12 items-center justify-center rounded-full border border-hairline bg-card text-muted-foreground">
|
|
290
|
+
<ImageIcon className="size-5" />
|
|
286
291
|
</div>
|
|
287
292
|
<div>
|
|
288
|
-
<p className="text-
|
|
289
|
-
<p className="mt-1
|
|
290
|
-
Drop files anywhere here or
|
|
293
|
+
<p className="font-heading text-[14px] font-semibold tracking-tight">No assets yet</p>
|
|
294
|
+
<p className="mt-1 max-w-xs text-[12.5px] leading-relaxed text-muted-foreground">
|
|
295
|
+
Drop files anywhere here, or use <span className="font-mono text-foreground">Upload</span>
|
|
296
|
+
.
|
|
291
297
|
</p>
|
|
292
298
|
</div>
|
|
293
299
|
</div>
|
|
@@ -329,12 +335,12 @@ function AssetCard({
|
|
|
329
335
|
}) {
|
|
330
336
|
const isImage = asset.mime.startsWith('image/');
|
|
331
337
|
return (
|
|
332
|
-
<div className="group relative flex flex-col overflow-hidden rounded-
|
|
338
|
+
<div className="group relative flex flex-col overflow-hidden rounded-[6px] border border-border bg-card shadow-edge transition-shadow hover:shadow-floating focus-within:ring-2 focus-within:ring-ring/30">
|
|
333
339
|
<button
|
|
334
340
|
type="button"
|
|
335
341
|
onClick={onPreview}
|
|
336
342
|
aria-label={`Preview ${asset.name}`}
|
|
337
|
-
className="relative flex aspect-square w-full items-center justify-center overflow-hidden bg-[repeating-conic-gradient(theme(colors.muted)_0_25%,transparent_0_50%)] bg-[length:
|
|
343
|
+
className="relative flex aspect-square w-full items-center justify-center overflow-hidden border-b border-hairline bg-[repeating-conic-gradient(theme(colors.muted)_0_25%,transparent_0_50%)] bg-[length:14px_14px]"
|
|
338
344
|
>
|
|
339
345
|
{isImage ? (
|
|
340
346
|
<img
|
|
@@ -347,16 +353,16 @@ function AssetCard({
|
|
|
347
353
|
}}
|
|
348
354
|
/>
|
|
349
355
|
) : (
|
|
350
|
-
<FileIcon className="size-
|
|
356
|
+
<FileIcon className="size-9 text-muted-foreground" />
|
|
351
357
|
)}
|
|
352
358
|
</button>
|
|
353
359
|
|
|
354
|
-
<div className="flex items-center gap-1
|
|
360
|
+
<div className="flex items-center gap-1 px-2.5 py-2">
|
|
355
361
|
<div className="min-w-0 flex-1">
|
|
356
|
-
<div className="truncate text-
|
|
362
|
+
<div className="truncate text-[12.5px] font-medium" title={asset.name}>
|
|
357
363
|
{asset.name}
|
|
358
364
|
</div>
|
|
359
|
-
<div className="truncate
|
|
365
|
+
<div className="folio truncate">{formatSize(asset.size)}</div>
|
|
360
366
|
</div>
|
|
361
367
|
<DropdownMenu>
|
|
362
368
|
<DropdownMenuTrigger
|
|
@@ -549,8 +555,10 @@ function PreviewDialog({ asset, onClose }: { asset: AssetEntry; onClose: () => v
|
|
|
549
555
|
<span className="text-sm">No preview available</span>
|
|
550
556
|
</div>
|
|
551
557
|
)}
|
|
552
|
-
<div className="rounded-
|
|
553
|
-
|
|
558
|
+
<div className="rounded-[5px] border border-hairline bg-muted/50 px-3 py-2 font-mono text-[11.5px] leading-relaxed">
|
|
559
|
+
<span className="text-muted-foreground">import asset from </span>
|
|
560
|
+
<span className="text-brand">'{importPath}'</span>
|
|
561
|
+
<span className="text-muted-foreground">;</span>
|
|
554
562
|
</div>
|
|
555
563
|
</DialogContent>
|
|
556
564
|
</Dialog>
|
|
@@ -621,13 +629,13 @@ function LogoSearchDialog({
|
|
|
621
629
|
</DialogHeader>
|
|
622
630
|
|
|
623
631
|
<div className="relative">
|
|
624
|
-
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-
|
|
632
|
+
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
|
|
625
633
|
<input
|
|
626
634
|
ref={inputRef}
|
|
627
635
|
value={query}
|
|
628
636
|
onChange={(e) => setQuery(e.target.value)}
|
|
629
637
|
placeholder="Search by brand…"
|
|
630
|
-
className="w-full rounded-
|
|
638
|
+
className="h-9 w-full rounded-[6px] border border-border bg-background py-2 pl-8 pr-3 text-[13px] outline-none focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
631
639
|
/>
|
|
632
640
|
</div>
|
|
633
641
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
|
|
11
|
+
export type HistoryEntry = {
|
|
12
|
+
undo: () => void;
|
|
13
|
+
redo: () => void;
|
|
14
|
+
coalesceKey?: string;
|
|
15
|
+
ts: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type HistoryCtx = {
|
|
19
|
+
canUndo: boolean;
|
|
20
|
+
canRedo: boolean;
|
|
21
|
+
record: (entry: Omit<HistoryEntry, 'ts'>) => void;
|
|
22
|
+
undo: () => void;
|
|
23
|
+
redo: () => void;
|
|
24
|
+
clear: () => void;
|
|
25
|
+
isSuppressed: () => boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const COALESCE_WINDOW_MS = 500;
|
|
29
|
+
|
|
30
|
+
const Ctx = createContext<HistoryCtx | null>(null);
|
|
31
|
+
|
|
32
|
+
export function useHistory(): HistoryCtx {
|
|
33
|
+
const v = useContext(Ctx);
|
|
34
|
+
if (!v) throw new Error('useHistory must be used inside <HistoryProvider>');
|
|
35
|
+
return v;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function HistoryProvider({ children }: { children: ReactNode }) {
|
|
39
|
+
const [past, setPast] = useState<HistoryEntry[]>([]);
|
|
40
|
+
const [future, setFuture] = useState<HistoryEntry[]>([]);
|
|
41
|
+
// Set while invoking an entry's undo/redo so providers can skip
|
|
42
|
+
// re-recording the resulting state mutation.
|
|
43
|
+
const suppressedRef = useRef(false);
|
|
44
|
+
|
|
45
|
+
const record = useCallback((entry: Omit<HistoryEntry, 'ts'>) => {
|
|
46
|
+
if (suppressedRef.current) return;
|
|
47
|
+
const ts = Date.now();
|
|
48
|
+
setPast((prev) => {
|
|
49
|
+
const top = prev.at(-1);
|
|
50
|
+
if (
|
|
51
|
+
top &&
|
|
52
|
+
entry.coalesceKey !== undefined &&
|
|
53
|
+
top.coalesceKey === entry.coalesceKey &&
|
|
54
|
+
ts - top.ts < COALESCE_WINDOW_MS
|
|
55
|
+
) {
|
|
56
|
+
const merged: HistoryEntry = {
|
|
57
|
+
undo: top.undo,
|
|
58
|
+
redo: entry.redo,
|
|
59
|
+
coalesceKey: entry.coalesceKey,
|
|
60
|
+
ts,
|
|
61
|
+
};
|
|
62
|
+
return [...prev.slice(0, -1), merged];
|
|
63
|
+
}
|
|
64
|
+
return [...prev, { ...entry, ts }];
|
|
65
|
+
});
|
|
66
|
+
setFuture([]);
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
const undo = useCallback(() => {
|
|
70
|
+
setPast((prev) => {
|
|
71
|
+
const top = prev.at(-1);
|
|
72
|
+
if (!top) return prev;
|
|
73
|
+
suppressedRef.current = true;
|
|
74
|
+
try {
|
|
75
|
+
top.undo();
|
|
76
|
+
} finally {
|
|
77
|
+
suppressedRef.current = false;
|
|
78
|
+
}
|
|
79
|
+
setFuture((f) => [...f, top]);
|
|
80
|
+
return prev.slice(0, -1);
|
|
81
|
+
});
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const redo = useCallback(() => {
|
|
85
|
+
setFuture((prev) => {
|
|
86
|
+
const top = prev.at(-1);
|
|
87
|
+
if (!top) return prev;
|
|
88
|
+
suppressedRef.current = true;
|
|
89
|
+
try {
|
|
90
|
+
top.redo();
|
|
91
|
+
} finally {
|
|
92
|
+
suppressedRef.current = false;
|
|
93
|
+
}
|
|
94
|
+
setPast((p) => [...p, top]);
|
|
95
|
+
return prev.slice(0, -1);
|
|
96
|
+
});
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
const clear = useCallback(() => {
|
|
100
|
+
setPast([]);
|
|
101
|
+
setFuture([]);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
const isSuppressed = useCallback(() => suppressedRef.current, []);
|
|
105
|
+
|
|
106
|
+
const value = useMemo<HistoryCtx>(
|
|
107
|
+
() => ({
|
|
108
|
+
canUndo: past.length > 0,
|
|
109
|
+
canRedo: future.length > 0,
|
|
110
|
+
record,
|
|
111
|
+
undo,
|
|
112
|
+
redo,
|
|
113
|
+
clear,
|
|
114
|
+
isSuppressed,
|
|
115
|
+
}),
|
|
116
|
+
[past.length, future.length, record, undo, redo, clear, isSuppressed],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
|
|
120
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { CSSProperties, HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export type ImagePlaceholderProps = {
|
|
4
|
+
hint: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
className?: string;
|
|
9
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'style' | 'className'>;
|
|
10
|
+
|
|
11
|
+
export function ImagePlaceholder({
|
|
12
|
+
hint,
|
|
13
|
+
width,
|
|
14
|
+
height,
|
|
15
|
+
style,
|
|
16
|
+
className,
|
|
17
|
+
...rest
|
|
18
|
+
}: ImagePlaceholderProps) {
|
|
19
|
+
const dims = width && height ? `${width} × ${height}` : null;
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
{...rest}
|
|
23
|
+
data-slide-placeholder={hint}
|
|
24
|
+
data-placeholder-w={width}
|
|
25
|
+
data-placeholder-h={height}
|
|
26
|
+
role="img"
|
|
27
|
+
aria-label={hint}
|
|
28
|
+
style={{
|
|
29
|
+
position: 'relative',
|
|
30
|
+
width: width ?? '100%',
|
|
31
|
+
height: height ?? '100%',
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
justifyContent: 'center',
|
|
35
|
+
flexDirection: 'column',
|
|
36
|
+
gap: 14,
|
|
37
|
+
border: '1px dashed rgba(120, 120, 130, 0.35)',
|
|
38
|
+
borderRadius: 12,
|
|
39
|
+
background:
|
|
40
|
+
'linear-gradient(135deg, rgba(120,120,130,0.06) 0%, rgba(120,120,130,0.02) 50%, rgba(120,120,130,0.06) 100%)',
|
|
41
|
+
color: 'rgba(90, 90, 100, 0.7)',
|
|
42
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif',
|
|
43
|
+
textAlign: 'center',
|
|
44
|
+
padding: 24,
|
|
45
|
+
boxSizing: 'border-box',
|
|
46
|
+
overflow: 'hidden',
|
|
47
|
+
...style,
|
|
48
|
+
}}
|
|
49
|
+
className={className}
|
|
50
|
+
>
|
|
51
|
+
<PlaceholderIcon />
|
|
52
|
+
<div
|
|
53
|
+
style={{
|
|
54
|
+
display: 'flex',
|
|
55
|
+
flexDirection: 'column',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
gap: 6,
|
|
58
|
+
maxWidth: '85%',
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<span
|
|
62
|
+
style={{
|
|
63
|
+
fontSize: 11,
|
|
64
|
+
fontWeight: 600,
|
|
65
|
+
letterSpacing: '0.14em',
|
|
66
|
+
textTransform: 'uppercase',
|
|
67
|
+
opacity: 0.55,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
Image
|
|
71
|
+
</span>
|
|
72
|
+
<span
|
|
73
|
+
style={{
|
|
74
|
+
fontSize: 16,
|
|
75
|
+
fontWeight: 500,
|
|
76
|
+
lineHeight: 1.4,
|
|
77
|
+
color: 'rgba(60, 60, 70, 0.85)',
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{hint}
|
|
81
|
+
</span>
|
|
82
|
+
{dims && (
|
|
83
|
+
<span
|
|
84
|
+
style={{
|
|
85
|
+
fontSize: 11,
|
|
86
|
+
fontVariantNumeric: 'tabular-nums',
|
|
87
|
+
fontFamily: 'ui-monospace, "SF Mono", Menlo, Consolas, monospace',
|
|
88
|
+
opacity: 0.5,
|
|
89
|
+
marginTop: 2,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{dims}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function PlaceholderIcon() {
|
|
101
|
+
return (
|
|
102
|
+
<svg
|
|
103
|
+
width="32"
|
|
104
|
+
height="32"
|
|
105
|
+
viewBox="0 0 32 32"
|
|
106
|
+
fill="none"
|
|
107
|
+
stroke="currentColor"
|
|
108
|
+
strokeWidth="1.5"
|
|
109
|
+
strokeLinecap="round"
|
|
110
|
+
strokeLinejoin="round"
|
|
111
|
+
style={{ opacity: 0.55 }}
|
|
112
|
+
role="img"
|
|
113
|
+
aria-label="image placeholder"
|
|
114
|
+
>
|
|
115
|
+
<title>image placeholder</title>
|
|
116
|
+
<rect x="4" y="6" width="24" height="20" rx="2.5" />
|
|
117
|
+
<circle cx="11" cy="13" r="2" />
|
|
118
|
+
<path d="M4 22l7-7 6 6 4-4 7 7" />
|
|
119
|
+
</svg>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MessageSquare, Trash2, X } from 'lucide-react';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { useInspector } from './
|
|
3
|
+
import { useInspector } from './inspector-provider';
|
|
4
4
|
|
|
5
5
|
export function CommentWidget() {
|
|
6
6
|
const { comments, remove, error } = useInspector();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import { findSlideSource, type SlideSourceHit } from '@/lib/inspector/fiber';
|
|
3
|
-
import { useInspector } from './
|
|
3
|
+
import { useInspector } from './inspector-provider';
|
|
4
4
|
|
|
5
5
|
type Highlight = { rect: DOMRect; hit: SlideSourceHit };
|
|
6
6
|
|