@ui-annotate/react-vite 0.1.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/code-open.d.ts +17 -0
- package/dist/code-open.js +82 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/inspector-transform.d.ts +59 -0
- package/dist/inspector-transform.js +218 -0
- package/dist/protocol/constants.d.ts +7 -0
- package/dist/protocol/constants.js +31 -0
- package/dist/protocol/ids.d.ts +3 -0
- package/dist/protocol/ids.js +15 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.js +4 -0
- package/dist/protocol/paths.d.ts +3 -0
- package/dist/protocol/paths.js +36 -0
- package/dist/protocol/task-model.d.ts +35 -0
- package/dist/protocol/task-model.js +68 -0
- package/dist/runtime/app/AnnotateToolbar.d.ts +9 -0
- package/dist/runtime/app/AnnotateToolbar.js +14 -0
- package/dist/runtime/app/AnnotateWindows.d.ts +28 -0
- package/dist/runtime/app/AnnotateWindows.js +8 -0
- package/dist/runtime/app/UiAnnotate.d.ts +5 -0
- package/dist/runtime/app/UiAnnotate.js +540 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/inspector/target-inspection.d.ts +10 -0
- package/dist/runtime/inspector/target-inspection.js +337 -0
- package/dist/runtime/layout/annotate-storage.d.ts +9 -0
- package/dist/runtime/layout/annotate-storage.js +134 -0
- package/dist/runtime/layout/use-annotate-layout.d.ts +17 -0
- package/dist/runtime/layout/use-annotate-layout.js +147 -0
- package/dist/runtime/overlay/SelectionOverlay.d.ts +7 -0
- package/dist/runtime/overlay/SelectionOverlay.js +95 -0
- package/dist/runtime/shared/annotate-constants.d.ts +13 -0
- package/dist/runtime/shared/annotate-constants.js +13 -0
- package/dist/runtime/shared/annotate-types.d.ts +36 -0
- package/dist/runtime/shared/annotate-types.js +1 -0
- package/dist/runtime/shared/clipboard.d.ts +1 -0
- package/dist/runtime/shared/clipboard.js +33 -0
- package/dist/runtime/style.css +206 -0
- package/dist/runtime/task/annotate-task.d.ts +16 -0
- package/dist/runtime/task/annotate-task.js +85 -0
- package/dist/runtime/task/use-annotate-task.d.ts +16 -0
- package/dist/runtime/task/use-annotate-task.js +115 -0
- package/dist/runtime/windows/AnnotateSettingsWindow.d.ts +6 -0
- package/dist/runtime/windows/AnnotateSettingsWindow.js +5 -0
- package/dist/runtime/windows/AnnotateWindow.d.ts +21 -0
- package/dist/runtime/windows/AnnotateWindow.js +83 -0
- package/dist/runtime/windows/AnnotateWindowFrame.d.ts +26 -0
- package/dist/runtime/windows/AnnotateWindowFrame.js +56 -0
- package/dist/runtime/windows/TargetTraceTree.d.ts +12 -0
- package/dist/runtime/windows/TargetTraceTree.js +163 -0
- package/dist/runtime/windows/window-shared.d.ts +14 -0
- package/dist/runtime/windows/window-shared.js +41 -0
- package/dist/task-api.d.ts +15 -0
- package/dist/task-api.js +239 -0
- package/dist/ui/components/accordion.d.ts +7 -0
- package/dist/ui/components/accordion.js +18 -0
- package/dist/ui/components/alert-dialog.d.ts +18 -0
- package/dist/ui/components/alert-dialog.js +41 -0
- package/dist/ui/components/alert.d.ts +9 -0
- package/dist/ui/components/alert.js +24 -0
- package/dist/ui/components/badge.d.ts +9 -0
- package/dist/ui/components/badge.js +24 -0
- package/dist/ui/components/breadcrumb.d.ts +11 -0
- package/dist/ui/components/breadcrumb.js +27 -0
- package/dist/ui/components/button.d.ts +10 -0
- package/dist/ui/components/button.js +31 -0
- package/dist/ui/components/card.d.ts +9 -0
- package/dist/ui/components/card.js +24 -0
- package/dist/ui/components/dropdown-menu.d.ts +11 -0
- package/dist/ui/components/dropdown-menu.js +21 -0
- package/dist/ui/components/input.d.ts +3 -0
- package/dist/ui/components/input.js +6 -0
- package/dist/ui/components/scroll-area.d.ts +5 -0
- package/dist/ui/components/scroll-area.js +12 -0
- package/dist/ui/components/separator.d.ts +4 -0
- package/dist/ui/components/separator.js +8 -0
- package/dist/ui/components/switch.d.ts +6 -0
- package/dist/ui/components/switch.js +7 -0
- package/dist/ui/components/table.d.ts +10 -0
- package/dist/ui/components/table.js +27 -0
- package/dist/ui/components/tabs.d.ts +11 -0
- package/dist/ui/components/tabs.js +28 -0
- package/dist/ui/components/textarea.d.ts +3 -0
- package/dist/ui/components/textarea.js +6 -0
- package/dist/ui/components/toggle-group.d.ts +9 -0
- package/dist/ui/components/toggle-group.js +22 -0
- package/dist/ui/components/toggle.d.ts +9 -0
- package/dist/ui/components/toggle.js +25 -0
- package/dist/ui/components/tooltip.d.ts +7 -0
- package/dist/ui/components/tooltip.js +18 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/lib/utils.d.ts +2 -0
- package/dist/ui/lib/utils.js +5 -0
- package/dist/ui/portal/portal-container.d.ts +13 -0
- package/dist/ui/portal/portal-container.js +12 -0
- package/dist/ui-annotate-plugin.d.ts +28 -0
- package/dist/ui-annotate-plugin.js +227 -0
- package/package.json +55 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useState } from "react";
|
|
3
|
+
import { MessageSquareIcon, Trash2Icon } from "lucide-react";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/components/tooltip.js";
|
|
5
|
+
import { cn } from "../../ui/lib/utils.js";
|
|
6
|
+
const highlightRoleClasses = {
|
|
7
|
+
"target-primary": "border-ui-annotate-highlight-target",
|
|
8
|
+
"trace-node": "border-ui-annotate-highlight-trace",
|
|
9
|
+
"annotate-hover": "border-ui-annotate-highlight-annotate"
|
|
10
|
+
};
|
|
11
|
+
const highlightIntentClasses = {
|
|
12
|
+
selected: "opacity-ui-annotate-highlight-selected",
|
|
13
|
+
active: "opacity-100 shadow-ui-annotate-highlight-active"
|
|
14
|
+
};
|
|
15
|
+
const activeHighlightRoleClasses = {
|
|
16
|
+
"target-primary": "shadow-ui-annotate-highlight-target-active",
|
|
17
|
+
"trace-node": "shadow-ui-annotate-highlight-trace-active",
|
|
18
|
+
"annotate-hover": "shadow-ui-annotate-highlight-annotate-active"
|
|
19
|
+
};
|
|
20
|
+
const highlightLabelRoleClasses = {
|
|
21
|
+
"target-primary": "bg-ui-annotate-highlight-target",
|
|
22
|
+
"trace-node": "bg-ui-annotate-highlight-trace",
|
|
23
|
+
"annotate-hover": "bg-ui-annotate-highlight-annotate"
|
|
24
|
+
};
|
|
25
|
+
const commentIconColor = "#451a03";
|
|
26
|
+
export function SelectionOverlay({ highlights, onEditTarget, onDeleteAnnotateTarget }) {
|
|
27
|
+
const [rects, setRects] = useState([]);
|
|
28
|
+
useLayoutEffect(() => {
|
|
29
|
+
let frameId = 0;
|
|
30
|
+
const updateRects = () => {
|
|
31
|
+
frameId = 0;
|
|
32
|
+
const nextRects = [];
|
|
33
|
+
for (const highlight of highlights) {
|
|
34
|
+
const rect = highlight.element.getBoundingClientRect();
|
|
35
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
nextRects.push({
|
|
39
|
+
key: highlight.key,
|
|
40
|
+
...(highlight.label ? { label: highlight.label } : {}),
|
|
41
|
+
...(highlight.targetId ? { targetId: highlight.targetId } : {}),
|
|
42
|
+
...(highlight.comments ? { comments: highlight.comments } : {}),
|
|
43
|
+
role: highlight.role,
|
|
44
|
+
intent: highlight.intent,
|
|
45
|
+
top: rect.top,
|
|
46
|
+
left: rect.left,
|
|
47
|
+
width: rect.width,
|
|
48
|
+
height: rect.height
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
setRects(nextRects);
|
|
52
|
+
};
|
|
53
|
+
const scheduleUpdate = () => {
|
|
54
|
+
if (frameId) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
frameId = window.requestAnimationFrame(updateRects);
|
|
58
|
+
};
|
|
59
|
+
updateRects();
|
|
60
|
+
window.addEventListener("scroll", scheduleUpdate, true);
|
|
61
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
62
|
+
return () => {
|
|
63
|
+
if (frameId) {
|
|
64
|
+
window.cancelAnimationFrame(frameId);
|
|
65
|
+
}
|
|
66
|
+
window.removeEventListener("scroll", scheduleUpdate, true);
|
|
67
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
68
|
+
};
|
|
69
|
+
}, [highlights]);
|
|
70
|
+
if (rects.length === 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return (_jsx("div", { className: "pointer-events-none fixed inset-0 z-[9998]", children: rects.map((rect) => (_jsx("div", { className: cn("fixed rounded-md border-2 border-ring shadow-ui-annotate-highlight", highlightRoleClasses[rect.role], highlightIntentClasses[rect.intent], rect.intent === "active" && activeHighlightRoleClasses[rect.role]), style: {
|
|
74
|
+
top: rect.top,
|
|
75
|
+
left: rect.left,
|
|
76
|
+
width: rect.width,
|
|
77
|
+
height: rect.height
|
|
78
|
+
}, children: rect.label ? (_jsxs("span", { className: cn("absolute -top-ui-annotate-highlight-label-top -left-0.5 inline-flex min-w-7 items-center gap-1 rounded-md rounded-bl-none bg-ring px-ui-annotate-highlight-label-x py-ui-annotate-highlight-label-y text-xs leading-ui-annotate-highlight-label font-bold text-primary-foreground shadow-ui-annotate-highlight-label", highlightLabelRoleClasses[rect.role]), children: [rect.label, rect.targetId ? (_jsxs("span", { className: "pointer-events-auto ml-1 inline-flex items-center gap-0.5", "aria-label": `${rect.targetId} tools`, children: [_jsx(CommentTooltip, { comments: rect.comments ?? [], children: _jsx("button", { type: "button", className: "inline-flex shrink-0 items-center justify-center rounded-[3px] border-0 p-0 hover:bg-ui-annotate-highlight-toolbar-hover", style: {
|
|
79
|
+
width: "var(--spacing-ui-annotate-highlight-toolbar-button)",
|
|
80
|
+
height: "var(--spacing-ui-annotate-highlight-toolbar-button)"
|
|
81
|
+
}, "aria-label": `Open ${rect.targetId} target comments`, onClick: () => onEditTarget(rect.targetId), children: _jsx(MessageSquareIcon, { style: {
|
|
82
|
+
width: "var(--spacing-ui-annotate-highlight-toolbar-icon)",
|
|
83
|
+
height: "var(--spacing-ui-annotate-highlight-toolbar-icon)",
|
|
84
|
+
...(rect.comments?.length ? { color: commentIconColor } : {})
|
|
85
|
+
}, "data-icon": "inline-start", "aria-hidden": "true" }) }) }), _jsx("button", { type: "button", className: "inline-flex shrink-0 items-center justify-center rounded-[3px] border-0 p-0 hover:bg-ui-annotate-highlight-toolbar-hover", style: {
|
|
86
|
+
width: "var(--spacing-ui-annotate-highlight-toolbar-button)",
|
|
87
|
+
height: "var(--spacing-ui-annotate-highlight-toolbar-button)"
|
|
88
|
+
}, "aria-label": `Delete ${rect.targetId}`, onClick: () => onDeleteAnnotateTarget(rect.targetId), children: _jsx(Trash2Icon, { style: {
|
|
89
|
+
width: "var(--spacing-ui-annotate-highlight-toolbar-icon)",
|
|
90
|
+
height: "var(--spacing-ui-annotate-highlight-toolbar-icon)"
|
|
91
|
+
}, "data-icon": "inline-start", "aria-hidden": "true" }) })] })) : null] })) : null }, rect.key))) }));
|
|
92
|
+
}
|
|
93
|
+
function CommentTooltip({ children, comments }) {
|
|
94
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: children }), _jsx(TooltipContent, { side: "top", className: "max-w-72 p-2 text-left text-xs leading-5 text-background", children: comments.length > 0 ? (_jsx("ol", { className: "m-0 flex list-decimal flex-col gap-1 pl-4", children: comments.map((comment, index) => (_jsx("li", { className: "whitespace-pre-wrap break-words", children: comment }, `${index}:${comment}`))) })) : (_jsx("span", { children: "No comments yet." })) })] }));
|
|
95
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const ANNOTATE_ROOT_ID = "__ui_annotate_root__";
|
|
2
|
+
export declare const INSPECTOR_ATTRIBUTE = "data-ui-annotate-inspector";
|
|
3
|
+
export declare const ANNOTATE_WINDOW_STORAGE_KEY = "ui-annotate.windows.v1";
|
|
4
|
+
export declare const ANNOTATE_TOOLBAR_STORAGE_KEY = "ui-annotate.toolbar.v1";
|
|
5
|
+
export declare const ANNOTATE_SETTINGS_STORAGE_KEY = "ui-annotate.settings.v1";
|
|
6
|
+
export declare const SAVE_DEBOUNCE_MS = 650;
|
|
7
|
+
export declare const TOOLBAR_WIDTH = 112;
|
|
8
|
+
export declare const TOOLBAR_HEIGHT = 48;
|
|
9
|
+
export declare const WINDOW_MIN_WIDTH = 320;
|
|
10
|
+
export declare const WINDOW_MIN_HEIGHT = 260;
|
|
11
|
+
export declare const WINDOW_VIEWPORT_MARGIN = 8;
|
|
12
|
+
export declare const WINDOW_MAX_SIZE_INSET = 24;
|
|
13
|
+
export declare const ANNOTATE_PORTAL_Z_INDEX = 2147483647;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const ANNOTATE_ROOT_ID = "__ui_annotate_root__";
|
|
2
|
+
export const INSPECTOR_ATTRIBUTE = "data-ui-annotate-inspector";
|
|
3
|
+
export const ANNOTATE_WINDOW_STORAGE_KEY = "ui-annotate.windows.v1";
|
|
4
|
+
export const ANNOTATE_TOOLBAR_STORAGE_KEY = "ui-annotate.toolbar.v1";
|
|
5
|
+
export const ANNOTATE_SETTINGS_STORAGE_KEY = "ui-annotate.settings.v1";
|
|
6
|
+
export const SAVE_DEBOUNCE_MS = 650;
|
|
7
|
+
export const TOOLBAR_WIDTH = 112;
|
|
8
|
+
export const TOOLBAR_HEIGHT = 48;
|
|
9
|
+
export const WINDOW_MIN_WIDTH = 320;
|
|
10
|
+
export const WINDOW_MIN_HEIGHT = 260;
|
|
11
|
+
export const WINDOW_VIEWPORT_MARGIN = 8;
|
|
12
|
+
export const WINDOW_MAX_SIZE_INSET = 24;
|
|
13
|
+
export const ANNOTATE_PORTAL_Z_INDEX = 2147483647;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TraceFrame, TraceNodeRef } from "../../protocol/task-model.js";
|
|
2
|
+
import type { AnnotateWindowFrameState } from "../windows/AnnotateWindowFrame.js";
|
|
3
|
+
export type SelectableTarget = {
|
|
4
|
+
element: Element;
|
|
5
|
+
path: string;
|
|
6
|
+
name: string;
|
|
7
|
+
elementName: string;
|
|
8
|
+
trace?: TraceFrame[];
|
|
9
|
+
annotateTrace?: TraceFrame[];
|
|
10
|
+
};
|
|
11
|
+
export type HighlightIntent = "selected" | "active";
|
|
12
|
+
export type OverlayHighlightRole = "target-primary" | "trace-node" | "annotate-hover";
|
|
13
|
+
export type OverlayHighlight = {
|
|
14
|
+
key: string;
|
|
15
|
+
element: Element;
|
|
16
|
+
intent: HighlightIntent;
|
|
17
|
+
role: OverlayHighlightRole;
|
|
18
|
+
label?: string;
|
|
19
|
+
targetId?: string;
|
|
20
|
+
comments?: string[];
|
|
21
|
+
};
|
|
22
|
+
export type AnnotateWindowId = "annotate" | "settings";
|
|
23
|
+
export type AnnotateWindowState = AnnotateWindowFrameState;
|
|
24
|
+
export type AnnotateWindowMap = Record<AnnotateWindowId, AnnotateWindowState>;
|
|
25
|
+
export type AnnotateSettings = {
|
|
26
|
+
showNativeTraceNodes: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type TraceNodeHover = {
|
|
29
|
+
source: "annotate";
|
|
30
|
+
targetId: string;
|
|
31
|
+
node: TraceNodeRef;
|
|
32
|
+
};
|
|
33
|
+
export type ToolbarPosition = {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeClipboardText(text: string): Promise<boolean>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export async function writeClipboardText(text) {
|
|
2
|
+
if (window.navigator.clipboard) {
|
|
3
|
+
try {
|
|
4
|
+
await window.navigator.clipboard.writeText(text);
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
// Fall through to the textarea fallback for older or restricted contexts.
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return writeClipboardTextWithTextarea(text);
|
|
12
|
+
}
|
|
13
|
+
function writeClipboardTextWithTextarea(text) {
|
|
14
|
+
if (typeof document.execCommand !== "function") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const textarea = document.createElement("textarea");
|
|
18
|
+
textarea.value = text;
|
|
19
|
+
textarea.setAttribute("readonly", "");
|
|
20
|
+
textarea.style.position = "fixed";
|
|
21
|
+
textarea.style.top = "-9999px";
|
|
22
|
+
document.body.append(textarea);
|
|
23
|
+
textarea.select();
|
|
24
|
+
try {
|
|
25
|
+
return document.execCommand("copy");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
textarea.remove();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
@layer theme, base, components, utilities;
|
|
2
|
+
|
|
3
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
4
|
+
@import "tailwindcss/utilities.css" layer(utilities);
|
|
5
|
+
|
|
6
|
+
@source "./**/*.{js,ts,tsx}";
|
|
7
|
+
@source "../ui/**/*.{js,ts,tsx}";
|
|
8
|
+
|
|
9
|
+
@theme inline {
|
|
10
|
+
--color-background: var(--background);
|
|
11
|
+
--color-foreground: var(--foreground);
|
|
12
|
+
--color-card: var(--card);
|
|
13
|
+
--color-card-foreground: var(--card-foreground);
|
|
14
|
+
--color-popover: var(--popover);
|
|
15
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
16
|
+
--color-primary: var(--primary);
|
|
17
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
18
|
+
--color-secondary: var(--secondary);
|
|
19
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
20
|
+
--color-muted: var(--muted);
|
|
21
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
22
|
+
--color-accent: var(--accent);
|
|
23
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
24
|
+
--color-destructive: var(--destructive);
|
|
25
|
+
--color-border: var(--border);
|
|
26
|
+
--color-input: var(--input);
|
|
27
|
+
--color-ring: var(--ring);
|
|
28
|
+
--color-ui-annotate-highlight-target: oklch(0.627 0.194 149.214);
|
|
29
|
+
--color-ui-annotate-highlight-context: oklch(0.623 0.214 259.815);
|
|
30
|
+
--color-ui-annotate-highlight-trace: oklch(0.704 0.191 47.604);
|
|
31
|
+
--color-ui-annotate-highlight-annotate: oklch(0.656 0.241 354.308);
|
|
32
|
+
--color-ui-annotate-highlight-toolbar-hover: color-mix(in oklab, var(--foreground) 24%, transparent);
|
|
33
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
34
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
35
|
+
--radius-lg: var(--radius);
|
|
36
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
37
|
+
--shadow-ui-annotate-highlight: 0 0 0 2px color-mix(in oklab, var(--background) 74%, transparent);
|
|
38
|
+
--shadow-ui-annotate-highlight-active: 0 0 0 2px color-mix(in oklab, var(--background) 82%, transparent), 0 0 0 5px color-mix(in oklab, var(--ring) 20%, transparent);
|
|
39
|
+
--shadow-ui-annotate-highlight-target-active: 0 0 0 2px color-mix(in oklab, var(--background) 82%, transparent), 0 0 0 5px color-mix(in oklab, var(--color-ui-annotate-highlight-target) 22%, transparent);
|
|
40
|
+
--shadow-ui-annotate-highlight-trace-active: 0 0 0 2px color-mix(in oklab, var(--background) 82%, transparent), 0 0 0 5px color-mix(in oklab, var(--color-ui-annotate-highlight-trace) 24%, transparent);
|
|
41
|
+
--shadow-ui-annotate-highlight-annotate-active: 0 0 0 2px color-mix(in oklab, var(--background) 82%, transparent), 0 0 0 5px color-mix(in oklab, var(--color-ui-annotate-highlight-annotate) 24%, transparent);
|
|
42
|
+
--shadow-ui-annotate-highlight-label: 0 8px 20px color-mix(in oklab, var(--foreground) 18%, transparent);
|
|
43
|
+
--spacing-ui-annotate-highlight-label-top: 23px;
|
|
44
|
+
--spacing-ui-annotate-highlight-label-x: 7px;
|
|
45
|
+
--spacing-ui-annotate-highlight-label-y: 3px;
|
|
46
|
+
--spacing-ui-annotate-highlight-toolbar-button: 18px;
|
|
47
|
+
--spacing-ui-annotate-highlight-toolbar-icon: 11px;
|
|
48
|
+
--opacity-ui-annotate-highlight-selected: 0.62;
|
|
49
|
+
--leading-ui-annotate-highlight-label: 1.2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#__ui_annotate_root__,
|
|
53
|
+
:host {
|
|
54
|
+
--background: oklch(0.985 0.002 247.839);
|
|
55
|
+
--foreground: oklch(0.145 0 0);
|
|
56
|
+
--card: oklch(1 0 0);
|
|
57
|
+
--card-foreground: oklch(0.145 0 0);
|
|
58
|
+
--popover: oklch(1 0 0);
|
|
59
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
60
|
+
--primary: oklch(0.205 0 0);
|
|
61
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
62
|
+
--secondary: oklch(0.97 0 0);
|
|
63
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
64
|
+
--muted: oklch(0.97 0 0);
|
|
65
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
66
|
+
--accent: oklch(0.97 0 0);
|
|
67
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
68
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
69
|
+
--border: oklch(0.922 0 0);
|
|
70
|
+
--input: oklch(0.922 0 0);
|
|
71
|
+
--ring: oklch(0.708 0 0);
|
|
72
|
+
--radius: 0.5rem;
|
|
73
|
+
color-scheme: light;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@layer base {
|
|
77
|
+
:host,
|
|
78
|
+
:host *,
|
|
79
|
+
:host ::before,
|
|
80
|
+
:host ::after,
|
|
81
|
+
:host ::backdrop,
|
|
82
|
+
:host ::file-selector-button,
|
|
83
|
+
#__ui_annotate_root__,
|
|
84
|
+
#__ui_annotate_root__ *,
|
|
85
|
+
#__ui_annotate_root__ ::before,
|
|
86
|
+
#__ui_annotate_root__ ::after,
|
|
87
|
+
#__ui_annotate_root__ ::backdrop,
|
|
88
|
+
#__ui_annotate_root__ ::file-selector-button {
|
|
89
|
+
box-sizing: border-box;
|
|
90
|
+
border: 0 solid;
|
|
91
|
+
margin: 0;
|
|
92
|
+
padding: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:host,
|
|
96
|
+
:host *,
|
|
97
|
+
#__ui_annotate_root__,
|
|
98
|
+
#__ui_annotate_root__ * {
|
|
99
|
+
@apply border-border outline-ring/50;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
:host,
|
|
103
|
+
#__ui_annotate_root__ {
|
|
104
|
+
@apply text-foreground;
|
|
105
|
+
font-family:
|
|
106
|
+
Inter,
|
|
107
|
+
ui-sans-serif,
|
|
108
|
+
system-ui,
|
|
109
|
+
-apple-system,
|
|
110
|
+
BlinkMacSystemFont,
|
|
111
|
+
"Segoe UI",
|
|
112
|
+
sans-serif;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
:host button,
|
|
116
|
+
:host input,
|
|
117
|
+
:host select,
|
|
118
|
+
:host optgroup,
|
|
119
|
+
:host textarea,
|
|
120
|
+
:host ::file-selector-button,
|
|
121
|
+
#__ui_annotate_root__ button,
|
|
122
|
+
#__ui_annotate_root__ input,
|
|
123
|
+
#__ui_annotate_root__ select,
|
|
124
|
+
#__ui_annotate_root__ optgroup,
|
|
125
|
+
#__ui_annotate_root__ textarea,
|
|
126
|
+
#__ui_annotate_root__ ::file-selector-button {
|
|
127
|
+
font: inherit;
|
|
128
|
+
font-feature-settings: inherit;
|
|
129
|
+
font-variation-settings: inherit;
|
|
130
|
+
letter-spacing: inherit;
|
|
131
|
+
color: inherit;
|
|
132
|
+
border-radius: 0;
|
|
133
|
+
background-color: transparent;
|
|
134
|
+
opacity: 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
:host button,
|
|
138
|
+
:host input:where([type="button"], [type="reset"], [type="submit"]),
|
|
139
|
+
:host ::file-selector-button,
|
|
140
|
+
#__ui_annotate_root__ button,
|
|
141
|
+
#__ui_annotate_root__ input:where([type="button"], [type="reset"], [type="submit"]),
|
|
142
|
+
#__ui_annotate_root__ ::file-selector-button {
|
|
143
|
+
appearance: button;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
:host button,
|
|
147
|
+
#__ui_annotate_root__ button {
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
:host button:disabled,
|
|
152
|
+
#__ui_annotate_root__ button:disabled {
|
|
153
|
+
cursor: not-allowed;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
:host code,
|
|
157
|
+
#__ui_annotate_root__ code {
|
|
158
|
+
font-family:
|
|
159
|
+
"SFMono-Regular",
|
|
160
|
+
Consolas,
|
|
161
|
+
"Liberation Mono",
|
|
162
|
+
monospace;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
:host ol,
|
|
166
|
+
:host ul,
|
|
167
|
+
:host menu,
|
|
168
|
+
#__ui_annotate_root__ ol,
|
|
169
|
+
#__ui_annotate_root__ ul,
|
|
170
|
+
#__ui_annotate_root__ menu {
|
|
171
|
+
list-style: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:host img,
|
|
175
|
+
:host svg,
|
|
176
|
+
:host video,
|
|
177
|
+
:host canvas,
|
|
178
|
+
:host audio,
|
|
179
|
+
:host iframe,
|
|
180
|
+
:host embed,
|
|
181
|
+
:host object,
|
|
182
|
+
#__ui_annotate_root__ img,
|
|
183
|
+
#__ui_annotate_root__ svg,
|
|
184
|
+
#__ui_annotate_root__ video,
|
|
185
|
+
#__ui_annotate_root__ canvas,
|
|
186
|
+
#__ui_annotate_root__ audio,
|
|
187
|
+
#__ui_annotate_root__ iframe,
|
|
188
|
+
#__ui_annotate_root__ embed,
|
|
189
|
+
#__ui_annotate_root__ object {
|
|
190
|
+
display: block;
|
|
191
|
+
vertical-align: middle;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
:host img,
|
|
195
|
+
:host video,
|
|
196
|
+
#__ui_annotate_root__ img,
|
|
197
|
+
#__ui_annotate_root__ video {
|
|
198
|
+
max-width: 100%;
|
|
199
|
+
height: auto;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
:host [hidden]:where(:not([hidden="until-found"])),
|
|
203
|
+
#__ui_annotate_root__ [hidden]:where(:not([hidden="until-found"])) {
|
|
204
|
+
display: none !important;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type AnnotateTaskFile, type TraceFrame } from "../../protocol/task-model.js";
|
|
2
|
+
export type AnnotateTargetInput = {
|
|
3
|
+
trace: TraceFrame[];
|
|
4
|
+
};
|
|
5
|
+
export type RegisterAnnotateTargetResult = {
|
|
6
|
+
task: AnnotateTaskFile;
|
|
7
|
+
targetId: string;
|
|
8
|
+
targetCreated: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const EMPTY_ANNOTATE_TASK_FILE: AnnotateTaskFile;
|
|
11
|
+
export declare function loadAnnotateTask(): Promise<AnnotateTaskFile>;
|
|
12
|
+
export declare function saveAnnotateTask(task: AnnotateTaskFile): Promise<AnnotateTaskFile>;
|
|
13
|
+
export declare function registerAnnotateTarget(task: AnnotateTaskFile, target: AnnotateTargetInput): RegisterAnnotateTargetResult;
|
|
14
|
+
export declare function updateTargetComments(task: AnnotateTaskFile, targetId: string, comments: string[]): AnnotateTaskFile;
|
|
15
|
+
export declare function deleteTarget(task: AnnotateTaskFile, targetId: string): AnnotateTaskFile;
|
|
16
|
+
export declare function deleteAllTargets(task: AnnotateTaskFile): AnnotateTaskFile;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getNextTargetId } from "../../protocol/index.js";
|
|
2
|
+
import { isSameTargetTrace } from "../../protocol/task-model.js";
|
|
3
|
+
import { ANNOTATE_TASK_AGENT_INSTRUCTIONS } from "../../protocol/constants.js";
|
|
4
|
+
export const EMPTY_ANNOTATE_TASK_FILE = {
|
|
5
|
+
agentInstructions: [...ANNOTATE_TASK_AGENT_INSTRUCTIONS],
|
|
6
|
+
targets: []
|
|
7
|
+
};
|
|
8
|
+
export async function loadAnnotateTask() {
|
|
9
|
+
return readAnnotateTaskResponse(await fetch("/__ui-annotate/annotate-task"));
|
|
10
|
+
}
|
|
11
|
+
export async function saveAnnotateTask(task) {
|
|
12
|
+
return readAnnotateTaskResponse(await fetch("/__ui-annotate/annotate-task", {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/json"
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify(task)
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
export function registerAnnotateTarget(task, target) {
|
|
21
|
+
const existingTarget = task.targets.find((candidate) => isSameTargetTrace(candidate.trace, target.trace));
|
|
22
|
+
if (existingTarget) {
|
|
23
|
+
return {
|
|
24
|
+
task,
|
|
25
|
+
targetId: existingTarget.id,
|
|
26
|
+
targetCreated: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const targetId = getNextTargetId(task.targets);
|
|
30
|
+
const nextTarget = {
|
|
31
|
+
id: targetId,
|
|
32
|
+
comments: [],
|
|
33
|
+
trace: target.trace.map(cloneTraceFrame)
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
task: {
|
|
37
|
+
...task,
|
|
38
|
+
targets: [...task.targets, nextTarget]
|
|
39
|
+
},
|
|
40
|
+
targetId,
|
|
41
|
+
targetCreated: true
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function updateTargetComments(task, targetId, comments) {
|
|
45
|
+
return {
|
|
46
|
+
...task,
|
|
47
|
+
targets: task.targets.map((target) => target.id === targetId ? { ...target, comments: [...comments] } : target)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function deleteTarget(task, targetId) {
|
|
51
|
+
return {
|
|
52
|
+
...task,
|
|
53
|
+
targets: task.targets.filter((target) => target.id !== targetId)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function deleteAllTargets(task) {
|
|
57
|
+
return {
|
|
58
|
+
...task,
|
|
59
|
+
targets: []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function cloneTraceFrame(frame) {
|
|
63
|
+
return {
|
|
64
|
+
...frame,
|
|
65
|
+
calls: frame.calls.map((call) => ({ ...call }))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function readAnnotateTaskResponse(response) {
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(await readErrorMessage(response));
|
|
71
|
+
}
|
|
72
|
+
return (await response.json());
|
|
73
|
+
}
|
|
74
|
+
async function readErrorMessage(response) {
|
|
75
|
+
try {
|
|
76
|
+
const body = (await response.json());
|
|
77
|
+
if (typeof body.error === "string") {
|
|
78
|
+
return body.error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// JSON parse failed; fall back to the generic status message.
|
|
83
|
+
}
|
|
84
|
+
return `UI Annotate task request failed with ${response.status}.`;
|
|
85
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RefObject } from "react";
|
|
2
|
+
import type { AnnotateTaskFile } from "../../protocol/index.js";
|
|
3
|
+
import type { SaveStatus, TaskStatus } from "../windows/window-shared.js";
|
|
4
|
+
type UseAnnotateTaskResult = {
|
|
5
|
+
commitTask: (nextTask: AnnotateTaskFile, mode: "debounced" | "immediate") => void;
|
|
6
|
+
flushTaskSave: () => Promise<boolean>;
|
|
7
|
+
saveError: string | null;
|
|
8
|
+
saveStatus: SaveStatus;
|
|
9
|
+
task: AnnotateTaskFile;
|
|
10
|
+
taskError: string | null;
|
|
11
|
+
taskRef: RefObject<AnnotateTaskFile>;
|
|
12
|
+
taskStatus: TaskStatus;
|
|
13
|
+
taskStatusRef: RefObject<TaskStatus>;
|
|
14
|
+
};
|
|
15
|
+
export declare function useAnnotateTask(): UseAnnotateTaskResult;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { SAVE_DEBOUNCE_MS } from "../shared/annotate-constants.js";
|
|
3
|
+
import { EMPTY_ANNOTATE_TASK_FILE, loadAnnotateTask, saveAnnotateTask } from "./annotate-task.js";
|
|
4
|
+
export function useAnnotateTask() {
|
|
5
|
+
const [taskStatus, setTaskStatus] = useState("loading");
|
|
6
|
+
const [task, setTask] = useState(EMPTY_ANNOTATE_TASK_FILE);
|
|
7
|
+
const [saveStatus, setSaveStatus] = useState("idle");
|
|
8
|
+
const [saveError, setSaveError] = useState(null);
|
|
9
|
+
const [taskError, setTaskError] = useState(null);
|
|
10
|
+
const saveTimerRef = useRef(null);
|
|
11
|
+
const saveSequenceRef = useRef(0);
|
|
12
|
+
const taskRef = useRef(task);
|
|
13
|
+
const taskStatusRef = useRef(taskStatus);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
setTaskStatus("loading");
|
|
17
|
+
setTaskError(null);
|
|
18
|
+
setSaveStatus("idle");
|
|
19
|
+
setSaveError(null);
|
|
20
|
+
const loadTask = async () => {
|
|
21
|
+
try {
|
|
22
|
+
const loadedTask = await loadAnnotateTask();
|
|
23
|
+
if (cancelled) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
setTask(loadedTask);
|
|
27
|
+
taskRef.current = loadedTask;
|
|
28
|
+
setTaskStatus("ready");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (cancelled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
setTaskStatus("error");
|
|
35
|
+
setTaskError(error instanceof Error ? error.message : "Failed to load annotate task file.");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
void loadTask();
|
|
39
|
+
return () => {
|
|
40
|
+
cancelled = true;
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
taskRef.current = task;
|
|
45
|
+
}, [task]);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
taskStatusRef.current = taskStatus;
|
|
48
|
+
}, [taskStatus]);
|
|
49
|
+
const performSave = useCallback(async (nextTask) => {
|
|
50
|
+
const saveSequence = saveSequenceRef.current + 1;
|
|
51
|
+
saveSequenceRef.current = saveSequence;
|
|
52
|
+
setSaveStatus("saving");
|
|
53
|
+
setSaveError(null);
|
|
54
|
+
try {
|
|
55
|
+
const savedTask = await saveAnnotateTask(nextTask);
|
|
56
|
+
if (saveSequenceRef.current !== saveSequence) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
setTask(savedTask);
|
|
60
|
+
taskRef.current = savedTask;
|
|
61
|
+
setSaveStatus("saved");
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (saveSequenceRef.current !== saveSequence) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
setSaveStatus("error");
|
|
69
|
+
setSaveError(error instanceof Error ? error.message : "Failed to save annotate task file.");
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
const commitTask = useCallback((nextTask, mode) => {
|
|
74
|
+
setTask(nextTask);
|
|
75
|
+
taskRef.current = nextTask;
|
|
76
|
+
if (saveTimerRef.current !== null) {
|
|
77
|
+
window.clearTimeout(saveTimerRef.current);
|
|
78
|
+
}
|
|
79
|
+
if (mode === "immediate") {
|
|
80
|
+
void performSave(nextTask);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setSaveStatus("dirty");
|
|
84
|
+
setSaveError(null);
|
|
85
|
+
saveTimerRef.current = window.setTimeout(() => {
|
|
86
|
+
saveTimerRef.current = null;
|
|
87
|
+
void performSave(taskRef.current);
|
|
88
|
+
}, SAVE_DEBOUNCE_MS);
|
|
89
|
+
}, [performSave]);
|
|
90
|
+
const flushTaskSave = useCallback(async () => {
|
|
91
|
+
if (saveTimerRef.current !== null) {
|
|
92
|
+
window.clearTimeout(saveTimerRef.current);
|
|
93
|
+
saveTimerRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
return performSave(taskRef.current);
|
|
96
|
+
}, [performSave]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
return () => {
|
|
99
|
+
if (saveTimerRef.current !== null) {
|
|
100
|
+
window.clearTimeout(saveTimerRef.current);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
104
|
+
return {
|
|
105
|
+
commitTask,
|
|
106
|
+
flushTaskSave,
|
|
107
|
+
saveError,
|
|
108
|
+
saveStatus,
|
|
109
|
+
task,
|
|
110
|
+
taskError,
|
|
111
|
+
taskRef,
|
|
112
|
+
taskStatus,
|
|
113
|
+
taskStatusRef
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { AnnotateSettings } from "../shared/annotate-types.js";
|
|
3
|
+
export declare function AnnotateSettingsWindow({ settings, onShowNativeTraceNodesChange }: {
|
|
4
|
+
settings: AnnotateSettings;
|
|
5
|
+
onShowNativeTraceNodesChange(value: boolean): void;
|
|
6
|
+
}): ReactNode;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Switch } from "../../ui/components/switch.js";
|
|
3
|
+
export function AnnotateSettingsWindow({ settings, onShowNativeTraceNodesChange }) {
|
|
4
|
+
return (_jsx("div", { className: "flex flex-col gap-3 text-sm", children: _jsxs("label", { className: "flex items-center justify-between gap-3 rounded-md border bg-background p-3", children: [_jsx("span", { className: "min-w-0 font-medium text-foreground", children: "Show native trace nodes" }), _jsx(Switch, { checked: settings.showNativeTraceNodes, onCheckedChange: onShowNativeTraceNodesChange })] }) }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { AnnotateTarget, TraceNodeRef } from "../../protocol/task-model.js";
|
|
3
|
+
import { type TraceNodeElementResolver } from "./TargetTraceTree.js";
|
|
4
|
+
import { type SaveStatus, type TaskStatus } from "./window-shared.js";
|
|
5
|
+
export declare function AnnotateWindow({ activeTargetId, saveError, saveStatus, targets, taskError, taskStatus, getTraceNodeElement, onActiveTargetChange, onAddComment, onChangeComment, onDeleteComment, onDeleteTarget, onHoverTraceNode, onOpenPath, onResetTargets }: {
|
|
6
|
+
activeTargetId: string | null;
|
|
7
|
+
saveError: string | null;
|
|
8
|
+
saveStatus: SaveStatus;
|
|
9
|
+
targets: AnnotateTarget[];
|
|
10
|
+
taskError: string | null;
|
|
11
|
+
taskStatus: TaskStatus;
|
|
12
|
+
getTraceNodeElement(targetId: string): TraceNodeElementResolver;
|
|
13
|
+
onActiveTargetChange(targetId: string): void;
|
|
14
|
+
onAddComment(targetId: string): void;
|
|
15
|
+
onChangeComment(targetId: string, index: number, value: string): void;
|
|
16
|
+
onDeleteComment(targetId: string, index: number): void;
|
|
17
|
+
onDeleteTarget(targetId: string): void;
|
|
18
|
+
onHoverTraceNode(targetId: string, node: TraceNodeRef | null): void;
|
|
19
|
+
onOpenPath(path: string): void;
|
|
20
|
+
onResetTargets(): void;
|
|
21
|
+
}): ReactNode;
|