feat: dark theme export background

This commit is contained in:
Arnošt Pleskot 2023-08-13 20:00:17 +02:00
parent baa133cbb7
commit 787f5d68cf
No known key found for this signature in database
11 changed files with 172 additions and 75 deletions

View File

@ -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({

View File

@ -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 = <

View File

@ -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,
); );
} }
}} }}

View File

@ -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">

View File

@ -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;

View File

@ -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,
); );

View File

@ -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();

View File

@ -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 = (

View File

@ -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");

View File

@ -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);

View File

@ -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<