Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import {
|
|
useCallback,
|
|
useRef,
|
|
useState,
|
|
type CSSProperties,
|
|
type JSX,
|
|
type ReactNode
|
|
} from "react";
|
|
import {
|
|
ContextMenu,
|
|
ContextMenuContent,
|
|
ContextMenuItem,
|
|
ContextMenuTrigger
|
|
} from "@tutti-os/ui-system";
|
|
import { useOptionalAgentHostApi } from "../../../agentActivityHost";
|
|
import { copyImageToClipboard } from "../lib/copyImageToClipboard";
|
|
import { translate } from "../../../i18n/index";
|
|
|
|
export function ConversationImageContextMenu({
|
|
src,
|
|
children,
|
|
asChild = false,
|
|
contentStyle
|
|
}: {
|
|
src: string;
|
|
children: ReactNode;
|
|
/**
|
|
* Attach the right-click listener directly to the child element instead of a
|
|
* wrapper span. Used for the zoomed image, whose positioning the zoom library
|
|
* manages and must not be disturbed by an extra wrapper element.
|
|
*/
|
|
asChild?: boolean;
|
|
/**
|
|
* Override the menu content style. Used by the zoomed image to raise the
|
|
* menu above the zoom modal (which sits above the default popover z-index).
|
|
*/
|
|
contentStyle?: CSSProperties;
|
|
}): JSX.Element {
|
|
const agentHostApi = useOptionalAgentHostApi();
|
|
const copyStartedRef = useRef(false);
|
|
const [menuResetKey, setMenuResetKey] = useState(0);
|
|
const copyAndClose = useCallback(() => {
|
|
if (copyStartedRef.current) {
|
|
return;
|
|
}
|
|
copyStartedRef.current = true;
|
|
setMenuResetKey((key) => key + 1);
|
|
void copyImageToClipboard(src, agentHostApi?.clipboard).finally(() => {
|
|
copyStartedRef.current = false;
|
|
});
|
|
}, [agentHostApi?.clipboard, src]);
|
|
return (
|
|
<ContextMenu key={menuResetKey}>
|
|
<ContextMenuTrigger asChild={asChild}>{children}</ContextMenuTrigger>
|
|
<ContextMenuContent style={contentStyle}>
|
|
<ContextMenuItem
|
|
onClick={copyAndClose}
|
|
onPointerDown={(event) => {
|
|
if (event.button !== 0) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
copyAndClose();
|
|
}}
|
|
onSelect={copyAndClose}
|
|
>
|
|
{translate("agentHost.agentGui.copyImage")}
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
);
|
|
}
|