Compare commits
1 Commits
master
...
image_back
Author | SHA1 | Date | |
---|---|---|---|
|
3c83a322b6 |
@ -14,10 +14,60 @@ import {
|
||||
bindOrUnbindLinearElement,
|
||||
} from "../element/binding";
|
||||
import { isBindingElement } from "../element/typeChecks";
|
||||
import { ExcalidrawImageElement } from "../element/types";
|
||||
import { imageFromImageData } from "../element/image";
|
||||
|
||||
export const actionFinalize = register({
|
||||
name: "finalize",
|
||||
perform: (elements, appState, _, { canvas, focusContainer }) => {
|
||||
perform: (
|
||||
elements,
|
||||
appState,
|
||||
_,
|
||||
{ canvas, focusContainer, imageCache, addFiles },
|
||||
) => {
|
||||
if (appState.editingImageElement) {
|
||||
const { elementId, imageData } = appState.editingImageElement;
|
||||
const editingImageElement = elements.find((el) => el.id === elementId) as
|
||||
| ExcalidrawImageElement
|
||||
| undefined;
|
||||
if (editingImageElement?.fileId) {
|
||||
const cachedImageData = imageCache.get(editingImageElement.fileId);
|
||||
if (cachedImageData) {
|
||||
const { image, dataURL } = imageFromImageData(imageData);
|
||||
|
||||
imageCache.set(editingImageElement.fileId, {
|
||||
...cachedImageData,
|
||||
image,
|
||||
});
|
||||
|
||||
addFiles([
|
||||
{
|
||||
id: editingImageElement.fileId,
|
||||
dataURL,
|
||||
mimeType: cachedImageData.mimeType,
|
||||
created: Date.now(),
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (appState.editingLinearElement) {
|
||||
const { elementId, startBindingElement, endBindingElement } =
|
||||
appState.editingLinearElement;
|
||||
@ -162,6 +212,7 @@ export const actionFinalize = register({
|
||||
keyTest: (event, appState) =>
|
||||
(event.key === KEYS.ESCAPE &&
|
||||
(appState.editingLinearElement !== null ||
|
||||
appState.editingImageElement !== null ||
|
||||
(!appState.draggingElement && appState.multiElement === null))) ||
|
||||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||
appState.multiElement !== null),
|
||||
|
75
src/actions/actionImageEditing.tsx
Normal file
75
src/actions/actionImageEditing.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { backgroundIcon } from "../components/icons";
|
||||
import { register } from "./register";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { isInitializedImageElement } from "../element/typeChecks";
|
||||
import Scene from "../scene/Scene";
|
||||
|
||||
export const actionEditImageAlpha = register({
|
||||
name: "editImageAlpha",
|
||||
perform: async (elements, appState, _, app) => {
|
||||
if (appState.editingImageElement) {
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const selectedElement = selectedElements[0];
|
||||
if (
|
||||
selectedElements.length === 1 &&
|
||||
isInitializedImageElement(selectedElement)
|
||||
) {
|
||||
const imgData = app.imageCache.get(selectedElement.fileId);
|
||||
if (!imgData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const image = await imgData.image;
|
||||
const { width, height } = image;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.height = height;
|
||||
canvas.width = width;
|
||||
const context = canvas.getContext("2d")!;
|
||||
|
||||
context.drawImage(image, 0, 0, width, height);
|
||||
|
||||
const imageData = context.getImageData(0, 0, width, height);
|
||||
|
||||
Scene.mapElementToScene(selectedElement.id, app.scene);
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: {
|
||||
editorType: "alpha",
|
||||
elementId: selectedElement.id,
|
||||
origImageData: imageData,
|
||||
imageData,
|
||||
pointerDownState: { screenX: 0, screenY: 0, sampledPixel: null },
|
||||
},
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={backgroundIcon}
|
||||
label="Edit Image Alpha"
|
||||
className={appState.editingImageElement ? "active" : ""}
|
||||
title={"Edit image alpha"}
|
||||
aria-label={"Edit image alpha"}
|
||||
onClick={() => updateData(null)}
|
||||
visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
|
||||
/>
|
||||
),
|
||||
});
|
@ -80,3 +80,4 @@ export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||
|
||||
export { actionToggleStats } from "./actionToggleStats";
|
||||
export { actionEditImageAlpha } from "./actionImageEditing";
|
||||
|
@ -101,7 +101,8 @@ export type ActionName =
|
||||
| "flipVertical"
|
||||
| "viewMode"
|
||||
| "exportWithDarkMode"
|
||||
| "toggleTheme";
|
||||
| "toggleTheme"
|
||||
| "editImageAlpha";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
@ -41,6 +41,7 @@ export const getDefaultAppState = (): Omit<
|
||||
editingElement: null,
|
||||
editingGroupId: null,
|
||||
editingLinearElement: null,
|
||||
editingImageElement: null,
|
||||
elementLocked: false,
|
||||
elementType: "selection",
|
||||
errorMessage: null,
|
||||
@ -125,6 +126,7 @@ const APP_STATE_STORAGE_CONF = (<
|
||||
editingElement: { browser: false, export: false, server: false },
|
||||
editingGroupId: { browser: true, export: false, server: false },
|
||||
editingLinearElement: { browser: false, export: false, server: false },
|
||||
editingImageElement: { browser: false, export: false, server: false },
|
||||
elementLocked: { browser: true, export: false, server: false },
|
||||
elementType: { browser: true, export: false, server: false },
|
||||
errorMessage: { browser: false, export: false, server: false },
|
||||
|
@ -19,6 +19,7 @@ import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
|
||||
import Stack from "./Stack";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { hasStrokeColor } from "../scene/comparisons";
|
||||
import { isImageElement } from "../element/typeChecks";
|
||||
|
||||
export const SelectedShapeActions = ({
|
||||
appState,
|
||||
@ -105,6 +106,13 @@ export const SelectedShapeActions = ({
|
||||
<>{renderAction("changeArrowhead")}</>
|
||||
)}
|
||||
|
||||
<fieldset>
|
||||
<div className="buttonList">
|
||||
{targetElements.some((element) => isImageElement(element)) &&
|
||||
renderAction("editImageAlpha")}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{renderAction("changeOpacity")}
|
||||
|
||||
<fieldset>
|
||||
|
@ -237,6 +237,7 @@ import {
|
||||
getBoundTextElementId,
|
||||
} from "../element/textElement";
|
||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||
import { ImageEditor } from "../element/imageEditor";
|
||||
|
||||
const IsMobileContext = React.createContext(false);
|
||||
export const useIsMobile = () => useContext(IsMobileContext);
|
||||
@ -281,7 +282,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
UIOptions: DEFAULT_UI_OPTIONS,
|
||||
};
|
||||
|
||||
private scene: Scene;
|
||||
public scene: Scene;
|
||||
private resizeObserver: ResizeObserver | undefined;
|
||||
private nearestScrollableContainer: HTMLElement | Document | undefined;
|
||||
public library: AppClassProperties["library"];
|
||||
@ -1031,8 +1032,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
|
||||
if (
|
||||
this.state.editingLinearElement &&
|
||||
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]
|
||||
(this.state.editingLinearElement &&
|
||||
!this.state.selectedElementIds[
|
||||
this.state.editingLinearElement.elementId
|
||||
]) ||
|
||||
(this.state.editingImageElement &&
|
||||
!this.state.selectedElementIds[
|
||||
this.state.editingImageElement.elementId
|
||||
])
|
||||
) {
|
||||
// defer so that the commitToHistory flag isn't reset via current update
|
||||
setTimeout(() => {
|
||||
@ -1135,6 +1142,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
imageCache: this.imageCache,
|
||||
isExporting: false,
|
||||
renderScrollbars: !this.isMobile,
|
||||
editingImageElement: this.state.editingImageElement,
|
||||
},
|
||||
);
|
||||
if (scrollBars) {
|
||||
@ -2330,6 +2338,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const scenePointer = viewportCoordsToSceneCoords(event, this.state);
|
||||
const { x: scenePointerX, y: scenePointerY } = scenePointer;
|
||||
|
||||
if (this.state.editingImageElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.state.editingLinearElement &&
|
||||
!this.state.editingLinearElement.isDragging
|
||||
@ -2920,6 +2932,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState: PointerDownState,
|
||||
): boolean => {
|
||||
if (this.state.elementType === "selection") {
|
||||
if (this.state.editingImageElement) {
|
||||
ImageEditor.handlePointerDown(
|
||||
this.state.editingImageElement,
|
||||
pointerDownState.origin,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const elements = this.scene.getElements();
|
||||
const selectedElements = getSelectedElements(elements, this.state);
|
||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||
@ -3480,6 +3500,22 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.editingImageElement) {
|
||||
const newImageData = ImageEditor.handlePointerMove(
|
||||
this.state.editingImageElement,
|
||||
pointerCoords,
|
||||
);
|
||||
if (newImageData) {
|
||||
this.setState({
|
||||
editingImageElement: {
|
||||
...this.state.editingImageElement,
|
||||
imageData: newImageData,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.editingLinearElement) {
|
||||
const didDrag = LinearElementEditor.handlePointDragging(
|
||||
this.state,
|
||||
@ -3802,6 +3838,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
||||
|
||||
if (this.state.editingImageElement) {
|
||||
ImageEditor.handlePointerUp(this.state.editingImageElement);
|
||||
}
|
||||
|
||||
// Handle end of dragging a point of a linear element, might close a loop
|
||||
// and sets binding element
|
||||
if (this.state.editingLinearElement) {
|
||||
|
@ -89,6 +89,14 @@ export const trash = createIcon(
|
||||
|
||||
{ width: 448, height: 512 },
|
||||
);
|
||||
export const backgroundIcon = createIcon(
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M512 320s-64 92.65-64 128c0 35.35 28.66 64 64 64s64-28.65 64-64-64-128-64-128zm-9.37-102.94L294.94 9.37C288.69 3.12 280.5 0 272.31 0s-16.38 3.12-22.62 9.37l-81.58 81.58L81.93 4.76c-6.25-6.25-16.38-6.25-22.62 0L36.69 27.38c-6.24 6.25-6.24 16.38 0 22.62l86.19 86.18-94.76 94.76c-37.49 37.48-37.49 98.26 0 135.75l117.19 117.19c18.74 18.74 43.31 28.12 67.87 28.12 24.57 0 49.13-9.37 67.87-28.12l221.57-221.57c12.5-12.5 12.5-32.75.01-45.25zm-116.22 70.97H65.93c1.36-3.84 3.57-7.98 7.43-11.83l13.15-13.15 81.61-81.61 58.6 58.6c12.49 12.49 32.75 12.49 45.24 0s12.49-32.75 0-45.24l-58.6-58.6 58.95-58.95 162.44 162.44-48.34 48.34z"
|
||||
></path>,
|
||||
|
||||
{ width: 576, height: 512 },
|
||||
);
|
||||
|
||||
export const palette = createIcon(
|
||||
"M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z",
|
||||
|
@ -109,3 +109,16 @@ export const normalizeSVG = async (SVGString: string) => {
|
||||
return svg.outerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
export const imageFromImageData = (imagedata: ImageData) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
canvas.width = imagedata.width;
|
||||
canvas.height = imagedata.height;
|
||||
ctx.putImageData(imagedata, 0, 0);
|
||||
|
||||
const image = new Image();
|
||||
const dataURL = canvas.toDataURL() as DataURL;
|
||||
image.src = dataURL;
|
||||
return { image, dataURL };
|
||||
};
|
||||
|
112
src/element/imageEditor.ts
Normal file
112
src/element/imageEditor.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { distance2d } from "../math";
|
||||
import Scene from "../scene/Scene";
|
||||
import {
|
||||
ExcalidrawImageElement,
|
||||
InitializedExcalidrawImageElement,
|
||||
} from "./types";
|
||||
|
||||
export type EditingImageElement = {
|
||||
editorType: "alpha";
|
||||
elementId: ExcalidrawImageElement["id"];
|
||||
origImageData: Readonly<ImageData>;
|
||||
imageData: ImageData;
|
||||
pointerDownState: {
|
||||
screenX: number;
|
||||
screenY: number;
|
||||
sampledPixel: readonly [number, number, number, number] | null;
|
||||
};
|
||||
};
|
||||
|
||||
const getElement = (id: EditingImageElement["elementId"]) => {
|
||||
const element = Scene.getScene(id)?.getNonDeletedElement(id);
|
||||
if (element) {
|
||||
return element as InitializedExcalidrawImageElement;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export class ImageEditor {
|
||||
static handlePointerDown(
|
||||
editingElement: EditingImageElement,
|
||||
scenePointer: { x: number; y: number },
|
||||
) {
|
||||
const imageElement = getElement(editingElement.elementId);
|
||||
|
||||
if (imageElement) {
|
||||
if (
|
||||
scenePointer.x >= imageElement.x &&
|
||||
scenePointer.x <= imageElement.x + imageElement.width &&
|
||||
scenePointer.y >= imageElement.y &&
|
||||
scenePointer.y <= imageElement.y + imageElement.height
|
||||
) {
|
||||
editingElement.pointerDownState.screenX = scenePointer.x;
|
||||
editingElement.pointerDownState.screenY = scenePointer.y;
|
||||
|
||||
const { width, height, data } = editingElement.origImageData;
|
||||
|
||||
const imageOffsetX = Math.round(
|
||||
(scenePointer.x - imageElement.x) * (width / imageElement.width),
|
||||
);
|
||||
const imageOffsetY = Math.round(
|
||||
(scenePointer.y - imageElement.y) * (height / imageElement.height),
|
||||
);
|
||||
|
||||
const sampledPixel = [
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 0],
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 1],
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 2],
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 3],
|
||||
] as const;
|
||||
|
||||
editingElement.pointerDownState.sampledPixel = sampledPixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static handlePointerMove(
|
||||
editingElement: EditingImageElement,
|
||||
scenePointer: { x: number; y: number },
|
||||
) {
|
||||
const { sampledPixel } = editingElement.pointerDownState;
|
||||
if (sampledPixel) {
|
||||
const { screenX, screenY } = editingElement.pointerDownState;
|
||||
const distance = distance2d(
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
screenX,
|
||||
screenY,
|
||||
);
|
||||
|
||||
const { width, height, data } = editingElement.origImageData;
|
||||
const newImageData = new ImageData(width, height);
|
||||
|
||||
for (let x = 0; x < width; ++x) {
|
||||
for (let y = 0; y < height; ++y) {
|
||||
if (
|
||||
Math.abs(sampledPixel[0] - data[(y * width + x) * 4 + 0]) +
|
||||
Math.abs(sampledPixel[1] - data[(y * width + x) * 4 + 1]) +
|
||||
Math.abs(sampledPixel[2] - data[(y * width + x) * 4 + 2]) <
|
||||
distance
|
||||
) {
|
||||
newImageData.data[(y * width + x) * 4 + 0] = 0;
|
||||
newImageData.data[(y * width + x) * 4 + 1] = 255;
|
||||
newImageData.data[(y * width + x) * 4 + 2] = 0;
|
||||
newImageData.data[(y * width + x) * 4 + 3] = 0;
|
||||
} else {
|
||||
for (let p = 0; p < 4; ++p) {
|
||||
newImageData.data[(y * width + x) * 4 + p] =
|
||||
data[(y * width + x) * 4 + p];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newImageData;
|
||||
}
|
||||
}
|
||||
|
||||
static handlePointerUp(editingElement: EditingImageElement) {
|
||||
editingElement.pointerDownState.sampledPixel = null;
|
||||
editingElement.origImageData = editingElement.imageData;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import {
|
||||
isLinearElement,
|
||||
isFreeDrawElement,
|
||||
isInitializedImageElement,
|
||||
isImageElement,
|
||||
} from "../element/typeChecks";
|
||||
import {
|
||||
getDiamondPoints,
|
||||
@ -221,19 +222,31 @@ const drawElementOnCanvas = (
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
const img = isInitializedImageElement(element)
|
||||
? renderConfig.imageCache.get(element.fileId)?.image
|
||||
: undefined;
|
||||
if (img != null && !(img instanceof Promise)) {
|
||||
context.drawImage(
|
||||
img,
|
||||
0 /* hardcoded for the selection box*/,
|
||||
0,
|
||||
element.width,
|
||||
element.height,
|
||||
);
|
||||
if (renderConfig.editingImageElement) {
|
||||
const { imageData } = renderConfig.editingImageElement;
|
||||
|
||||
const imgCanvas = document.createElement("canvas");
|
||||
imgCanvas.width = imageData.width;
|
||||
imgCanvas.height = imageData.height;
|
||||
const imgContext = imgCanvas.getContext("2d")!;
|
||||
imgContext.putImageData(imageData, 0, 0);
|
||||
|
||||
context.drawImage(imgCanvas, 0, 0, element.width, element.height);
|
||||
} else {
|
||||
drawImagePlaceholder(element, context, renderConfig.zoom.value);
|
||||
const img = isInitializedImageElement(element)
|
||||
? renderConfig.imageCache.get(element.fileId)?.image
|
||||
: undefined;
|
||||
if (img != null && !(img instanceof Promise)) {
|
||||
context.drawImage(
|
||||
img,
|
||||
0 /* hardcoded for the selection box*/,
|
||||
0,
|
||||
element.width,
|
||||
element.height,
|
||||
);
|
||||
} else {
|
||||
drawImagePlaceholder(element, context, renderConfig.zoom.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -410,23 +423,23 @@ const generateElementShape = (
|
||||
topY + (rightY - topY) * 0.25
|
||||
} L ${rightX - (rightX - topX) * 0.25} ${
|
||||
rightY - (rightY - topY) * 0.25
|
||||
}
|
||||
}
|
||||
C ${rightX} ${rightY}, ${rightX} ${rightY}, ${
|
||||
rightX - (rightX - bottomX) * 0.25
|
||||
} ${rightY + (bottomY - rightY) * 0.25}
|
||||
} ${rightY + (bottomY - rightY) * 0.25}
|
||||
L ${bottomX + (rightX - bottomX) * 0.25} ${
|
||||
bottomY - (bottomY - rightY) * 0.25
|
||||
}
|
||||
}
|
||||
C ${bottomX} ${bottomY}, ${bottomX} ${bottomY}, ${
|
||||
bottomX - (bottomX - leftX) * 0.25
|
||||
} ${bottomY - (bottomY - leftY) * 0.25}
|
||||
} ${bottomY - (bottomY - leftY) * 0.25}
|
||||
L ${leftX + (bottomX - leftX) * 0.25} ${
|
||||
leftY + (bottomY - leftY) * 0.25
|
||||
}
|
||||
}
|
||||
C ${leftX} ${leftY}, ${leftX} ${leftY}, ${
|
||||
leftX + (topX - leftX) * 0.25
|
||||
} ${leftY - (leftY - topY) * 0.25}
|
||||
L ${topX - (topX - leftX) * 0.25} ${topY + (leftY - topY) * 0.25}
|
||||
} ${leftY - (leftY - topY) * 0.25}
|
||||
L ${topX - (topX - leftX) * 0.25} ${topY + (leftY - topY) * 0.25}
|
||||
C ${topX} ${topY}, ${topX} ${topY}, ${
|
||||
topX + (rightX - topX) * 0.25
|
||||
} ${topY + (rightY - topY) * 0.25}`,
|
||||
@ -608,7 +621,10 @@ const generateElementWithCanvas = (
|
||||
if (
|
||||
!prevElementWithCanvas ||
|
||||
shouldRegenerateBecauseZoom ||
|
||||
prevElementWithCanvas.theme !== renderConfig.theme
|
||||
prevElementWithCanvas.theme !== renderConfig.theme ||
|
||||
(renderConfig.editingImageElement &&
|
||||
isImageElement(element) &&
|
||||
element.id === renderConfig.editingImageElement.elementId)
|
||||
) {
|
||||
const elementWithCanvas = generateElementCanvas(
|
||||
element,
|
||||
|
@ -4,15 +4,13 @@ import {
|
||||
NonDeleted,
|
||||
} from "../element/types";
|
||||
import { getNonDeletedElements, isNonDeletedElement } from "../element";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
|
||||
type ElementKey = ExcalidrawElement | ElementIdKey;
|
||||
type ElementKey = ExcalidrawElement | ExcalidrawElement["id"];
|
||||
|
||||
type SceneStateCallback = () => void;
|
||||
type SceneStateCallbackRemover = () => void;
|
||||
|
||||
const isIdKey = (elementKey: ElementKey): elementKey is ElementIdKey => {
|
||||
const isIdKey = (elementKey: ElementKey): elementKey is string => {
|
||||
if (typeof elementKey === "string") {
|
||||
return true;
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ export const exportToCanvas = async (
|
||||
renderSelection: false,
|
||||
renderGrid: false,
|
||||
isExporting: true,
|
||||
editingImageElement: null,
|
||||
});
|
||||
|
||||
return canvas;
|
||||
|
@ -11,6 +11,7 @@ export type RenderConfig = {
|
||||
zoom: AppState["zoom"];
|
||||
shouldCacheIgnoreZoom: AppState["shouldCacheIgnoreZoom"];
|
||||
theme: AppState["theme"];
|
||||
editingImageElement: AppState["editingImageElement"];
|
||||
// collab-related state
|
||||
// ---------------------------------------------------------------------------
|
||||
remotePointerViewportCoords: { [id: string]: { x: number; y: number } };
|
||||
|
@ -29,6 +29,8 @@ import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||
import Library from "./data/library";
|
||||
import type { FileSystemHandle } from "./data/filesystem";
|
||||
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||
import { EditingImageElement } from "./element/imageEditor";
|
||||
import Scene from "./scene/Scene";
|
||||
|
||||
export type Point = Readonly<RoughPoint>;
|
||||
|
||||
@ -77,6 +79,7 @@ export type AppState = {
|
||||
// (e.g. text element when typing into the input)
|
||||
editingElement: NonDeletedExcalidrawElement | null;
|
||||
editingLinearElement: LinearElementEditor | null;
|
||||
editingImageElement: EditingImageElement | null;
|
||||
elementType: typeof SHAPES[number]["value"];
|
||||
elementLocked: boolean;
|
||||
exportBackground: boolean;
|
||||
@ -316,6 +319,8 @@ export type AppClassProperties = {
|
||||
}
|
||||
>;
|
||||
files: BinaryFiles;
|
||||
scene: Scene;
|
||||
addFiles: App["addFiles"];
|
||||
};
|
||||
|
||||
export type PointerDownState = Readonly<{
|
||||
|
Loading…
x
Reference in New Issue
Block a user