diff --git a/public/app/features/explore-map/components/ExploreMapFloatingToolbar.tsx b/public/app/features/explore-map/components/ExploreMapFloatingToolbar.tsx
index c6372ac02dd..4846ab6ba0a 100644
--- a/public/app/features/explore-map/components/ExploreMapFloatingToolbar.tsx
+++ b/public/app/features/explore-map/components/ExploreMapFloatingToolbar.tsx
@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
-import { useCallback, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import { useAssistant, createAssistantContextItem } from '@grafana/assistant';
import { GrafanaTheme2 } from '@grafana/data';
@@ -13,13 +13,14 @@ import prometheusLogoSvg from 'app/plugins/datasource/prometheus/img/prometheus_
import tempoLogoSvg from 'app/plugins/datasource/tempo/img/tempo_logo.svg';
import { useDispatch, useSelector } from 'app/types/store';
-import { addPanel, addFrame, addPostItNote, setCursorMode } from '../state/crdtSlice';
+import { addPanel, addFrame, addPostItNote, pastePanel, setCursorMode } from '../state/crdtSlice';
import {
selectPanels,
selectMapUid,
selectViewport,
selectSelectedPanelIds,
selectCursorMode,
+ selectClipboard,
} from '../state/selectors';
import { AddPanelAction } from './AssistantComponents';
@@ -28,6 +29,7 @@ export function ExploreMapFloatingToolbar() {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [isOpen, setIsOpen] = useState(false);
+ const [hasSystemClipboard, setHasSystemClipboard] = useState(false);
const currentUsername = contextSrv.user.name || contextSrv.user.login || 'Unknown';
// Get assistant functionality
@@ -39,6 +41,30 @@ export function ExploreMapFloatingToolbar() {
const viewport = useSelector((state) => selectViewport(state.exploreMapCRDT));
const selectedPanelIds = useSelector((state) => selectSelectedPanelIds(state.exploreMapCRDT));
const cursorMode = useSelector((state) => selectCursorMode(state.exploreMapCRDT));
+ const clipboard = useSelector((state) => selectClipboard(state.exploreMapCRDT));
+
+ // Check system clipboard for panel data when dropdown opens
+ useEffect(() => {
+ if (!isOpen) {
+ return;
+ }
+
+ const checkClipboard = async () => {
+ try {
+ const text = await navigator.clipboard.readText();
+ const data = JSON.parse(text);
+ if (data && typeof data === 'object' && 'mode' in data && 'width' in data && 'height' in data) {
+ setHasSystemClipboard(true);
+ } else {
+ setHasSystemClipboard(false);
+ }
+ } catch {
+ setHasSystemClipboard(false);
+ }
+ };
+
+ checkClipboard();
+ }, [isOpen]);
const handleAddPanel = useCallback(() => {
dispatch(
@@ -175,6 +201,42 @@ export function ExploreMapFloatingToolbar() {
setIsOpen(false);
}, [dispatch, currentUsername]);
+ const handlePaste = useCallback(async () => {
+ try {
+ // Try to read from system clipboard first for cross-canvas support
+ const text = await navigator.clipboard.readText();
+ const clipboardData = JSON.parse(text);
+
+ // Validate clipboard data structure
+ if (clipboardData && typeof clipboardData === 'object' && 'mode' in clipboardData && 'width' in clipboardData && 'height' in clipboardData) {
+ dispatch(
+ pastePanel({
+ viewportSize: {
+ width: window.innerWidth,
+ height: window.innerHeight,
+ },
+ createdBy: currentUsername,
+ clipboardData,
+ })
+ );
+ return;
+ }
+ } catch (err) {
+ // Fall through to use local clipboard
+ }
+
+ // Fallback to local clipboard
+ dispatch(
+ pastePanel({
+ viewportSize: {
+ width: window.innerWidth,
+ height: window.innerHeight,
+ },
+ createdBy: currentUsername,
+ })
+ );
+ }, [dispatch, currentUsername]);
+
const handleSetPointerMode = useCallback(() => {
dispatch(setCursorMode({ mode: 'pointer' }));
}, [dispatch]);
@@ -324,6 +386,12 @@ CRITICAL:
logoAlt="Pyroscope"
onClick={handleAddProfilesDrilldownPanel}
/>
+