feat: dark theme export background
This commit is contained in:
parent
baa133cbb7
commit
787f5d68cf
@ -25,7 +25,7 @@ import { nativeFileSystemSupported } from "../data/filesystem";
|
|||||||
import { Theme } from "../element/types";
|
import { Theme } from "../element/types";
|
||||||
|
|
||||||
import "../components/ToolIcon.scss";
|
import "../components/ToolIcon.scss";
|
||||||
import Select from "../components/Select";
|
import Select, { convertToSelectItems } from "../components/Select";
|
||||||
|
|
||||||
export const actionChangeProjectName = register({
|
export const actionChangeProjectName = register({
|
||||||
name: "changeProjectName",
|
name: "changeProjectName",
|
||||||
@ -119,21 +119,27 @@ export const actionChangeFancyBackgroundImageUrl = register({
|
|||||||
trackEvent: { category: "export", action: "toggleBackgroundImage" },
|
trackEvent: { category: "export", action: "toggleBackgroundImage" },
|
||||||
perform: (_elements, appState, value) => {
|
perform: (_elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
appState: { ...appState, fancyBackgroundImageUrl: value },
|
appState: { ...appState, fancyBackgroundImageKey: value },
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData }) => {
|
||||||
<Select
|
const items = convertToSelectItems(
|
||||||
items={FANCY_BACKGROUND_IMAGES}
|
FANCY_BACKGROUND_IMAGES,
|
||||||
ariaLabel={t("imageExportDialog.label.backgroundImage")}
|
(item) => item.label,
|
||||||
placeholder={t("imageExportDialog.label.backgroundImage")}
|
);
|
||||||
value={DEFAULT_FANCY_BACKGROUND_IMAGE}
|
return (
|
||||||
onChange={(value) => {
|
<Select
|
||||||
updateData(value);
|
items={items}
|
||||||
}}
|
ariaLabel={t("imageExportDialog.label.backgroundImage")}
|
||||||
/>
|
placeholder={t("imageExportDialog.label.backgroundImage")}
|
||||||
),
|
value={DEFAULT_FANCY_BACKGROUND_IMAGE}
|
||||||
|
onChange={(value) => {
|
||||||
|
updateData(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionChangeExportEmbedScene = register({
|
export const actionChangeExportEmbedScene = register({
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
FANCY_BACKGROUND_IMAGES,
|
|
||||||
EXPORT_SCALES,
|
EXPORT_SCALES,
|
||||||
THEME,
|
THEME,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
@ -101,8 +100,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
pendingImageElementId: null,
|
pendingImageElementId: null,
|
||||||
showHyperlinkPopup: false,
|
showHyperlinkPopup: false,
|
||||||
selectedLinearElement: null,
|
selectedLinearElement: null,
|
||||||
fancyBackgroundImageUrl:
|
fancyBackgroundImageKey: DEFAULT_FANCY_BACKGROUND_IMAGE,
|
||||||
FANCY_BACKGROUND_IMAGES[DEFAULT_FANCY_BACKGROUND_IMAGE].path,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,7 +208,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
pendingImageElementId: { browser: false, export: false, server: false },
|
pendingImageElementId: { browser: false, export: false, server: false },
|
||||||
showHyperlinkPopup: { browser: false, export: false, server: false },
|
showHyperlinkPopup: { browser: false, export: false, server: false },
|
||||||
selectedLinearElement: { browser: true, export: false, server: false },
|
selectedLinearElement: { browser: true, export: false, server: false },
|
||||||
fancyBackgroundImageUrl: { browser: false, export: false, server: false },
|
fancyBackgroundImageKey: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
|
@ -38,7 +38,7 @@ import { Tooltip } from "./Tooltip";
|
|||||||
import "./ImageExportDialog.scss";
|
import "./ImageExportDialog.scss";
|
||||||
import { useAppProps } from "./App";
|
import { useAppProps } from "./App";
|
||||||
import { FilledButton } from "./FilledButton";
|
import { FilledButton } from "./FilledButton";
|
||||||
import Select from "./Select";
|
import Select, { convertToSelectItems } from "./Select";
|
||||||
|
|
||||||
const supportsContextFilters =
|
const supportsContextFilters =
|
||||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||||
@ -69,6 +69,11 @@ function isBackgroundImageKey(
|
|||||||
return key in FANCY_BACKGROUND_IMAGES;
|
return key in FANCY_BACKGROUND_IMAGES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const backgroundSelectItems = convertToSelectItems(
|
||||||
|
FANCY_BACKGROUND_IMAGES,
|
||||||
|
(item) => item.label,
|
||||||
|
);
|
||||||
|
|
||||||
const ImageExportModal = ({
|
const ImageExportModal = ({
|
||||||
appState,
|
appState,
|
||||||
elements,
|
elements,
|
||||||
@ -138,7 +143,7 @@ const ImageExportModal = ({
|
|||||||
}, [
|
}, [
|
||||||
appState,
|
appState,
|
||||||
appState.exportBackground,
|
appState.exportBackground,
|
||||||
appState.fancyBackgroundImageUrl,
|
appState.fancyBackgroundImageKey,
|
||||||
files,
|
files,
|
||||||
exportedElements,
|
exportedElements,
|
||||||
]);
|
]);
|
||||||
@ -150,7 +155,9 @@ const ImageExportModal = ({
|
|||||||
<div
|
<div
|
||||||
className={clsx("ImageExportModal__preview__canvas", {
|
className={clsx("ImageExportModal__preview__canvas", {
|
||||||
"ImageExportModal__preview__canvas--img-bcg":
|
"ImageExportModal__preview__canvas--img-bcg":
|
||||||
appState.exportBackground && appState.fancyBackgroundImageUrl,
|
appState.exportBackground &&
|
||||||
|
appState.fancyBackgroundImageKey &&
|
||||||
|
appState.fancyBackgroundImageKey !== "solid",
|
||||||
})}
|
})}
|
||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
>
|
>
|
||||||
@ -199,7 +206,7 @@ const ImageExportModal = ({
|
|||||||
>
|
>
|
||||||
{exportWithBackground && (
|
{exportWithBackground && (
|
||||||
<Select
|
<Select
|
||||||
items={FANCY_BACKGROUND_IMAGES}
|
items={backgroundSelectItems}
|
||||||
ariaLabel={t("imageExportDialog.label.backgroundImage")}
|
ariaLabel={t("imageExportDialog.label.backgroundImage")}
|
||||||
placeholder={t("imageExportDialog.label.backgroundImage")}
|
placeholder={t("imageExportDialog.label.backgroundImage")}
|
||||||
value={exportBackgroundImage}
|
value={exportBackgroundImage}
|
||||||
@ -209,7 +216,7 @@ const ImageExportModal = ({
|
|||||||
actionManager.executeAction(
|
actionManager.executeAction(
|
||||||
actionChangeFancyBackgroundImageUrl,
|
actionChangeFancyBackgroundImageUrl,
|
||||||
"ui",
|
"ui",
|
||||||
FANCY_BACKGROUND_IMAGES[value].path,
|
value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -4,23 +4,39 @@ import * as RadixSelect from "@radix-ui/react-select";
|
|||||||
import "./Select.scss";
|
import "./Select.scss";
|
||||||
import { tablerChevronDownIcon, tablerChevronUpIcon } from "./icons";
|
import { tablerChevronDownIcon, tablerChevronUpIcon } from "./icons";
|
||||||
|
|
||||||
type SelectItems = Record<string, { path: string | null; label: string }>;
|
type SelectItems<T extends string> = Record<T, string>;
|
||||||
|
|
||||||
export type SelectProps = {
|
export type SelectProps<T extends string> = {
|
||||||
items: SelectItems;
|
items: SelectItems<T>;
|
||||||
value: keyof SelectItems;
|
value: T;
|
||||||
onChange: (value: keyof SelectItems) => void;
|
onChange: (value: T) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Select = ({
|
type ConverterFunction<T> = (
|
||||||
|
items: Record<string, T>,
|
||||||
|
getLabel: (item: T) => string,
|
||||||
|
) => SelectItems<string>;
|
||||||
|
|
||||||
|
export const convertToSelectItems: ConverterFunction<any> = (
|
||||||
|
items,
|
||||||
|
getLabel,
|
||||||
|
) => {
|
||||||
|
const result: SelectItems<string> = {};
|
||||||
|
for (const key in items) {
|
||||||
|
result[key] = getLabel(items[key]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Select = <T extends string>({
|
||||||
items,
|
items,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder,
|
placeholder,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
}: SelectProps) => (
|
}: SelectProps<T>) => (
|
||||||
<RadixSelect.Root value={value} onValueChange={onChange}>
|
<RadixSelect.Root value={value} onValueChange={onChange}>
|
||||||
<RadixSelect.Trigger
|
<RadixSelect.Trigger
|
||||||
className="Select__trigger"
|
className="Select__trigger"
|
||||||
@ -41,11 +57,13 @@ const Select = ({
|
|||||||
</RadixSelect.ScrollUpButton>
|
</RadixSelect.ScrollUpButton>
|
||||||
|
|
||||||
<RadixSelect.Viewport className="Select__viewport">
|
<RadixSelect.Viewport className="Select__viewport">
|
||||||
{Object.entries(items).map(([itemValue, itemLabel]) => (
|
{(Object.entries(items) as [T, string][]).map(
|
||||||
<SelectItem value={itemValue} key={itemValue}>
|
([itemValue, itemLabel]) => (
|
||||||
{itemLabel.label}
|
<SelectItem value={itemValue} key={itemValue}>
|
||||||
</SelectItem>
|
{itemLabel}
|
||||||
))}
|
</SelectItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</RadixSelect.Viewport>
|
</RadixSelect.Viewport>
|
||||||
|
|
||||||
<RadixSelect.ScrollDownButton className="Select__scroll-button">
|
<RadixSelect.ScrollDownButton className="Select__scroll-button">
|
||||||
|
@ -188,6 +188,13 @@ export const ACTIVE_THRESHOLD = 3_000;
|
|||||||
|
|
||||||
export const THEME_FILTER = cssVariables.themeFilter;
|
export const THEME_FILTER = cssVariables.themeFilter;
|
||||||
|
|
||||||
|
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||||
|
// as a temp hack to make images in dark theme look closer to original
|
||||||
|
// color scheme (it's still not quite there and the colors look slightly
|
||||||
|
// desatured, alas...)
|
||||||
|
export const IMAGE_INVERT_FILTER =
|
||||||
|
"invert(100%) hue-rotate(180deg) saturate(1.25)";
|
||||||
|
|
||||||
export const URL_QUERY_KEYS = {
|
export const URL_QUERY_KEYS = {
|
||||||
addLibrary: "addLibrary",
|
addLibrary: "addLibrary",
|
||||||
} as const;
|
} as const;
|
||||||
@ -320,16 +327,33 @@ export const DEFAULT_SIDEBAR = {
|
|||||||
export const LIBRARY_DISABLED_TYPES = new Set(["embeddable", "image"] as const);
|
export const LIBRARY_DISABLED_TYPES = new Set(["embeddable", "image"] as const);
|
||||||
|
|
||||||
export const FANCY_BACKGROUND_IMAGES = {
|
export const FANCY_BACKGROUND_IMAGES = {
|
||||||
solid: { path: null, label: "solid color" },
|
solid: { light: null, dark: null, label: "solid color" },
|
||||||
bubbles: { path: "/backgrounds/bubbles.svg" as DataURL, label: "bubbles" },
|
bubbles: {
|
||||||
|
light: "/backgrounds/bubbles.svg" as DataURL,
|
||||||
|
dark: "/backgrounds/bubbles_dark.svg" as DataURL,
|
||||||
|
label: "bubbles",
|
||||||
|
},
|
||||||
bubbles2: {
|
bubbles2: {
|
||||||
path: "/backgrounds/bubbles2.svg" as DataURL,
|
light: "/backgrounds/bubbles2.svg" as DataURL,
|
||||||
|
dark: "/backgrounds/bubbles2_dark.svg" as DataURL,
|
||||||
label: "bubbles 2",
|
label: "bubbles 2",
|
||||||
},
|
},
|
||||||
bricks: { path: "/backgrounds/bricks.svg" as DataURL, label: "bricks" },
|
bricks: {
|
||||||
lines: { path: "/backgrounds/lines.svg" as DataURL, label: "lines" },
|
light: "/backgrounds/bricks.svg" as DataURL,
|
||||||
lines2: { path: "/backgrounds/lines2.svg" as DataURL, label: "lines 2" },
|
dark: "/backgrounds/bricks_dark.svg" as DataURL,
|
||||||
|
label: "bricks",
|
||||||
|
},
|
||||||
|
lines: {
|
||||||
|
light: "/backgrounds/lines.svg" as DataURL,
|
||||||
|
dark: "/backgrounds/lines_dark.svg" as DataURL,
|
||||||
|
label: "lines",
|
||||||
|
},
|
||||||
|
lines2: {
|
||||||
|
light: "/backgrounds/lines2.svg" as DataURL,
|
||||||
|
dark: "/backgrounds/lines2_dark.svg" as DataURL,
|
||||||
|
label: "lines 2",
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const DEFAULT_FANCY_BACKGROUND_IMAGE: keyof typeof FANCY_BACKGROUND_IMAGES =
|
export const DEFAULT_FANCY_BACKGROUND_IMAGE: keyof typeof FANCY_BACKGROUND_IMAGES =
|
||||||
"solid" as const;
|
"bubbles" as const;
|
||||||
|
@ -47,7 +47,7 @@ export const exportCanvas = async (
|
|||||||
exportPadding,
|
exportPadding,
|
||||||
exportScale: appState.exportScale,
|
exportScale: appState.exportScale,
|
||||||
exportEmbedScene: appState.exportEmbedScene && type === "svg",
|
exportEmbedScene: appState.exportEmbedScene && type === "svg",
|
||||||
fancyBackgroundImageUrl: appState.fancyBackgroundImageUrl,
|
fancyBackgroundImageKey: appState.fancyBackgroundImageKey,
|
||||||
},
|
},
|
||||||
files,
|
files,
|
||||||
);
|
);
|
||||||
|
@ -124,9 +124,9 @@ export const normalizeSVG = async (SVGString: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadSVGElement = (filePath: string) => {
|
export const loadSVGElement = (dataURL: DataURL) => {
|
||||||
return new Promise<SVGSVGElement>((resolve, reject) => {
|
return new Promise<SVGSVGElement>((resolve, reject) => {
|
||||||
fetch(filePath)
|
fetch(dataURL)
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((svgString) => {
|
.then((svgString) => {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
@ -34,6 +34,7 @@ import { getDefaultAppState } from "../appState";
|
|||||||
import {
|
import {
|
||||||
BOUND_TEXT_PADDING,
|
BOUND_TEXT_PADDING,
|
||||||
FRAME_STYLE,
|
FRAME_STYLE,
|
||||||
|
IMAGE_INVERT_FILTER,
|
||||||
MAX_DECIMALS_FOR_SVG_EXPORT,
|
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
SVG_NS,
|
SVG_NS,
|
||||||
@ -56,12 +57,6 @@ import { getContainingFrame } from "../frame";
|
|||||||
import { normalizeLink, toValidURL } from "../data/url";
|
import { normalizeLink, toValidURL } from "../data/url";
|
||||||
import { ShapeCache } from "../scene/ShapeCache";
|
import { ShapeCache } from "../scene/ShapeCache";
|
||||||
|
|
||||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
|
||||||
// as a temp hack to make images in dark theme look closer to original
|
|
||||||
// color scheme (it's still not quite there and the colors look slightly
|
|
||||||
// desatured, alas...)
|
|
||||||
const IMAGE_INVERT_FILTER = "invert(100%) hue-rotate(180deg) saturate(1.25)";
|
|
||||||
|
|
||||||
const defaultAppState = getDefaultAppState();
|
const defaultAppState = getDefaultAppState();
|
||||||
|
|
||||||
const isPendingImageElement = (
|
const isPendingImageElement = (
|
||||||
|
@ -3,9 +3,10 @@ import { NonDeletedExcalidrawElement } from "../element/types";
|
|||||||
import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds";
|
import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds";
|
||||||
import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
|
import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
|
||||||
import { distance, isOnlyExportingSingleFrame } from "../utils";
|
import { distance, isOnlyExportingSingleFrame } from "../utils";
|
||||||
import { AppState, BinaryFiles, DataURL } from "../types";
|
import { AppState, BinaryFiles } from "../types";
|
||||||
import {
|
import {
|
||||||
DEFAULT_EXPORT_PADDING,
|
DEFAULT_EXPORT_PADDING,
|
||||||
|
FANCY_BACKGROUND_IMAGES,
|
||||||
FANCY_BG_BORDER_RADIUS,
|
FANCY_BG_BORDER_RADIUS,
|
||||||
FANCY_BG_PADDING,
|
FANCY_BG_PADDING,
|
||||||
SVG_NS,
|
SVG_NS,
|
||||||
@ -51,7 +52,8 @@ export const exportToCanvas = async (
|
|||||||
) => {
|
) => {
|
||||||
const exportWithFancyBackground =
|
const exportWithFancyBackground =
|
||||||
exportBackground &&
|
exportBackground &&
|
||||||
!!appState.fancyBackgroundImageUrl &&
|
appState.fancyBackgroundImageKey &&
|
||||||
|
appState.fancyBackgroundImageKey !== "solid" &&
|
||||||
elements.length > 0;
|
elements.length > 0;
|
||||||
const padding = !exportWithFancyBackground
|
const padding = !exportWithFancyBackground
|
||||||
? exportPadding
|
? exportPadding
|
||||||
@ -75,7 +77,7 @@ export const exportToCanvas = async (
|
|||||||
|
|
||||||
const renderConfig = {
|
const renderConfig = {
|
||||||
viewBackgroundColor:
|
viewBackgroundColor:
|
||||||
exportBackground && !appState.fancyBackgroundImageUrl
|
exportBackground && !exportWithFancyBackground
|
||||||
? viewBackgroundColor
|
? viewBackgroundColor
|
||||||
: null,
|
: null,
|
||||||
scrollX: -minX + (onlyExportingSingleFrame ? 0 : padding),
|
scrollX: -minX + (onlyExportingSingleFrame ? 0 : padding),
|
||||||
@ -92,15 +94,19 @@ export const exportToCanvas = async (
|
|||||||
renderSelection: false,
|
renderSelection: false,
|
||||||
renderGrid: false,
|
renderGrid: false,
|
||||||
isExporting: true,
|
isExporting: true,
|
||||||
exportBackgroundImage: appState.fancyBackgroundImageUrl,
|
exportBackgroundImage: appState.fancyBackgroundImageKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (exportWithFancyBackground) {
|
if (
|
||||||
|
exportWithFancyBackground &&
|
||||||
|
appState.fancyBackgroundImageKey !== "solid"
|
||||||
|
) {
|
||||||
await applyFancyBackgroundOnCanvas({
|
await applyFancyBackgroundOnCanvas({
|
||||||
canvas,
|
canvas,
|
||||||
fancyBackgroundImageUrl: appState.fancyBackgroundImageUrl!,
|
fancyBackgroundImageKey: appState.fancyBackgroundImageKey,
|
||||||
backgroundColor: viewBackgroundColor,
|
backgroundColor: viewBackgroundColor,
|
||||||
exportScale: appState.exportScale,
|
exportScale: appState.exportScale,
|
||||||
|
theme: renderConfig.theme,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +145,7 @@ export const exportToSvg = async (
|
|||||||
exportWithDarkMode?: boolean;
|
exportWithDarkMode?: boolean;
|
||||||
exportEmbedScene?: boolean;
|
exportEmbedScene?: boolean;
|
||||||
renderFrame?: boolean;
|
renderFrame?: boolean;
|
||||||
fancyBackgroundImageUrl: DataURL | null;
|
fancyBackgroundImageKey?: keyof typeof FANCY_BACKGROUND_IMAGES;
|
||||||
},
|
},
|
||||||
files: BinaryFiles | null,
|
files: BinaryFiles | null,
|
||||||
opts?: {
|
opts?: {
|
||||||
@ -157,8 +163,9 @@ export const exportToSvg = async (
|
|||||||
|
|
||||||
const exportWithFancyBackground =
|
const exportWithFancyBackground =
|
||||||
exportBackground &&
|
exportBackground &&
|
||||||
!!appState.fancyBackgroundImageUrl &&
|
elements.length > 0 &&
|
||||||
elements.length > 0;
|
appState.fancyBackgroundImageKey &&
|
||||||
|
appState.fancyBackgroundImageKey !== "solid";
|
||||||
|
|
||||||
const padding = !exportWithFancyBackground
|
const padding = !exportWithFancyBackground
|
||||||
? exportPadding
|
? exportPadding
|
||||||
@ -191,7 +198,8 @@ export const exportToSvg = async (
|
|||||||
svgRoot.setAttribute("filter", THEME_FILTER);
|
svgRoot.setAttribute("filter", THEME_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
let assetPath = "https://excalidraw.com/";
|
// let assetPath = "https://excalidraw.com/";
|
||||||
|
let assetPath = "http://localhost:3000/";
|
||||||
// Asset path needs to be determined only when using package
|
// Asset path needs to be determined only when using package
|
||||||
if (import.meta.env.VITE_IS_EXCALIDRAW_NPM_PACKAGE) {
|
if (import.meta.env.VITE_IS_EXCALIDRAW_NPM_PACKAGE) {
|
||||||
assetPath =
|
assetPath =
|
||||||
@ -258,14 +266,17 @@ export const exportToSvg = async (
|
|||||||
|
|
||||||
// render background rect
|
// render background rect
|
||||||
if (appState.exportBackground && viewBackgroundColor) {
|
if (appState.exportBackground && viewBackgroundColor) {
|
||||||
if (appState.fancyBackgroundImageUrl) {
|
if (
|
||||||
|
appState.fancyBackgroundImageKey &&
|
||||||
|
appState.fancyBackgroundImageKey !== "solid"
|
||||||
|
) {
|
||||||
await applyFancyBackgroundOnSvg({
|
await applyFancyBackgroundOnSvg({
|
||||||
svgRoot,
|
svgRoot,
|
||||||
fancyBackgroundImageUrl:
|
fancyBackgroundImageKey: `${appState.fancyBackgroundImageKey}`,
|
||||||
`${appState.fancyBackgroundImageUrl}` as DataURL,
|
|
||||||
backgroundColor: viewBackgroundColor,
|
backgroundColor: viewBackgroundColor,
|
||||||
dimensions: { w: width, h: height },
|
dimensions: { w: width, h: height },
|
||||||
exportScale,
|
exportScale,
|
||||||
|
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
import { FANCY_BG_BORDER_RADIUS, FANCY_BG_PADDING, SVG_NS } from "../constants";
|
import {
|
||||||
|
FANCY_BACKGROUND_IMAGES,
|
||||||
|
FANCY_BG_BORDER_RADIUS,
|
||||||
|
FANCY_BG_PADDING,
|
||||||
|
IMAGE_INVERT_FILTER,
|
||||||
|
SVG_NS,
|
||||||
|
THEME,
|
||||||
|
THEME_FILTER,
|
||||||
|
} from "../constants";
|
||||||
import { loadHTMLImageElement, loadSVGElement } from "../element/image";
|
import { loadHTMLImageElement, loadSVGElement } from "../element/image";
|
||||||
import { roundRect } from "../renderer/roundRect";
|
import { roundRect } from "../renderer/roundRect";
|
||||||
import { AppState, DataURL } from "../types";
|
import { AppState } from "../types";
|
||||||
|
|
||||||
type Dimensions = { w: number; h: number };
|
type Dimensions = { w: number; h: number };
|
||||||
|
|
||||||
@ -62,6 +70,7 @@ const addContentBackground = (
|
|||||||
normalizedDimensions: Dimensions,
|
normalizedDimensions: Dimensions,
|
||||||
contentBackgroundColor: string,
|
contentBackgroundColor: string,
|
||||||
exportScale: AppState["exportScale"],
|
exportScale: AppState["exportScale"],
|
||||||
|
theme: AppState["theme"],
|
||||||
) => {
|
) => {
|
||||||
const shadows = [
|
const shadows = [
|
||||||
{
|
{
|
||||||
@ -113,6 +122,9 @@ const addContentBackground = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (index === shadows.length - 1) {
|
if (index === shadows.length - 1) {
|
||||||
|
if (theme === THEME.DARK) {
|
||||||
|
context.filter = THEME_FILTER;
|
||||||
|
}
|
||||||
context.fillStyle = contentBackgroundColor;
|
context.fillStyle = contentBackgroundColor;
|
||||||
context.fill();
|
context.fill();
|
||||||
}
|
}
|
||||||
@ -123,17 +135,25 @@ const addContentBackground = (
|
|||||||
|
|
||||||
export const applyFancyBackgroundOnCanvas = async ({
|
export const applyFancyBackgroundOnCanvas = async ({
|
||||||
canvas,
|
canvas,
|
||||||
fancyBackgroundImageUrl,
|
fancyBackgroundImageKey,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
exportScale,
|
exportScale,
|
||||||
|
theme,
|
||||||
}: {
|
}: {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
fancyBackgroundImageUrl: DataURL;
|
fancyBackgroundImageKey: Exclude<
|
||||||
|
keyof typeof FANCY_BACKGROUND_IMAGES,
|
||||||
|
"solid"
|
||||||
|
>;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
exportScale: AppState["exportScale"];
|
exportScale: AppState["exportScale"];
|
||||||
|
theme: AppState["theme"];
|
||||||
}) => {
|
}) => {
|
||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
|
|
||||||
|
const fancyBackgroundImageUrl =
|
||||||
|
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
|
||||||
|
|
||||||
const fancyBackgroundImage = await loadHTMLImageElement(
|
const fancyBackgroundImage = await loadHTMLImageElement(
|
||||||
fancyBackgroundImageUrl,
|
fancyBackgroundImageUrl,
|
||||||
);
|
);
|
||||||
@ -142,31 +162,45 @@ export const applyFancyBackgroundOnCanvas = async ({
|
|||||||
|
|
||||||
addImageBackground(context, canvasDimensions, fancyBackgroundImage);
|
addImageBackground(context, canvasDimensions, fancyBackgroundImage);
|
||||||
|
|
||||||
addContentBackground(context, canvasDimensions, backgroundColor, exportScale);
|
addContentBackground(
|
||||||
|
context,
|
||||||
|
canvasDimensions,
|
||||||
|
backgroundColor,
|
||||||
|
exportScale,
|
||||||
|
theme,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const applyFancyBackgroundOnSvg = async ({
|
export const applyFancyBackgroundOnSvg = async ({
|
||||||
svgRoot,
|
svgRoot,
|
||||||
fancyBackgroundImageUrl,
|
fancyBackgroundImageKey,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
dimensions,
|
dimensions,
|
||||||
exportScale,
|
exportScale,
|
||||||
|
theme,
|
||||||
}: {
|
}: {
|
||||||
svgRoot: SVGSVGElement;
|
svgRoot: SVGSVGElement;
|
||||||
fancyBackgroundImageUrl: DataURL;
|
fancyBackgroundImageKey: Exclude<
|
||||||
|
keyof typeof FANCY_BACKGROUND_IMAGES,
|
||||||
|
"solid"
|
||||||
|
>;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
dimensions: Dimensions;
|
dimensions: Dimensions;
|
||||||
exportScale: AppState["exportScale"];
|
exportScale: AppState["exportScale"];
|
||||||
|
theme: AppState["theme"];
|
||||||
}) => {
|
}) => {
|
||||||
const fancyBackgroundImage = await loadSVGElement(
|
const fancyBackgroundImageUrl =
|
||||||
`${fancyBackgroundImageUrl}`,
|
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
|
||||||
);
|
const fancyBackgroundImage = await loadSVGElement(fancyBackgroundImageUrl);
|
||||||
|
|
||||||
fancyBackgroundImage.setAttribute("x", "0");
|
fancyBackgroundImage.setAttribute("x", "0");
|
||||||
fancyBackgroundImage.setAttribute("y", "0");
|
fancyBackgroundImage.setAttribute("y", "0");
|
||||||
fancyBackgroundImage.setAttribute("width", `${dimensions.w}`);
|
fancyBackgroundImage.setAttribute("width", `${dimensions.w}`);
|
||||||
fancyBackgroundImage.setAttribute("height", `${dimensions.h}`);
|
fancyBackgroundImage.setAttribute("height", `${dimensions.h}`);
|
||||||
fancyBackgroundImage.setAttribute("preserveAspectRatio", "none");
|
fancyBackgroundImage.setAttribute("preserveAspectRatio", "none");
|
||||||
|
if (theme === THEME.DARK) {
|
||||||
|
fancyBackgroundImage.setAttribute("filter", IMAGE_INVERT_FILTER);
|
||||||
|
}
|
||||||
|
|
||||||
svgRoot.appendChild(fancyBackgroundImage);
|
svgRoot.appendChild(fancyBackgroundImage);
|
||||||
|
|
||||||
|
@ -32,7 +32,11 @@ import { isOverScrollBars } from "./scene";
|
|||||||
import { MaybeTransformHandleType } from "./element/transformHandles";
|
import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||||
import Library from "./data/library";
|
import Library from "./data/library";
|
||||||
import type { FileSystemHandle } from "./data/filesystem";
|
import type { FileSystemHandle } from "./data/filesystem";
|
||||||
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
import type {
|
||||||
|
FANCY_BACKGROUND_IMAGES,
|
||||||
|
IMAGE_MIME_TYPES,
|
||||||
|
MIME_TYPES,
|
||||||
|
} from "./constants";
|
||||||
import { ContextMenuItems } from "./components/ContextMenu";
|
import { ContextMenuItems } from "./components/ContextMenu";
|
||||||
import { Merge, ForwardRef, ValueOf } from "./utility-types";
|
import { Merge, ForwardRef, ValueOf } from "./utility-types";
|
||||||
|
|
||||||
@ -287,7 +291,7 @@ export type AppState = {
|
|||||||
pendingImageElementId: ExcalidrawImageElement["id"] | null;
|
pendingImageElementId: ExcalidrawImageElement["id"] | null;
|
||||||
showHyperlinkPopup: false | "info" | "editor";
|
showHyperlinkPopup: false | "info" | "editor";
|
||||||
selectedLinearElement: LinearElementEditor | null;
|
selectedLinearElement: LinearElementEditor | null;
|
||||||
fancyBackgroundImageUrl: DataURL | null;
|
fancyBackgroundImageKey: keyof typeof FANCY_BACKGROUND_IMAGES;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UIAppState = Omit<
|
export type UIAppState = Omit<
|
||||||
|
Loading…
x
Reference in New Issue
Block a user