debug: clipboard
This commit is contained in:
parent
ec2de7205f
commit
348912f32f
@ -10,26 +10,34 @@ import { actionDeleteSelected } from "./actionDeleteSelected";
|
||||
import { exportCanvas } from "../data/index";
|
||||
import { getNonDeletedElements, isTextElement } from "../element";
|
||||
import { t } from "../i18n";
|
||||
import { isFirefox } from "../constants";
|
||||
|
||||
export const actionCopy = register({
|
||||
name: "copy",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, _, app) => {
|
||||
perform: async (elements, appState, _, app) => {
|
||||
const elementsToCopy = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
});
|
||||
|
||||
copyToClipboard(elementsToCopy, app.files);
|
||||
try {
|
||||
await copyToClipboard(elementsToCopy, app.files);
|
||||
} catch (error: any) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
predicate: (elements, appState, appProps, app) => {
|
||||
return app.device.isMobile && !!navigator.clipboard;
|
||||
},
|
||||
contextItemLabel: "labels.copy",
|
||||
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||
keyTest: undefined,
|
||||
@ -38,15 +46,91 @@ export const actionCopy = register({
|
||||
export const actionPaste = register({
|
||||
name: "paste",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements: any, appStates: any, data, app) => {
|
||||
app.pasteFromClipboard(null);
|
||||
perform: async (elements, appState, data, app) => {
|
||||
const MIME_TYPES: Record<string, string> = {};
|
||||
try {
|
||||
try {
|
||||
const clipboardItems = await navigator.clipboard?.read();
|
||||
for (const item of clipboardItems) {
|
||||
for (const type of item.types) {
|
||||
try {
|
||||
const blob = await item.getType(type);
|
||||
MIME_TYPES[type] = await blob.text();
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Cannot retrieve ${type} from clipboardItem: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(MIME_TYPES).length === 0) {
|
||||
console.warn(
|
||||
"No clipboard data found from clipboard.read(). Falling back to clipboard.readText()",
|
||||
);
|
||||
// throw so we fall back onto clipboard.readText()
|
||||
throw new Error("No clipboard data found");
|
||||
}
|
||||
} catch (error: any) {
|
||||
try {
|
||||
MIME_TYPES["text/plain"] = await navigator.clipboard?.readText();
|
||||
} catch (error: any) {
|
||||
console.warn(`Cannot readText() from clipboard: ${error.message}`);
|
||||
if (isFirefox) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: t("hints.firefox_clipboard_write"),
|
||||
},
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`actionPaste: ${error.message}`);
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
try {
|
||||
console.log("actionPaste (1)", { MIME_TYPES });
|
||||
const event = new ClipboardEvent("paste", {
|
||||
clipboardData: new DataTransfer(),
|
||||
});
|
||||
for (const [type, value] of Object.entries(MIME_TYPES)) {
|
||||
try {
|
||||
event.clipboardData?.setData(type, value);
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Cannot set ${type} as clipboardData item: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
event.clipboardData?.types.forEach((type) => {
|
||||
console.log(
|
||||
`actionPaste (2) event.clipboardData?.getData(${type})`,
|
||||
event.clipboardData?.getData(type),
|
||||
);
|
||||
});
|
||||
app.pasteFromClipboard(event);
|
||||
} catch (error: any) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
predicate: (elements, appState, appProps, app) => {
|
||||
return app.device.isMobile && !!navigator.clipboard;
|
||||
},
|
||||
contextItemLabel: "labels.paste",
|
||||
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||
keyTest: undefined,
|
||||
|
@ -118,7 +118,7 @@ export const copyToClipboard = async (
|
||||
await copyTextToSystemClipboard(json);
|
||||
} catch (error: any) {
|
||||
PREFER_APP_CLIPBOARD = true;
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@ -193,7 +193,7 @@ const maybeParseHTMLPaste = (event: ClipboardEvent) => {
|
||||
* via async clipboard API if supported)
|
||||
*/
|
||||
const getSystemClipboard = async (
|
||||
event: ClipboardEvent | null,
|
||||
event: ClipboardEvent,
|
||||
isPlainPaste = false,
|
||||
): Promise<
|
||||
| { type: "text"; value: string }
|
||||
@ -205,10 +205,7 @@ const getSystemClipboard = async (
|
||||
return { type: "mixedContent", value: mixedContent };
|
||||
}
|
||||
|
||||
const text = event
|
||||
? event.clipboardData?.getData("text/plain")
|
||||
: probablySupportsClipboardReadText &&
|
||||
(await navigator.clipboard.readText());
|
||||
const text = event.clipboardData?.getData("text/plain");
|
||||
|
||||
return { type: "text", value: (text || "").trim() };
|
||||
} catch {
|
||||
@ -220,7 +217,7 @@ const getSystemClipboard = async (
|
||||
* Attempts to parse clipboard. Prefers system clipboard.
|
||||
*/
|
||||
export const parseClipboard = async (
|
||||
event: ClipboardEvent | null,
|
||||
event: ClipboardEvent,
|
||||
isPlainPaste = false,
|
||||
): Promise<ClipboardData> => {
|
||||
const systemClipboard = await getSystemClipboard(event, isPlainPaste);
|
||||
|
@ -1275,6 +1275,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
top={this.state.contextMenu.top}
|
||||
left={this.state.contextMenu.left}
|
||||
actionManager={this.actionManager}
|
||||
onClose={(cb) => {
|
||||
this.setState({ contextMenu: null }, () => {
|
||||
this.focusContainer();
|
||||
cb?.();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<StaticCanvas
|
||||
@ -2195,14 +2201,21 @@ class App extends React.Component<AppProps, AppState> {
|
||||
};
|
||||
|
||||
public pasteFromClipboard = withBatchedUpdates(
|
||||
async (event: ClipboardEvent | null) => {
|
||||
async (event: ClipboardEvent) => {
|
||||
const isPlainPaste = !!(IS_PLAIN_PASTE && event);
|
||||
|
||||
console.warn(
|
||||
"pasteFromClipboard",
|
||||
event?.clipboardData?.types,
|
||||
event?.clipboardData?.getData("text/plain"),
|
||||
);
|
||||
|
||||
// #686
|
||||
const target = document.activeElement;
|
||||
const isExcalidrawActive =
|
||||
this.excalidrawContainerRef.current?.contains(target);
|
||||
if (event && !isExcalidrawActive) {
|
||||
console.log("exit (1)");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2215,6 +2228,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
(!(elementUnderCursor instanceof HTMLCanvasElement) ||
|
||||
isWritableElement(target))
|
||||
) {
|
||||
console.log("exit (2)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,7 @@ import {
|
||||
} from "../actions/shortcuts";
|
||||
import { Action } from "../actions/types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import {
|
||||
useExcalidrawAppState,
|
||||
useExcalidrawElements,
|
||||
useExcalidrawSetAppState,
|
||||
} from "./App";
|
||||
import { useExcalidrawAppState, useExcalidrawElements } from "./App";
|
||||
import React from "react";
|
||||
|
||||
export type ContextMenuItem = typeof CONTEXT_MENU_SEPARATOR | Action;
|
||||
@ -25,14 +21,14 @@ type ContextMenuProps = {
|
||||
items: ContextMenuItems;
|
||||
top: number;
|
||||
left: number;
|
||||
onClose: (cb?: () => void) => void;
|
||||
};
|
||||
|
||||
export const CONTEXT_MENU_SEPARATOR = "separator";
|
||||
|
||||
export const ContextMenu = React.memo(
|
||||
({ actionManager, items, top, left }: ContextMenuProps) => {
|
||||
({ actionManager, items, top, left, onClose }: ContextMenuProps) => {
|
||||
const appState = useExcalidrawAppState();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
const elements = useExcalidrawElements();
|
||||
|
||||
const filteredItems = items.reduce((acc: ContextMenuItem[], item) => {
|
||||
@ -54,7 +50,7 @@ export const ContextMenu = React.memo(
|
||||
|
||||
return (
|
||||
<Popover
|
||||
onCloseRequest={() => setAppState({ contextMenu: null })}
|
||||
onCloseRequest={() => onClose()}
|
||||
top={top}
|
||||
left={left}
|
||||
fitInViewport={true}
|
||||
@ -102,7 +98,7 @@ export const ContextMenu = React.memo(
|
||||
// we need update state before executing the action in case
|
||||
// the action uses the appState it's being passed (that still
|
||||
// contains a defined contextMenu) to return the next state.
|
||||
setAppState({ contextMenu: null }, () => {
|
||||
onClose(() => {
|
||||
actionManager.executeAction(item, "contextMenu");
|
||||
});
|
||||
}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user