Compare commits
1 Commits
master
...
upload-ima
Author | SHA1 | Date | |
---|---|---|---|
|
0cb67a0bc9 |
@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { fileOpen } from "browser-nativefs";
|
||||
import socketIOClient from "socket.io-client";
|
||||
import rough from "roughjs/bin/rough";
|
||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||
@ -7,6 +8,7 @@ import { simplify, Point } from "points-on-curve";
|
||||
import { FlooredNumber, SocketUpdateData } from "../types";
|
||||
|
||||
import {
|
||||
newImageElement,
|
||||
newElement,
|
||||
newTextElement,
|
||||
duplicateElement,
|
||||
@ -2058,6 +2060,30 @@ class App extends React.Component<any, AppState> {
|
||||
editingElement: element,
|
||||
});
|
||||
}
|
||||
} else if (this.state.elementType === "image") {
|
||||
const element = newImageElement({
|
||||
x: x,
|
||||
y: y,
|
||||
strokeColor: this.state.currentItemStrokeColor,
|
||||
backgroundColor: this.state.currentItemBackgroundColor,
|
||||
fillStyle: this.state.currentItemFillStyle,
|
||||
strokeWidth: this.state.currentItemStrokeWidth,
|
||||
roughness: this.state.currentItemRoughness,
|
||||
opacity: this.state.currentItemOpacity,
|
||||
});
|
||||
this.setState(() => ({
|
||||
selectedElementIds: {
|
||||
[element.id]: true,
|
||||
},
|
||||
}));
|
||||
globalSceneState.replaceAllElements([
|
||||
...globalSceneState.getElementsIncludingDeleted(),
|
||||
element,
|
||||
]);
|
||||
this.setState({
|
||||
draggingElement: element,
|
||||
editingElement: element,
|
||||
});
|
||||
} else {
|
||||
const element = newElement({
|
||||
type: this.state.elementType,
|
||||
@ -2313,7 +2339,7 @@ class App extends React.Component<any, AppState> {
|
||||
}
|
||||
});
|
||||
|
||||
const onPointerUp = withBatchedUpdates((childEvent: PointerEvent) => {
|
||||
const onPointerUp = withBatchedUpdates(async (childEvent: PointerEvent) => {
|
||||
const {
|
||||
draggingElement,
|
||||
resizingElement,
|
||||
@ -2338,6 +2364,24 @@ class App extends React.Component<any, AppState> {
|
||||
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||
window.removeEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||
|
||||
if (draggingElement?.type === "image") {
|
||||
const selectedFile = await fileOpen({
|
||||
description: "Image",
|
||||
extensions: ["jpg", "jpeg", "png"],
|
||||
mimeTypes: ["image/jpeg", "image/png"],
|
||||
});
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
mutateElement(draggingElement, {
|
||||
imageData: reader.result as string,
|
||||
});
|
||||
this.actionManager.executeAction(actionFinalize);
|
||||
};
|
||||
reader.readAsDataURL(selectedFile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (draggingElement?.type === "draw") {
|
||||
this.actionManager.executeAction(actionFinalize);
|
||||
return;
|
||||
|
@ -25,6 +25,7 @@ function isElementDraggableFromInside(
|
||||
): boolean {
|
||||
const dragFromInside =
|
||||
element.backgroundColor !== "transparent" ||
|
||||
element.type === "image" ||
|
||||
appState.selectedElementIds[element.id];
|
||||
if (element.type === "line" || element.type === "draw") {
|
||||
return dragFromInside && isPathALoop(element.points);
|
||||
@ -89,7 +90,7 @@ export function hitTest(
|
||||
);
|
||||
}
|
||||
return Math.hypot(a * tx - px, b * ty - py) < lineThreshold;
|
||||
} else if (element.type === "rectangle") {
|
||||
} else if (element.type === "rectangle" || element.type === "image") {
|
||||
if (isElementDraggableFromInside(element, appState)) {
|
||||
return (
|
||||
x > x1 - lineThreshold &&
|
||||
|
@ -9,6 +9,7 @@ export {
|
||||
newElement,
|
||||
newTextElement,
|
||||
newLinearElement,
|
||||
newImageElement,
|
||||
duplicateElement,
|
||||
} from "./newElement";
|
||||
export {
|
||||
|
@ -19,7 +19,7 @@ export function mutateElement<TElement extends Mutable<ExcalidrawElement>>(
|
||||
) {
|
||||
// casting to any because can't use `in` operator
|
||||
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
||||
const { points } = updates as any;
|
||||
const { points, imageData } = updates as any;
|
||||
|
||||
if (typeof points !== "undefined") {
|
||||
updates = { ...getSizeFromPoints(points), ...updates };
|
||||
@ -36,6 +36,7 @@ export function mutateElement<TElement extends Mutable<ExcalidrawElement>>(
|
||||
if (
|
||||
typeof updates.height !== "undefined" ||
|
||||
typeof updates.width !== "undefined" ||
|
||||
typeof imageData !== "undefined" ||
|
||||
typeof points !== "undefined"
|
||||
) {
|
||||
invalidateShapeForElement(element);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawGenericElement,
|
||||
@ -110,6 +111,15 @@ export function newLinearElement(
|
||||
};
|
||||
}
|
||||
|
||||
export function newImageElement(
|
||||
opts: ElementConstructorOpts,
|
||||
): NonDeleted<ExcalidrawImageElement> {
|
||||
return {
|
||||
..._newElementBase<ExcalidrawImageElement>("image", opts),
|
||||
imageData: "",
|
||||
};
|
||||
}
|
||||
|
||||
// Simplified deep clone for the purpose of cloning ExcalidrawElement only
|
||||
// (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
|
||||
//
|
||||
|
@ -31,7 +31,8 @@ export type ExcalidrawGenericElement = _ExcalidrawElementBase & {
|
||||
export type ExcalidrawElement =
|
||||
| ExcalidrawGenericElement
|
||||
| ExcalidrawTextElement
|
||||
| ExcalidrawLinearElement;
|
||||
| ExcalidrawLinearElement
|
||||
| ExcalidrawImageElement;
|
||||
|
||||
export type NonDeleted<TElement extends ExcalidrawElement> = TElement & {
|
||||
isDeleted: false;
|
||||
@ -55,6 +56,12 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase &
|
||||
lastCommittedPoint?: Point | null;
|
||||
}>;
|
||||
|
||||
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
||||
Readonly<{
|
||||
type: "image";
|
||||
imageData: string;
|
||||
}>;
|
||||
|
||||
export type PointerType = "mouse" | "pen" | "touch";
|
||||
|
||||
export type TextAlign = "left" | "center" | "right";
|
||||
|
@ -96,6 +96,7 @@
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selection",
|
||||
"image": "Image",
|
||||
"draw": "Free draw",
|
||||
"rectangle": "Rectangle",
|
||||
"diamond": "Diamond",
|
||||
|
@ -95,6 +95,20 @@ function drawElementOnCanvas(
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
context.drawImage(
|
||||
img,
|
||||
20 /* hardcoded for the selection box*/,
|
||||
20,
|
||||
element.width,
|
||||
element.height,
|
||||
);
|
||||
};
|
||||
img.src = element.imageData;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (isTextElement(element)) {
|
||||
const font = context.font;
|
||||
@ -271,6 +285,11 @@ function generateElement(
|
||||
shape = [];
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
// just to ensure we don't regenerate element.canvas on rerenders
|
||||
shape = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
shapeCache.set(element, shape);
|
||||
}
|
||||
@ -345,7 +364,8 @@ export function renderElement(
|
||||
case "line":
|
||||
case "draw":
|
||||
case "arrow":
|
||||
case "text": {
|
||||
case "text":
|
||||
case "image": {
|
||||
const elementWithCanvas = generateElement(element, generator, sceneState);
|
||||
|
||||
if (renderOptimizations) {
|
||||
|
@ -93,6 +93,19 @@ export const SHAPES = [
|
||||
value: "text",
|
||||
key: "t",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
// fa-image
|
||||
<svg viewBox="0 0 512 512">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
value: "image",
|
||||
key: "i",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const shapesShortcutKeys = SHAPES.map((shape, index) => [
|
||||
|
Loading…
x
Reference in New Issue
Block a user