Regenerate roughjs shape only when the item is updated (#316)

* Regenerate roughjs shape only when the item is updated

* Remove shape object during export and history undo/redo

* Remove shape element during copying

* Fix shape generation during creation
This commit is contained in:
Gasim Gasimzada 2020-01-12 04:00:00 +04:00 committed by Christopher Chedeau
parent 1bf18fe4ed
commit 74764b06eb
7 changed files with 109 additions and 71 deletions

View File

@ -24,6 +24,7 @@ export const actionChangeStrokeColor: Action = {
return {
elements: changeProperty(elements, el => ({
...el,
shape: null,
strokeColor: value
})),
appState: { ...appState, currentItemStrokeColor: value }
@ -50,6 +51,7 @@ export const actionChangeBackgroundColor: Action = {
return {
elements: changeProperty(elements, el => ({
...el,
shape: null,
backgroundColor: value
})),
appState: { ...appState, currentItemBackgroundColor: value }
@ -76,6 +78,7 @@ export const actionChangeFillStyle: Action = {
return {
elements: changeProperty(elements, el => ({
...el,
shape: null,
fillStyle: value
}))
};
@ -104,6 +107,7 @@ export const actionChangeStrokeWidth: Action = {
return {
elements: changeProperty(elements, el => ({
...el,
shape: null,
strokeWidth: value
}))
};
@ -130,6 +134,7 @@ export const actionChangeSloppiness: Action = {
return {
elements: changeProperty(elements, el => ({
...el,
shape: null,
roughness: value
}))
};
@ -156,6 +161,7 @@ export const actionChangeOpacity: Action = {
return {
elements: changeProperty(elements, el => ({
...el,
shape: null,
opacity: value
}))
};
@ -185,6 +191,7 @@ export const actionChangeFontSize: Action = {
if (isTextElement(el)) {
const element: ExcalidrawTextElement = {
...el,
shape: null,
font: `${value}px ${el.font.split("px ")[1]}`
};
redrawTextBoundingBox(element);
@ -223,6 +230,7 @@ export const actionChangeFontFamily: Action = {
if (isTextElement(el)) {
const element: ExcalidrawTextElement = {
...el,
shape: null,
font: `${el.font.split("px ")[0]}px ${value}`
};
redrawTextBoundingBox(element);

View File

@ -27,6 +27,7 @@ export const actionPasteStyles: Action = {
if (element.isSelected) {
const newElement = {
...element,
shape: null,
backgroundColor: pastedElement?.backgroundColor,
strokeWidth: pastedElement?.strokeWidth,
strokeColor: pastedElement?.strokeColor,

View File

@ -1,5 +1,6 @@
import { randomSeed } from "../random";
import nanoid from "nanoid";
import { Drawable } from "roughjs/bin/core";
export function newElement(
type: string,
@ -28,7 +29,8 @@ export function newElement(
roughness,
opacity,
isSelected: false,
seed: randomSeed()
seed: randomSeed(),
shape: null as Drawable | Drawable[] | null
};
return element;
}

View File

@ -7,7 +7,10 @@ class SceneHistory {
generateCurrentEntry(elements: readonly ExcalidrawElement[]) {
return JSON.stringify(
elements.map(element => ({ ...element, isSelected: false }))
elements.map(({ shape, ...element }) => ({
...element,
isSelected: false
}))
);
}

View File

@ -279,7 +279,9 @@ export class App extends React.Component<{}, AppState> {
private copyToClipboard = () => {
if (navigator.clipboard) {
const text = JSON.stringify(
elements.filter(element => element.isSelected)
elements
.filter(element => element.isSelected)
.map(({ shape, ...el }) => el)
);
navigator.clipboard.writeText(text);
}
@ -303,7 +305,11 @@ export class App extends React.Component<{}, AppState> {
onCut={e => {
e.clipboardData.setData(
"text/plain",
JSON.stringify(elements.filter(element => element.isSelected))
JSON.stringify(
elements
.filter(element => element.isSelected)
.map(({ shape, ...el }) => el)
)
);
elements = deleteSelectedElements(elements);
this.forceUpdate();
@ -312,7 +318,11 @@ export class App extends React.Component<{}, AppState> {
onCopy={e => {
e.clipboardData.setData(
"text/plain",
JSON.stringify(elements.filter(element => element.isSelected))
JSON.stringify(
elements
.filter(element => element.isSelected)
.map(({ shape, ...el }) => el)
)
);
e.preventDefault();
}}
@ -465,6 +475,7 @@ export class App extends React.Component<{}, AppState> {
1,
100
);
type ResizeTestType = ReturnType<typeof resizeTest>;
let resizeHandle: ResizeTestType = false;
let isResizingElements = false;
@ -670,6 +681,7 @@ export class App extends React.Component<{}, AppState> {
el.x = element.x;
el.y = element.y;
el.shape = null;
});
lastX = x;
lastY = y;
@ -705,6 +717,7 @@ export class App extends React.Component<{}, AppState> {
// otherwise we would read a stale one!
const draggingElement = this.state.draggingElement;
if (!draggingElement) return;
let width =
e.clientX -
CANVAS_WINDOW_OFFSET_LEFT -
@ -720,6 +733,7 @@ export class App extends React.Component<{}, AppState> {
draggingElement.height = e.shiftKey
? Math.abs(width) * Math.sign(height)
: height;
draggingElement.shape = null;
if (this.state.elementType === "selection") {
elements = setSelection(elements, draggingElement);

View File

@ -4,6 +4,7 @@ import { ExcalidrawElement } from "../element/types";
import { isTextElement } from "../element/typeChecks";
import { getDiamondPoints, getArrowPoints } from "../element/bounds";
import { RoughCanvas } from "roughjs/bin/canvas";
import { Drawable } from "roughjs/bin/core";
export function renderElement(
element: ExcalidrawElement,
@ -17,69 +18,76 @@ export function renderElement(
context.fillRect(0, 0, element.width, element.height);
context.fillStyle = fillStyle;
} else if (element.type === "rectangle") {
const shape = withCustomMathRandom(element.seed, () => {
return generator.rectangle(0, 0, element.width, element.height, {
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
if (!element.shape) {
element.shape = withCustomMathRandom(element.seed, () => {
return generator.rectangle(0, 0, element.width, element.height, {
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
});
});
});
}
context.globalAlpha = element.opacity / 100;
rc.draw(shape);
rc.draw(element.shape as Drawable);
context.globalAlpha = 1;
} else if (element.type === "diamond") {
const shape = withCustomMathRandom(element.seed, () => {
const [
topX,
topY,
rightX,
rightY,
bottomX,
bottomY,
leftX,
leftY
] = getDiamondPoints(element);
return generator.polygon(
[
[topX, topY],
[rightX, rightY],
[bottomX, bottomY],
[leftX, leftY]
],
{
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}
);
});
context.globalAlpha = element.opacity / 100;
rc.draw(shape);
context.globalAlpha = 1;
} else if (element.type === "ellipse") {
const shape = withCustomMathRandom(element.seed, () =>
generator.ellipse(
element.width / 2,
element.height / 2,
element.width,
element.height,
{
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}
)
);
if (!element.shape) {
element.shape = withCustomMathRandom(element.seed, () => {
const [
topX,
topY,
rightX,
rightY,
bottomX,
bottomY,
leftX,
leftY
] = getDiamondPoints(element);
return generator.polygon(
[
[topX, topY],
[rightX, rightY],
[bottomX, bottomY],
[leftX, leftY]
],
{
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}
);
});
}
context.globalAlpha = element.opacity / 100;
rc.draw(shape);
rc.draw(element.shape as Drawable);
context.globalAlpha = 1;
} else if (element.type === "ellipse") {
if (!element.shape) {
element.shape = withCustomMathRandom(element.seed, () =>
generator.ellipse(
element.width / 2,
element.height / 2,
element.width,
element.height,
{
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}
)
);
}
context.globalAlpha = element.opacity / 100;
rc.draw(element.shape as Drawable);
context.globalAlpha = 1;
} else if (element.type === "arrow") {
const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
@ -89,17 +97,19 @@ export function renderElement(
roughness: element.roughness
};
const shapes = withCustomMathRandom(element.seed, () => [
// \
generator.line(x3, y3, x2, y2, options),
// -----
generator.line(x1, y1, x2, y2, options),
// /
generator.line(x4, y4, x2, y2, options)
]);
if (!element.shape) {
element.shape = withCustomMathRandom(element.seed, () => [
// \
generator.line(x3, y3, x2, y2, options),
// -----
generator.line(x1, y1, x2, y2, options),
// /
generator.line(x4, y4, x2, y2, options)
]);
}
context.globalAlpha = element.opacity / 100;
shapes.forEach(shape => rc.draw(shape));
(element.shape as Drawable[]).forEach(shape => rc.draw(shape));
context.globalAlpha = 1;
return;
} else if (isTextElement(element)) {

View File

@ -35,7 +35,7 @@ export function saveAsJSON(
const serialized = JSON.stringify({
version: 1,
source: window.location.origin,
elements
elements: elements.map(({ shape, ...el }) => el)
});
saveFile(