Skip to content

Commit e041714

Browse files
committed
[pr] export rewrite
1 parent a897052 commit e041714

File tree

31 files changed

+1111
-526
lines changed

31 files changed

+1111
-526
lines changed

examples/with-script-in-browser/components/ExampleApp.tsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,12 @@ export default function ExampleApp({
370370
return false;
371371
}
372372
await exportToClipboard({
373-
elements: excalidrawAPI.getSceneElements(),
374-
appState: excalidrawAPI.getAppState(),
375-
files: excalidrawAPI.getFiles(),
376-
type,
373+
data: {
374+
elements: excalidrawAPI.getSceneElements(),
375+
appState: excalidrawAPI.getAppState(),
376+
files: excalidrawAPI.getFiles(),
377+
},
378+
type: "json",
377379
});
378380
window.alert(`Copied to clipboard as ${type} successfully`);
379381
};
@@ -826,15 +828,17 @@ export default function ExampleApp({
826828
return;
827829
}
828830
const svg = await exportToSvg({
829-
elements: excalidrawAPI?.getSceneElements(),
830-
appState: {
831-
...initialData.appState,
832-
exportWithDarkMode,
833-
exportEmbedScene,
834-
width: 300,
835-
height: 100,
831+
data: {
832+
elements: excalidrawAPI?.getSceneElements(),
833+
appState: {
834+
...initialData.appState,
835+
exportWithDarkMode,
836+
exportEmbedScene,
837+
width: 300,
838+
height: 100,
839+
},
840+
files: excalidrawAPI?.getFiles(),
836841
},
837-
files: excalidrawAPI?.getFiles(),
838842
});
839843
appRef.current.querySelector(".export-svg").innerHTML =
840844
svg.outerHTML;
@@ -850,14 +854,18 @@ export default function ExampleApp({
850854
return;
851855
}
852856
const blob = await exportToBlob({
853-
elements: excalidrawAPI?.getSceneElements(),
854-
mimeType: "image/png",
855-
appState: {
856-
...initialData.appState,
857-
exportEmbedScene,
858-
exportWithDarkMode,
857+
data: {
858+
elements: excalidrawAPI?.getSceneElements(),
859+
appState: {
860+
...initialData.appState,
861+
exportEmbedScene,
862+
exportWithDarkMode,
863+
},
864+
files: excalidrawAPI?.getFiles(),
865+
},
866+
config: {
867+
mimeType: "image/png",
859868
},
860-
files: excalidrawAPI?.getFiles(),
861869
});
862870
setBlobUrl(window.URL.createObjectURL(blob));
863871
}}

packages/common/src/colors.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import oc from "open-color";
22

3+
import {
4+
COLOR_WHITE,
5+
COLOR_CHARCOAL_BLACK,
6+
COLOR_TRANSPARENT,
7+
} from "./constants";
8+
39
import type { Merge } from "./utility-types";
410

511
export const COLOR_OUTLINE_CONTRAST_THRESHOLD = 240;
@@ -18,13 +24,18 @@ const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
1824
};
1925

2026
export type ColorPickerColor =
21-
| Exclude<keyof oc, "indigo" | "lime">
27+
| Exclude<keyof oc, "indigo" | "lime" | "black">
2228
| "transparent"
29+
| "charcoal"
2330
| "bronze";
2431
export type ColorTuple = readonly [string, string, string, string, string];
2532
export type ColorPalette = Merge<
2633
Record<ColorPickerColor, ColorTuple>,
27-
{ black: "#1e1e1e"; white: "#ffffff"; transparent: "transparent" }
34+
{
35+
charcoal: typeof COLOR_CHARCOAL_BLACK;
36+
white: typeof COLOR_WHITE;
37+
transparent: typeof COLOR_TRANSPARENT;
38+
}
2839
>;
2940

3041
// used general type instead of specific type (ColorPalette) to support custom colors
@@ -44,17 +55,17 @@ export const CANVAS_PALETTE_SHADE_INDEXES = [0, 1, 2, 3, 4] as const;
4455
export const getSpecificColorShades = (
4556
color: Exclude<
4657
ColorPickerColor,
47-
"transparent" | "white" | "black" | "bronze"
58+
"transparent" | "charcoal" | "black" | "white" | "bronze"
4859
>,
4960
indexArr: Readonly<ColorShadesIndexes>,
5061
) => {
5162
return indexArr.map((index) => oc[color][index]) as any as ColorTuple;
5263
};
5364

5465
export const COLOR_PALETTE = {
55-
transparent: "transparent",
56-
black: "#1e1e1e",
57-
white: "#ffffff",
66+
transparent: COLOR_TRANSPARENT,
67+
charcoal: COLOR_CHARCOAL_BLACK,
68+
white: COLOR_WHITE,
5869
// open-colors
5970
gray: getSpecificColorShades("gray", ELEMENTS_PALETTE_SHADE_INDEXES),
6071
red: getSpecificColorShades("red", ELEMENTS_PALETTE_SHADE_INDEXES),
@@ -90,7 +101,7 @@ const COMMON_ELEMENT_SHADES = pick(COLOR_PALETTE, [
90101

91102
// ORDER matters for positioning in quick picker
92103
export const DEFAULT_ELEMENT_STROKE_PICKS = [
93-
COLOR_PALETTE.black,
104+
COLOR_PALETTE.charcoal,
94105
COLOR_PALETTE.red[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
95106
COLOR_PALETTE.green[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
96107
COLOR_PALETTE.blue[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
@@ -128,7 +139,7 @@ export const DEFAULT_ELEMENT_STROKE_COLOR_PALETTE = {
128139
transparent: COLOR_PALETTE.transparent,
129140
white: COLOR_PALETTE.white,
130141
gray: COLOR_PALETTE.gray,
131-
black: COLOR_PALETTE.black,
142+
charcoal: COLOR_PALETTE.charcoal,
132143
bronze: COLOR_PALETTE.bronze,
133144
// rest
134145
...COMMON_ELEMENT_SHADES,
@@ -139,7 +150,7 @@ export const DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE = {
139150
transparent: COLOR_PALETTE.transparent,
140151
white: COLOR_PALETTE.white,
141152
gray: COLOR_PALETTE.gray,
142-
black: COLOR_PALETTE.black,
153+
charcoal: COLOR_PALETTE.charcoal,
143154
bronze: COLOR_PALETTE.bronze,
144155

145156
...COMMON_ELEMENT_SHADES,

packages/common/src/constants.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import type {
22
ExcalidrawElement,
33
FontFamilyValues,
44
} from "@excalidraw/element/types";
5-
import type { AppProps, AppState } from "@excalidraw/excalidraw/types";
6-
7-
import { COLOR_PALETTE } from "./colors";
5+
import type {
6+
AppProps,
7+
AppState,
8+
NormalizedZoomValue,
9+
} from "@excalidraw/excalidraw/types";
810

911
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
1012
export const isWindows = /^Win/.test(navigator.platform);
@@ -231,6 +233,14 @@ export const DEFAULT_TEXT_ALIGN = "left";
231233
export const DEFAULT_VERTICAL_ALIGN = "top";
232234
export const DEFAULT_VERSION = "{version}";
233235
export const DEFAULT_TRANSFORM_HANDLE_SPACING = 2;
236+
export const DEFAULT_ZOOM_VALUE = 1 as NormalizedZoomValue;
237+
238+
// -----------------------------------------------
239+
// !!! these colors are tied to color picker !!!
240+
export const COLOR_WHITE = "#ffffff";
241+
export const COLOR_CHARCOAL_BLACK = "#1e1e1e";
242+
export const COLOR_TRANSPARENT = "transparent";
243+
// -----------------------------------------------
234244

235245
export const SIDE_RESIZING_THRESHOLD = 2 * DEFAULT_TRANSFORM_HANDLE_SPACING;
236246
// a small epsilon to make side resizing always take precedence
@@ -239,8 +249,6 @@ export const EPSILON = 0.00001;
239249
export const DEFAULT_COLLISION_THRESHOLD =
240250
2 * SIDE_RESIZING_THRESHOLD - EPSILON;
241251

242-
export const COLOR_WHITE = "#ffffff";
243-
export const COLOR_CHARCOAL_BLACK = "#1e1e1e";
244252
// keep this in sync with CSS
245253
export const COLOR_VOICE_CALL = "#a2f1a6";
246254

@@ -458,8 +466,8 @@ export const DEFAULT_ELEMENT_PROPS: {
458466
opacity: ExcalidrawElement["opacity"];
459467
locked: ExcalidrawElement["locked"];
460468
} = {
461-
strokeColor: COLOR_PALETTE.black,
462-
backgroundColor: COLOR_PALETTE.transparent,
469+
strokeColor: COLOR_CHARCOAL_BLACK,
470+
backgroundColor: COLOR_TRANSPARENT,
463471
fillStyle: "solid",
464472
strokeWidth: 2,
465473
strokeStyle: "solid",

packages/common/src/utils.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import type {
1414
Zoom,
1515
} from "@excalidraw/excalidraw/types";
1616

17-
import { COLOR_PALETTE } from "./colors";
1817
import {
18+
COLOR_TRANSPARENT,
1919
DEFAULT_VERSION,
2020
ENV,
2121
FONT_FAMILY,
@@ -567,11 +567,7 @@ export const mapFind = <T, K>(
567567
export const isTransparent = (color: string) => {
568568
const isRGBTransparent = color.length === 5 && color.substr(4, 1) === "0";
569569
const isRRGGBBTransparent = color.length === 9 && color.substr(7, 2) === "00";
570-
return (
571-
isRGBTransparent ||
572-
isRRGGBBTransparent ||
573-
color === COLOR_PALETTE.transparent
574-
);
570+
return isRGBTransparent || isRRGGBBTransparent || color === COLOR_TRANSPARENT;
575571
};
576572

577573
export const isBindingFallthroughEnabled = (el: ExcalidrawBindableElement) =>

packages/element/src/ShapeCache.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { RoughGenerator } from "roughjs/bin/generator";
2+
3+
import { COLOR_PALETTE } from "@excalidraw/common";
4+
5+
import type { EmbedsValidationStatus } from "@excalidraw/excalidraw/types";
6+
import type {
7+
ElementShape,
8+
ElementShapes,
9+
StaticCanvasRenderConfig,
10+
} from "@excalidraw/excalidraw/scene/types";
11+
12+
import { _generateElementShape } from "./Shape";
13+
14+
import { elementWithCanvasCache } from "./renderElement";
15+
16+
import type { ExcalidrawElement, ExcalidrawSelectionElement } from "./types";
17+
18+
import type { Drawable } from "roughjs/bin/core";
19+
20+
export class ShapeCache {
21+
private static rg = new RoughGenerator();
22+
private static cache = new WeakMap<ExcalidrawElement, ElementShape>();
23+
24+
/**
25+
* Retrieves shape from cache if available. Use this only if shape
26+
* is optional and you have a fallback in case it's not cached.
27+
*/
28+
public static get = <T extends ExcalidrawElement>(element: T) => {
29+
return ShapeCache.cache.get(
30+
element,
31+
) as T["type"] extends keyof ElementShapes
32+
? ElementShapes[T["type"]] | undefined
33+
: ElementShape | undefined;
34+
};
35+
36+
public static set = <T extends ExcalidrawElement>(
37+
element: T,
38+
shape: T["type"] extends keyof ElementShapes
39+
? ElementShapes[T["type"]]
40+
: Drawable,
41+
) => ShapeCache.cache.set(element, shape);
42+
43+
public static delete = (element: ExcalidrawElement) =>
44+
ShapeCache.cache.delete(element);
45+
46+
public static destroy = () => {
47+
ShapeCache.cache = new WeakMap();
48+
};
49+
50+
/**
51+
* Generates & caches shape for element if not already cached, otherwise
52+
* returns cached shape.
53+
*/
54+
public static generateElementShape = <
55+
T extends Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
56+
>(
57+
element: T,
58+
renderConfig: {
59+
isExporting: boolean;
60+
canvasBackgroundColor: StaticCanvasRenderConfig["canvasBackgroundColor"];
61+
embedsValidationStatus: EmbedsValidationStatus;
62+
} | null,
63+
) => {
64+
// when exporting, always regenerated to guarantee the latest shape
65+
const cachedShape = renderConfig?.isExporting
66+
? undefined
67+
: ShapeCache.get(element);
68+
69+
// `null` indicates no rc shape applicable for this element type,
70+
// but it's considered a valid cache value (= do not regenerate)
71+
if (cachedShape !== undefined) {
72+
return cachedShape;
73+
}
74+
75+
elementWithCanvasCache.delete(element);
76+
77+
const shape = _generateElementShape(
78+
element,
79+
ShapeCache.rg,
80+
renderConfig || {
81+
isExporting: false,
82+
canvasBackgroundColor: COLOR_PALETTE.white,
83+
embedsValidationStatus: null,
84+
},
85+
) as T["type"] extends keyof ElementShapes
86+
? ElementShapes[T["type"]]
87+
: Drawable | null;
88+
89+
ShapeCache.cache.set(element, shape);
90+
91+
return shape;
92+
};
93+
}

packages/element/src/shape.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,11 @@ import type { GlobalPoint } from "@excalidraw/math";
2929

3030
import type { Mutable } from "@excalidraw/common/utility-types";
3131

32-
import type {
33-
AppState,
34-
EmbedsValidationStatus,
35-
} from "@excalidraw/excalidraw/types";
32+
import type { EmbedsValidationStatus } from "@excalidraw/excalidraw/types";
3633
import type {
3734
ElementShape,
3835
ElementShapes,
36+
StaticCanvasRenderConfig,
3937
} from "@excalidraw/excalidraw/scene/types";
4038

4139
import { elementWithCanvasCache } from "./renderElement";
@@ -115,7 +113,7 @@ export class ShapeCache {
115113
element: T,
116114
renderConfig: {
117115
isExporting: boolean;
118-
canvasBackgroundColor: AppState["viewBackgroundColor"];
116+
canvasBackgroundColor: StaticCanvasRenderConfig["canvasBackgroundColor"];
119117
embedsValidationStatus: EmbedsValidationStatus;
120118
} | null,
121119
) => {
@@ -283,8 +281,10 @@ const getArrowheadShapes = (
283281
arrowhead: Arrowhead,
284282
generator: RoughGenerator,
285283
options: Options,
286-
canvasBackgroundColor: string,
284+
canvasBackgroundColor: string | null,
287285
) => {
286+
canvasBackgroundColor = canvasBackgroundColor || "transparent";
287+
288288
const arrowheadPoints = getArrowheadPoints(
289289
element,
290290
shape,
@@ -611,7 +611,7 @@ const generateElementShape = (
611611
embedsValidationStatus,
612612
}: {
613613
isExporting: boolean;
614-
canvasBackgroundColor: string;
614+
canvasBackgroundColor: string | null;
615615
embedsValidationStatus: EmbedsValidationStatus | null;
616616
},
617617
): Drawable | Drawable[] | null => {

0 commit comments

Comments
 (0)