feat: exporting redesign (#3613)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
parent
357266e9ab
commit
790c9fd02e
@ -11,7 +11,8 @@ import { t } from "../i18n";
|
|||||||
import { useIsMobile } from "../components/App";
|
import { useIsMobile } from "../components/App";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { supported } from "browser-fs-access";
|
import { supported as fsSupported } from "browser-fs-access";
|
||||||
|
import { CheckboxItem } from "../components/CheckboxItem";
|
||||||
|
|
||||||
export const actionChangeProjectName = register({
|
export const actionChangeProjectName = register({
|
||||||
name: "changeProjectName",
|
name: "changeProjectName",
|
||||||
@ -40,14 +41,12 @@ export const actionChangeExportBackground = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => (
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
<label>
|
<CheckboxItem
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={appState.exportBackground}
|
checked={appState.exportBackground}
|
||||||
onChange={(event) => updateData(event.target.checked)}
|
onChange={(checked) => updateData(checked)}
|
||||||
/>{" "}
|
>
|
||||||
{t("labels.withBackground")}
|
{t("labels.withBackground")}
|
||||||
</label>
|
</CheckboxItem>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,17 +59,15 @@ export const actionChangeExportEmbedScene = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => (
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
<label style={{ display: "flex" }}>
|
<CheckboxItem
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={appState.exportEmbedScene}
|
checked={appState.exportEmbedScene}
|
||||||
onChange={(event) => updateData(event.target.checked)}
|
onChange={(checked) => updateData(checked)}
|
||||||
/>{" "}
|
>
|
||||||
{t("labels.exportEmbedScene")}
|
{t("labels.exportEmbedScene")}
|
||||||
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
||||||
<div className="TooltipIcon">{questionCircle}</div>
|
<div className="Tooltip-icon">{questionCircle}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</label>
|
</CheckboxItem>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,14 +80,12 @@ export const actionChangeShouldAddWatermark = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => (
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
<label>
|
<CheckboxItem
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={appState.shouldAddWatermark}
|
checked={appState.shouldAddWatermark}
|
||||||
onChange={(event) => updateData(event.target.checked)}
|
onChange={(checked) => updateData(checked)}
|
||||||
/>{" "}
|
>
|
||||||
{t("labels.addWatermark")}
|
{t("labels.addWatermark")}
|
||||||
</label>
|
</CheckboxItem>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -126,11 +121,10 @@ export const actionSaveScene = register({
|
|||||||
event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
|
event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="icon"
|
||||||
icon={save}
|
icon={save}
|
||||||
title={t("buttons.save")}
|
title={t("buttons.save")}
|
||||||
aria-label={t("buttons.save")}
|
aria-label={t("buttons.save")}
|
||||||
showAriaLabel={useIsMobile()}
|
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
data-testid="save-button"
|
data-testid="save-button"
|
||||||
/>
|
/>
|
||||||
@ -162,7 +156,7 @@ export const actionSaveAsScene = register({
|
|||||||
title={t("buttons.saveAs")}
|
title={t("buttons.saveAs")}
|
||||||
aria-label={t("buttons.saveAs")}
|
aria-label={t("buttons.saveAs")}
|
||||||
showAriaLabel={useIsMobile()}
|
showAriaLabel={useIsMobile()}
|
||||||
hidden={!supported}
|
hidden={!fsSupported}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
data-testid="save-as-button"
|
data-testid="save-as-button"
|
||||||
/>
|
/>
|
||||||
|
@ -131,4 +131,5 @@ export interface ActionsManagerInterface {
|
|||||||
registerAction: (action: Action) => void;
|
registerAction: (action: Action) => void;
|
||||||
handleKeyDown: (event: React.KeyboardEvent | KeyboardEvent) => boolean;
|
handleKeyDown: (event: React.KeyboardEvent | KeyboardEvent) => boolean;
|
||||||
renderAction: (name: ActionName) => React.ReactElement | null;
|
renderAction: (name: ActionName) => React.ReactElement | null;
|
||||||
|
executeAction: (action: Action) => void;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import React, { useContext } from "react";
|
|||||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { supported } from "browser-fs-access";
|
import { supported as fsSupported } from "browser-fs-access";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -3885,7 +3885,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// default: assume an Excalidraw file regardless of extension/MimeType
|
// default: assume an Excalidraw file regardless of extension/MimeType
|
||||||
} else {
|
} else {
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
if (supported) {
|
if (fsSupported) {
|
||||||
try {
|
try {
|
||||||
// This will only work as of Chrome 86,
|
// This will only work as of Chrome 86,
|
||||||
// but can be safely ignored on older releases.
|
// but can be safely ignored on older releases.
|
||||||
|
@ -15,6 +15,11 @@ export const BackgroundPickerAndDarkModeToggle = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||||
{showThemeBtn && <>{actionManager.renderAction("toggleTheme")}</>}
|
{showThemeBtn && actionManager.renderAction("toggleTheme")}
|
||||||
|
{appState.fileHandle && (
|
||||||
|
<div style={{ marginInlineStart: "0.25rem" }}>
|
||||||
|
{actionManager.renderAction("saveScene")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
53
src/components/Card.scss
Normal file
53
src/components/Card.scss
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.Card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
max-width: 290px;
|
||||||
|
|
||||||
|
margin: 1em;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.Card-icon {
|
||||||
|
font-size: 2.6em;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 1.4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--card-color);
|
||||||
|
color: $oc-white;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 2.8rem;
|
||||||
|
height: 2.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Card-details {
|
||||||
|
font-size: 0.96em;
|
||||||
|
min-height: 90px;
|
||||||
|
padding: 0 1em;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .Card-button.ToolIcon_type_button {
|
||||||
|
height: 2.5rem;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
background-color: var(--card-color);
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--card-color-darker);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--card-color-darkest);
|
||||||
|
}
|
||||||
|
.ToolIcon__label {
|
||||||
|
color: $oc-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/components/Card.tsx
Normal file
20
src/components/Card.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import OpenColor from "open-color";
|
||||||
|
|
||||||
|
import "./Card.scss";
|
||||||
|
|
||||||
|
export const Card: React.FC<{
|
||||||
|
color: keyof OpenColor;
|
||||||
|
}> = ({ children, color }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="Card"
|
||||||
|
style={{
|
||||||
|
["--card-color" as any]: OpenColor[color][7],
|
||||||
|
["--card-color-darker" as any]: OpenColor[color][8],
|
||||||
|
["--card-color-darkest" as any]: OpenColor[color][9],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
85
src/components/CheckboxItem.scss
Normal file
85
src/components/CheckboxItem.scss
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.Checkbox {
|
||||||
|
margin: 3px 0.3em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover:not(.is-checked) .Checkbox-box {
|
||||||
|
box-shadow: 0 0 0 2px #{$oc-blue-4};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
.Checkbox-box {
|
||||||
|
box-shadow: 0 0 2px 1px inset #{$oc-blue-7} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.Checkbox-box {
|
||||||
|
background-color: fade-out($oc-blue-1, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-checked {
|
||||||
|
.Checkbox-box {
|
||||||
|
background-color: #{$oc-blue-1};
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover .Checkbox-box {
|
||||||
|
background-color: #{$oc-blue-2};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Checkbox-box {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
margin: 0 1em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
box-shadow: 0 0 0 2px #{$oc-blue-7};
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
color: #{$oc-blue-7};
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 3px #{$oc-blue-7};
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tooltip-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/components/CheckboxItem.tsx
Normal file
26
src/components/CheckboxItem.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
import { checkIcon } from "./icons";
|
||||||
|
|
||||||
|
import "./CheckboxItem.scss";
|
||||||
|
|
||||||
|
export const CheckboxItem: React.FC<{
|
||||||
|
checked: boolean;
|
||||||
|
onChange: (checked: boolean) => void;
|
||||||
|
}> = ({ children, checked, onChange }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("Checkbox", { "is-checked": checked })}
|
||||||
|
onClick={(event) => {
|
||||||
|
onChange(!checked);
|
||||||
|
((event.currentTarget as HTMLDivElement).querySelector(
|
||||||
|
".Checkbox-box",
|
||||||
|
) as HTMLButtonElement).focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button className="Checkbox-box" role="checkbox" aria-checked={checked}>
|
||||||
|
{checkIcon}
|
||||||
|
</button>
|
||||||
|
<div className="Checkbox-label">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -160,7 +160,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-input {
|
.color-picker-input {
|
||||||
width: 12ch; /* length of `transparent` + 1 */
|
width: 11ch; /* length of `transparent` */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
background-color: var(--input-bg-color);
|
background-color: var(--input-bg-color);
|
||||||
|
@ -2,6 +2,7 @@ import "./ToolIcon.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { ToolButton } from "./ToolButton";
|
||||||
|
|
||||||
export type Appearence = "light" | "dark";
|
export type Appearence = "light" | "dark";
|
||||||
|
|
||||||
@ -12,31 +13,19 @@ export const DarkModeToggle = (props: {
|
|||||||
onChange: (value: Appearence) => void;
|
onChange: (value: Appearence) => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const title = props.title
|
const title =
|
||||||
? props.title
|
props.title ||
|
||||||
: props.value === "dark"
|
(props.value === "dark" ? t("buttons.lightMode") : t("buttons.darkMode"));
|
||||||
? t("buttons.lightMode")
|
|
||||||
: t("buttons.darkMode");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<ToolButton
|
||||||
className="ToolIcon ToolIcon_type_floating ToolIcon_size_M"
|
type="icon"
|
||||||
data-testid="toggle-dark-mode"
|
icon={props.value === "light" ? ICONS.MOON : ICONS.SUN}
|
||||||
title={title}
|
title={title}
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
|
||||||
type="checkbox"
|
|
||||||
onChange={(event) =>
|
|
||||||
props.onChange(event.target.checked ? "dark" : "light")
|
|
||||||
}
|
|
||||||
checked={props.value === "dark"}
|
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
|
onClick={() => props.onChange(props.value === "dark" ? "light" : "dark")}
|
||||||
|
data-testid="toggle-dark-mode"
|
||||||
/>
|
/>
|
||||||
<div className="ToolIcon__icon">
|
|
||||||
{props.value === "light" ? ICONS.MOON : ICONS.SUN}
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,33 +28,6 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ExportDialog__name {
|
|
||||||
grid-column: project-name;
|
|
||||||
margin: auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.TextInput {
|
|
||||||
height: calc(1rem - 3px);
|
|
||||||
width: 200px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
|
||||||
margin-left: 8px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&--readonly {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
&:hover {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
width: auto;
|
|
||||||
max-width: 200px;
|
|
||||||
padding-left: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include isMobile {
|
@include isMobile {
|
||||||
.ExportDialog {
|
.ExportDialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -84,4 +57,62 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ExportDialog--json {
|
||||||
|
.ExportDialog-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
justify-items: center;
|
||||||
|
row-gap: 2em;
|
||||||
|
|
||||||
|
@media (max-width: 460px) {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
.Card-details {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProjectName {
|
||||||
|
width: fit-content;
|
||||||
|
margin: 1em auto;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.TextInput {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProjectName-label {
|
||||||
|
margin: 0.625em 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.ExportDialog-imageExportButton {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
margin: 0 0.2em;
|
||||||
|
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: var(--button-color);
|
||||||
|
box-shadow: 0 3px 5px -1px rgb(0 0 0 / 28%), 0 6px 10px 0 rgb(0 0 0 / 14%);
|
||||||
|
|
||||||
|
font-family: Cascadia;
|
||||||
|
font-size: 1.8em;
|
||||||
|
color: $oc-white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--button-color-darker);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--button-color-darkest);
|
||||||
|
box-shadow: 0 3px 5px -1px rgb(0 0 0 / 20%), 0 6px 10px 0 rgb(0 0 0 / 14%);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,16 +6,20 @@ import { canvasToBlob } from "../data/blob";
|
|||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { CanvasError } from "../errors";
|
import { CanvasError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useIsMobile } from "../components/App";
|
import { useIsMobile } from "./App";
|
||||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
import { exportToCanvas, getExportSize } from "../scene/export";
|
import { exportToCanvas, getExportSize } from "../scene/export";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import "./ExportDialog.scss";
|
import { clipboard, exportImage } from "./icons";
|
||||||
import { clipboard, exportFile, link } from "./icons";
|
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
|
|
||||||
|
import "./ExportDialog.scss";
|
||||||
|
import { supported as fsSupported } from "browser-fs-access";
|
||||||
|
import OpenColor from "open-color";
|
||||||
|
import { CheckboxItem } from "./CheckboxItem";
|
||||||
|
|
||||||
const scales = [1, 2, 3];
|
const scales = [1, 2, 3];
|
||||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
||||||
|
|
||||||
@ -52,7 +56,30 @@ export type ExportCB = (
|
|||||||
scale?: number,
|
scale?: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
const ExportModal = ({
|
const ExportButton: React.FC<{
|
||||||
|
color: keyof OpenColor;
|
||||||
|
onClick: () => void;
|
||||||
|
title: string;
|
||||||
|
shade?: number;
|
||||||
|
}> = ({ children, title, onClick, color, shade = 6 }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="ExportDialog-imageExportButton"
|
||||||
|
style={{
|
||||||
|
["--button-color" as any]: OpenColor[color][shade],
|
||||||
|
["--button-color-darker" as any]: OpenColor[color][shade + 1],
|
||||||
|
["--button-color-darkest" as any]: OpenColor[color][shade + 2],
|
||||||
|
}}
|
||||||
|
title={title}
|
||||||
|
aria-label={title}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageExportModal = ({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
exportPadding = 10,
|
exportPadding = 10,
|
||||||
@ -60,7 +87,6 @@ const ExportModal = ({
|
|||||||
onExportToPng,
|
onExportToPng,
|
||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
onExportToBackend,
|
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
@ -69,7 +95,6 @@ const ExportModal = ({
|
|||||||
onExportToPng: ExportCB;
|
onExportToPng: ExportCB;
|
||||||
onExportToSvg: ExportCB;
|
onExportToSvg: ExportCB;
|
||||||
onExportToClipboard: ExportCB;
|
onExportToClipboard: ExportCB;
|
||||||
onExportToBackend?: ExportCB;
|
|
||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
||||||
@ -133,98 +158,103 @@ const ExportModal = ({
|
|||||||
<div className="ExportDialog__preview" ref={previewRef} />
|
<div className="ExportDialog__preview" ref={previewRef} />
|
||||||
{supportsContextFilters &&
|
{supportsContextFilters &&
|
||||||
actionManager.renderAction("exportWithDarkMode")}
|
actionManager.renderAction("exportWithDarkMode")}
|
||||||
<Stack.Col gap={2} align="center">
|
<div style={{ display: "grid", gridTemplateColumns: "1fr" }}>
|
||||||
<div className="ExportDialog__actions">
|
<div
|
||||||
<Stack.Row gap={2}>
|
style={{
|
||||||
<ToolButton
|
display: "grid",
|
||||||
type="button"
|
gridTemplateColumns: "repeat(auto-fit, minmax(190px, 1fr))",
|
||||||
label="PNG"
|
// dunno why this is needed, but when the items wrap it creates
|
||||||
title={t("buttons.exportToPng")}
|
// an overflow
|
||||||
aria-label={t("buttons.exportToPng")}
|
overflow: "hidden",
|
||||||
onClick={() => onExportToPng(exportedElements, scale)}
|
}}
|
||||||
/>
|
>
|
||||||
<ToolButton
|
{actionManager.renderAction("changeExportBackground")}
|
||||||
type="button"
|
{someElementIsSelected && (
|
||||||
label="SVG"
|
<CheckboxItem
|
||||||
title={t("buttons.exportToSvg")}
|
checked={exportSelected}
|
||||||
aria-label={t("buttons.exportToSvg")}
|
onChange={(checked) => setExportSelected(checked)}
|
||||||
onClick={() => onExportToSvg(exportedElements, scale)}
|
>
|
||||||
/>
|
{t("labels.onlySelected")}
|
||||||
{probablySupportsClipboardBlob && (
|
</CheckboxItem>
|
||||||
<ToolButton
|
|
||||||
type="button"
|
|
||||||
icon={clipboard}
|
|
||||||
title={t("buttons.copyPngToClipboard")}
|
|
||||||
aria-label={t("buttons.copyPngToClipboard")}
|
|
||||||
onClick={() => onExportToClipboard(exportedElements, scale)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{onExportToBackend && (
|
{actionManager.renderAction("changeExportEmbedScene")}
|
||||||
<ToolButton
|
|
||||||
type="button"
|
|
||||||
icon={link}
|
|
||||||
title={t("buttons.getShareableLink")}
|
|
||||||
aria-label={t("buttons.getShareableLink")}
|
|
||||||
onClick={() => onExportToBackend(exportedElements)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack.Row>
|
|
||||||
<div className="ExportDialog__name">
|
|
||||||
{actionManager.renderAction("changeProjectName")}
|
|
||||||
</div>
|
</div>
|
||||||
<Stack.Row gap={2}>
|
</div>
|
||||||
{scales.map((s) => {
|
<div style={{ display: "flex", alignItems: "center", marginTop: ".6em" }}>
|
||||||
|
<Stack.Row gap={2} justifyContent={"center"}>
|
||||||
|
{scales.map((_scale) => {
|
||||||
const [width, height] = getExportSize(
|
const [width, height] = getExportSize(
|
||||||
exportedElements,
|
exportedElements,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
shouldAddWatermark,
|
shouldAddWatermark,
|
||||||
s,
|
_scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
const scaleButtonTitle = `${t(
|
const scaleButtonTitle = `${t(
|
||||||
"buttons.scale",
|
"buttons.scale",
|
||||||
)} ${s}x (${width}x${height})`;
|
)} ${_scale}x (${width}x${height})`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
key={s}
|
key={_scale}
|
||||||
size="s"
|
size="s"
|
||||||
type="radio"
|
type="radio"
|
||||||
icon={`${s}x`}
|
icon={`${_scale}x`}
|
||||||
name="export-canvas-scale"
|
name="export-canvas-scale"
|
||||||
title={scaleButtonTitle}
|
title={scaleButtonTitle}
|
||||||
aria-label={scaleButtonTitle}
|
aria-label={scaleButtonTitle}
|
||||||
id="export-canvas-scale"
|
id="export-canvas-scale"
|
||||||
checked={s === scale}
|
checked={_scale === scale}
|
||||||
onChange={() => setScale(s)}
|
onChange={() => setScale(_scale)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
|
<p style={{ marginLeft: "1em", userSelect: "none" }}>Scale</p>
|
||||||
</div>
|
</div>
|
||||||
{actionManager.renderAction("changeExportBackground")}
|
<div
|
||||||
{someElementIsSelected && (
|
style={{
|
||||||
<div>
|
display: "flex",
|
||||||
<label>
|
alignItems: "center",
|
||||||
<input
|
justifyContent: "center",
|
||||||
type="checkbox"
|
margin: ".6em 0",
|
||||||
checked={exportSelected}
|
}}
|
||||||
onChange={(event) =>
|
>
|
||||||
setExportSelected(event.currentTarget.checked)
|
{!fsSupported && actionManager.renderAction("changeProjectName")}
|
||||||
}
|
|
||||||
/>{" "}
|
|
||||||
{t("labels.onlySelected")}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Stack.Row gap={2} justifyContent="center" style={{ margin: "2em 0" }}>
|
||||||
|
<ExportButton
|
||||||
|
color="indigo"
|
||||||
|
title={t("buttons.exportToPng")}
|
||||||
|
aria-label={t("buttons.exportToPng")}
|
||||||
|
onClick={() => onExportToPng(exportedElements, scale)}
|
||||||
|
>
|
||||||
|
PNG
|
||||||
|
</ExportButton>
|
||||||
|
<ExportButton
|
||||||
|
color="red"
|
||||||
|
title={t("buttons.exportToSvg")}
|
||||||
|
aria-label={t("buttons.exportToSvg")}
|
||||||
|
onClick={() => onExportToSvg(exportedElements, scale)}
|
||||||
|
>
|
||||||
|
SVG
|
||||||
|
</ExportButton>
|
||||||
|
{probablySupportsClipboardBlob && (
|
||||||
|
<ExportButton
|
||||||
|
title={t("buttons.copyPngToClipboard")}
|
||||||
|
onClick={() => onExportToClipboard(exportedElements, scale)}
|
||||||
|
color="gray"
|
||||||
|
shade={7}
|
||||||
|
>
|
||||||
|
{clipboard}
|
||||||
|
</ExportButton>
|
||||||
)}
|
)}
|
||||||
{actionManager.renderAction("changeExportEmbedScene")}
|
</Stack.Row>
|
||||||
{actionManager.renderAction("changeShouldAddWatermark")}
|
|
||||||
</Stack.Col>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExportDialog = ({
|
export const ImageExportDialog = ({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
exportPadding = 10,
|
exportPadding = 10,
|
||||||
@ -232,7 +262,6 @@ export const ExportDialog = ({
|
|||||||
onExportToPng,
|
onExportToPng,
|
||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
onExportToBackend,
|
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
@ -241,7 +270,6 @@ export const ExportDialog = ({
|
|||||||
onExportToPng: ExportCB;
|
onExportToPng: ExportCB;
|
||||||
onExportToSvg: ExportCB;
|
onExportToSvg: ExportCB;
|
||||||
onExportToClipboard: ExportCB;
|
onExportToClipboard: ExportCB;
|
||||||
onExportToBackend?: ExportCB;
|
|
||||||
}) => {
|
}) => {
|
||||||
const [modalIsShown, setModalIsShown] = useState(false);
|
const [modalIsShown, setModalIsShown] = useState(false);
|
||||||
|
|
||||||
@ -255,16 +283,16 @@ export const ExportDialog = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalIsShown(true);
|
setModalIsShown(true);
|
||||||
}}
|
}}
|
||||||
data-testid="export-button"
|
data-testid="image-export-button"
|
||||||
icon={exportFile}
|
icon={exportImage}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t("buttons.export")}
|
aria-label={t("buttons.exportImage")}
|
||||||
showAriaLabel={useIsMobile()}
|
showAriaLabel={useIsMobile()}
|
||||||
title={t("buttons.export")}
|
title={t("buttons.exportImage")}
|
||||||
/>
|
/>
|
||||||
{modalIsShown && (
|
{modalIsShown && (
|
||||||
<Dialog onCloseRequest={handleClose} title={t("buttons.export")}>
|
<Dialog onCloseRequest={handleClose} title={t("buttons.exportImage")}>
|
||||||
<ExportModal
|
<ImageExportModal
|
||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
exportPadding={exportPadding}
|
exportPadding={exportPadding}
|
||||||
@ -272,7 +300,6 @@ export const ExportDialog = ({
|
|||||||
onExportToPng={onExportToPng}
|
onExportToPng={onExportToPng}
|
||||||
onExportToSvg={onExportToSvg}
|
onExportToSvg={onExportToSvg}
|
||||||
onExportToClipboard={onExportToClipboard}
|
onExportToClipboard={onExportToClipboard}
|
||||||
onExportToBackend={onExportToBackend}
|
|
||||||
onCloseRequest={handleClose}
|
onCloseRequest={handleClose}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
117
src/components/JSONExportDialog.tsx
Normal file
117
src/components/JSONExportDialog.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { ActionsManagerInterface } from "../actions/types";
|
||||||
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { useIsMobile } from "./App";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { Dialog } from "./Dialog";
|
||||||
|
import { exportFile, exportToFileIcon, link } from "./icons";
|
||||||
|
import { ToolButton } from "./ToolButton";
|
||||||
|
import { actionSaveAsScene } from "../actions/actionExport";
|
||||||
|
import { Card } from "./Card";
|
||||||
|
|
||||||
|
import "./ExportDialog.scss";
|
||||||
|
import { supported as fsSupported } from "browser-fs-access";
|
||||||
|
|
||||||
|
export type ExportCB = (
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
scale?: number,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
const JSONExportModal = ({
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
actionManager,
|
||||||
|
onExportToBackend,
|
||||||
|
}: {
|
||||||
|
appState: AppState;
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
actionManager: ActionsManagerInterface;
|
||||||
|
onExportToBackend?: ExportCB;
|
||||||
|
onCloseRequest: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="ExportDialog ExportDialog--json">
|
||||||
|
<div className="ExportDialog-cards">
|
||||||
|
<Card color="lime">
|
||||||
|
<div className="Card-icon">{exportToFileIcon}</div>
|
||||||
|
<h2>{t("exportDialog.disk_title")}</h2>
|
||||||
|
<div className="Card-details">
|
||||||
|
{t("exportDialog.disk_details")}
|
||||||
|
{!fsSupported && actionManager.renderAction("changeProjectName")}
|
||||||
|
</div>
|
||||||
|
<ToolButton
|
||||||
|
className="Card-button"
|
||||||
|
type="button"
|
||||||
|
title={t("exportDialog.disk_button")}
|
||||||
|
aria-label={t("exportDialog.disk_button")}
|
||||||
|
showAriaLabel={true}
|
||||||
|
onClick={() => {
|
||||||
|
actionManager.executeAction(actionSaveAsScene);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
{onExportToBackend && (
|
||||||
|
<Card color="pink">
|
||||||
|
<div className="Card-icon">{link}</div>
|
||||||
|
<h2>{t("exportDialog.link_title")}</h2>
|
||||||
|
<div className="Card-details">{t("exportDialog.link_details")}</div>
|
||||||
|
<ToolButton
|
||||||
|
className="Card-button"
|
||||||
|
type="button"
|
||||||
|
title={t("exportDialog.link_button")}
|
||||||
|
aria-label={t("exportDialog.link_button")}
|
||||||
|
showAriaLabel={true}
|
||||||
|
onClick={() => onExportToBackend(elements)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const JSONExportDialog = ({
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
actionManager,
|
||||||
|
onExportToBackend,
|
||||||
|
}: {
|
||||||
|
appState: AppState;
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
actionManager: ActionsManagerInterface;
|
||||||
|
onExportToBackend?: ExportCB;
|
||||||
|
}) => {
|
||||||
|
const [modalIsShown, setModalIsShown] = useState(false);
|
||||||
|
|
||||||
|
const handleClose = React.useCallback(() => {
|
||||||
|
setModalIsShown(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ToolButton
|
||||||
|
onClick={() => {
|
||||||
|
setModalIsShown(true);
|
||||||
|
}}
|
||||||
|
data-testid="json-export-button"
|
||||||
|
icon={exportFile}
|
||||||
|
type="button"
|
||||||
|
aria-label={t("buttons.export")}
|
||||||
|
showAriaLabel={useIsMobile()}
|
||||||
|
title={t("buttons.export")}
|
||||||
|
/>
|
||||||
|
{modalIsShown && (
|
||||||
|
<Dialog onCloseRequest={handleClose} title={t("buttons.export")}>
|
||||||
|
<JSONExportModal
|
||||||
|
elements={elements}
|
||||||
|
appState={appState}
|
||||||
|
actionManager={actionManager}
|
||||||
|
onExportToBackend={onExportToBackend}
|
||||||
|
onCloseRequest={handleClose}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -28,7 +28,7 @@ import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
|||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
import CollabButton from "./CollabButton";
|
import CollabButton from "./CollabButton";
|
||||||
import { ErrorDialog } from "./ErrorDialog";
|
import { ErrorDialog } from "./ErrorDialog";
|
||||||
import { ExportCB, ExportDialog } from "./ExportDialog";
|
import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
|
||||||
import { FixedSideContainer } from "./FixedSideContainer";
|
import { FixedSideContainer } from "./FixedSideContainer";
|
||||||
import { HintViewer } from "./HintViewer";
|
import { HintViewer } from "./HintViewer";
|
||||||
import { exportFile, load, trash } from "./icons";
|
import { exportFile, load, trash } from "./icons";
|
||||||
@ -46,6 +46,7 @@ import { ToolButton } from "./ToolButton";
|
|||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
import { UserList } from "./UserList";
|
import { UserList } from "./UserList";
|
||||||
import Library from "../data/library";
|
import Library from "../data/library";
|
||||||
|
import { JSONExportDialog } from "./JSONExportDialog";
|
||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
@ -382,7 +383,29 @@ const LayerUI = ({
|
|||||||
}: LayerUIProps) => {
|
}: LayerUIProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const renderExportDialog = () => {
|
const renderJSONExportDialog = () => {
|
||||||
|
if (!UIOptions.canvasActions.export) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JSONExportDialog
|
||||||
|
elements={elements}
|
||||||
|
appState={appState}
|
||||||
|
actionManager={actionManager}
|
||||||
|
onExportToBackend={
|
||||||
|
onExportToBackend
|
||||||
|
? (elements) => {
|
||||||
|
onExportToBackend &&
|
||||||
|
onExportToBackend(elements, appState, canvas);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderImageExportDialog = () => {
|
||||||
if (!UIOptions.canvasActions.export) {
|
if (!UIOptions.canvasActions.export) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -406,25 +429,21 @@ const LayerUI = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExportDialog
|
<ImageExportDialog
|
||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
onExportToPng={createExporter("png")}
|
onExportToPng={createExporter("png")}
|
||||||
onExportToSvg={createExporter("svg")}
|
onExportToSvg={createExporter("svg")}
|
||||||
onExportToClipboard={createExporter("clipboard")}
|
onExportToClipboard={createExporter("clipboard")}
|
||||||
onExportToBackend={
|
|
||||||
onExportToBackend
|
|
||||||
? (elements) => {
|
|
||||||
onExportToBackend &&
|
|
||||||
onExportToBackend(elements, appState, canvas);
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Separator = () => {
|
||||||
|
return <div style={{ width: ".625em" }} />;
|
||||||
|
};
|
||||||
|
|
||||||
const renderViewModeCanvasActions = () => {
|
const renderViewModeCanvasActions = () => {
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
@ -438,9 +457,8 @@ const LayerUI = ({
|
|||||||
<Island padding={2} style={{ zIndex: 1 }}>
|
<Island padding={2} style={{ zIndex: 1 }}>
|
||||||
<Stack.Col gap={4}>
|
<Stack.Col gap={4}>
|
||||||
<Stack.Row gap={1} justifyContent="space-between">
|
<Stack.Row gap={1} justifyContent="space-between">
|
||||||
{actionManager.renderAction("saveScene")}
|
{renderJSONExportDialog()}
|
||||||
{actionManager.renderAction("saveAsScene")}
|
{renderImageExportDialog()}
|
||||||
{renderExportDialog()}
|
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</Island>
|
</Island>
|
||||||
@ -459,11 +477,12 @@ const LayerUI = ({
|
|||||||
<Island padding={2} style={{ zIndex: 1 }}>
|
<Island padding={2} style={{ zIndex: 1 }}>
|
||||||
<Stack.Col gap={4}>
|
<Stack.Col gap={4}>
|
||||||
<Stack.Row gap={1} justifyContent="space-between">
|
<Stack.Row gap={1} justifyContent="space-between">
|
||||||
{actionManager.renderAction("loadScene")}
|
|
||||||
{actionManager.renderAction("saveScene")}
|
|
||||||
{actionManager.renderAction("saveAsScene")}
|
|
||||||
{renderExportDialog()}
|
|
||||||
{actionManager.renderAction("clearCanvas")}
|
{actionManager.renderAction("clearCanvas")}
|
||||||
|
<Separator />
|
||||||
|
{actionManager.renderAction("loadScene")}
|
||||||
|
{renderJSONExportDialog()}
|
||||||
|
{renderImageExportDialog()}
|
||||||
|
<Separator />
|
||||||
{onCollabButtonClick && (
|
{onCollabButtonClick && (
|
||||||
<CollabButton
|
<CollabButton
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
@ -712,7 +731,8 @@ const LayerUI = ({
|
|||||||
elements={elements}
|
elements={elements}
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
libraryMenu={libraryMenu}
|
libraryMenu={libraryMenu}
|
||||||
exportButton={renderExportDialog()}
|
renderJSONExportDialog={renderJSONExportDialog}
|
||||||
|
renderImageExportDialog={renderImageExportDialog}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
onCollabButtonClick={onCollabButtonClick}
|
onCollabButtonClick={onCollabButtonClick}
|
||||||
onLockToggle={onLockToggle}
|
onLockToggle={onLockToggle}
|
||||||
|
@ -20,7 +20,8 @@ import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkMode
|
|||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
exportButton: React.ReactNode;
|
renderJSONExportDialog: () => React.ReactNode;
|
||||||
|
renderImageExportDialog: () => React.ReactNode;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
libraryMenu: JSX.Element | null;
|
libraryMenu: JSX.Element | null;
|
||||||
@ -38,7 +39,8 @@ export const MobileMenu = ({
|
|||||||
elements,
|
elements,
|
||||||
libraryMenu,
|
libraryMenu,
|
||||||
actionManager,
|
actionManager,
|
||||||
exportButton,
|
renderJSONExportDialog,
|
||||||
|
renderImageExportDialog,
|
||||||
setAppState,
|
setAppState,
|
||||||
onCollabButtonClick,
|
onCollabButtonClick,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
@ -107,19 +109,17 @@ export const MobileMenu = ({
|
|||||||
if (viewModeEnabled) {
|
if (viewModeEnabled) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{actionManager.renderAction("saveScene")}
|
{renderJSONExportDialog()}
|
||||||
{actionManager.renderAction("saveAsScene")}
|
{renderImageExportDialog()}
|
||||||
{exportButton}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{actionManager.renderAction("loadScene")}
|
|
||||||
{actionManager.renderAction("saveScene")}
|
|
||||||
{actionManager.renderAction("saveAsScene")}
|
|
||||||
{exportButton}
|
|
||||||
{actionManager.renderAction("clearCanvas")}
|
{actionManager.renderAction("clearCanvas")}
|
||||||
|
{actionManager.renderAction("loadScene")}
|
||||||
|
{renderJSONExportDialog()}
|
||||||
|
{renderImageExportDialog()}
|
||||||
{onCollabButtonClick && (
|
{onCollabButtonClick && (
|
||||||
<CollabButton
|
<CollabButton
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
|
25
src/components/ProjectName.scss
Normal file
25
src/components/ProjectName.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.ProjectName {
|
||||||
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.TextInput {
|
||||||
|
height: calc(1rem - 3px);
|
||||||
|
width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&--readonly {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
width: auto;
|
||||||
|
max-width: 200px;
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ import "./TextInput.scss";
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { focusNearestParent } from "../utils";
|
import { focusNearestParent } from "../utils";
|
||||||
|
|
||||||
|
import "./ProjectName.scss";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
@ -37,8 +39,8 @@ export class ProjectName extends Component<Props, State> {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="ProjectName">
|
||||||
<label htmlFor="file-name">
|
<label className="ProjectName-label" htmlFor="filename">
|
||||||
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`}
|
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`}
|
||||||
</label>
|
</label>
|
||||||
{this.props.isNameEditable ? (
|
{this.props.isNameEditable ? (
|
||||||
@ -46,18 +48,18 @@ export class ProjectName extends Component<Props, State> {
|
|||||||
className="TextInput"
|
className="TextInput"
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
id="file-name"
|
id="filename"
|
||||||
value={this.state.fileName}
|
value={this.state.fileName}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
this.setState({ fileName: event.target.value })
|
this.setState({ fileName: event.target.value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="TextInput TextInput--readonly" id="file-name">
|
<span className="TextInput TextInput--readonly" id="filename">
|
||||||
{this.props.value}
|
{this.props.value}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
top: 64px;
|
top: 64px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
z-index: 999;
|
z-index: 10;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 24px 8px 0;
|
margin: 0 24px 8px 0;
|
||||||
|
@ -29,9 +29,13 @@ type ToolButtonProps =
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onClick?(): void;
|
onClick?(): void;
|
||||||
})
|
})
|
||||||
|
| (ToolButtonBaseProps & {
|
||||||
|
type: "icon";
|
||||||
|
children?: React.ReactNode;
|
||||||
|
onClick?(): void;
|
||||||
|
})
|
||||||
| (ToolButtonBaseProps & {
|
| (ToolButtonBaseProps & {
|
||||||
type: "radio";
|
type: "radio";
|
||||||
|
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onChange?(): void;
|
onChange?(): void;
|
||||||
});
|
});
|
||||||
@ -43,7 +47,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
React.useImperativeHandle(ref, () => innerRef.current);
|
React.useImperativeHandle(ref, () => innerRef.current);
|
||||||
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
|
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
|
||||||
|
|
||||||
if (props.type === "button") {
|
if (props.type === "button" || props.type === "icon") {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@ -56,6 +60,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
{
|
{
|
||||||
ToolIcon: !props.hidden,
|
ToolIcon: !props.hidden,
|
||||||
"ToolIcon--selected": props.selected,
|
"ToolIcon--selected": props.selected,
|
||||||
|
"ToolIcon--plain": props.type === "icon",
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
data-testid={props["data-testid"]}
|
data-testid={props["data-testid"]}
|
||||||
@ -66,6 +71,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
>
|
>
|
||||||
|
{(props.icon || props.label) && (
|
||||||
<div className="ToolIcon__icon" aria-hidden="true">
|
<div className="ToolIcon__icon" aria-hidden="true">
|
||||||
{props.icon || props.label}
|
{props.icon || props.label}
|
||||||
{props.keyBindingLabel && (
|
{props.keyBindingLabel && (
|
||||||
@ -74,6 +80,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{props.showAriaLabel && (
|
{props.showAriaLabel && (
|
||||||
<div className="ToolIcon__label">{props["aria-label"]}</div>
|
<div className="ToolIcon__label">{props["aria-label"]}</div>
|
||||||
)}
|
)}
|
||||||
|
@ -11,6 +11,15 @@
|
|||||||
background-color: var(--button-gray-1);
|
background-color: var(--button-gray-1);
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon--plain {
|
||||||
|
background-color: transparent;
|
||||||
|
.ToolIcon__icon {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon__icon {
|
.ToolIcon__icon {
|
||||||
@ -187,17 +196,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TooltipIcon {
|
|
||||||
width: 0.9em;
|
|
||||||
height: 0.9em;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-top: 1px;
|
|
||||||
|
|
||||||
@include isMobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlocked-icon {
|
.unlocked-icon {
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
left: 2px;
|
left: 2px;
|
||||||
|
@ -23,3 +23,17 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.Tooltip-icon {
|
||||||
|
width: 0.9em;
|
||||||
|
height: 0.9em;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 1px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@include isMobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,6 +41,14 @@ const createIcon = (d: string | React.ReactNode, opts: number | Opts = 512) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkIcon = createIcon(
|
||||||
|
<polyline fill="none" stroke="currentColor" points="20 6 9 17 4 12" />,
|
||||||
|
{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const link = createIcon(
|
export const link = createIcon(
|
||||||
"M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z",
|
"M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z",
|
||||||
{ mirror: true },
|
{ mirror: true },
|
||||||
@ -80,6 +88,25 @@ export const exportFile = createIcon(
|
|||||||
{ width: 576, height: 512, mirror: true },
|
{ width: 576, height: 512, mirror: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const exportImage = createIcon(
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
d="M571 308l-95.7-96.4c-10.1-10.1-27.4-3-27.4 11.3V288h-64v64h64v65.2c0 14.3 17.3 21.4 27.4 11.3L571 332c6.6-6.6 6.6-17.4 0-24zm-187 44v-64 64z"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M384 121.941V128H256V0h6.059c6.362 0 12.471 2.53 16.97 7.029l97.941 97.941a24.01 24.01 0 017.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160H248zm-135.455 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.545 328c4.686-4.686 11.799-4.201 16.485.485L160.545 368 264.06 264.485c4.686-4.686 12.284-4.686 16.971 0L320.545 304v112z"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
{ width: 576, height: 512, mirror: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const exportToFileIcon = createIcon(
|
||||||
|
"M216 0h80c13.3 0 24 10.7 24 24v168h87.7c17.8 0 26.7 21.5 14.1 34.1L269.7 378.3c-7.5 7.5-19.8 7.5-27.3 0L90.1 226.1c-12.6-12.6-3.7-34.1 14.1-34.1H192V24c0-13.3 10.7-24 24-24zm296 376v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h146.7l49 49c20.1 20.1 52.5 20.1 72.6 0l49-49H488c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z",
|
||||||
|
{ width: 512, height: 512 },
|
||||||
|
);
|
||||||
|
|
||||||
export const zoomIn = createIcon(
|
export const zoomIn = createIcon(
|
||||||
"M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z",
|
"M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z",
|
||||||
{ width: 448, height: 512 },
|
{ width: 448, height: 512 },
|
||||||
@ -350,14 +377,6 @@ export const DistributeHorizontallyIcon = React.memo(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
></svg>;
|
|
||||||
|
|
||||||
export const DistributeVerticallyIcon = React.memo(
|
export const DistributeVerticallyIcon = React.memo(
|
||||||
({ theme }: { theme: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
|
@ -42,8 +42,8 @@
|
|||||||
"fontSize": "Font size",
|
"fontSize": "Font size",
|
||||||
"fontFamily": "Font family",
|
"fontFamily": "Font family",
|
||||||
"onlySelected": "Only selected",
|
"onlySelected": "Only selected",
|
||||||
"withBackground": "With background",
|
"withBackground": "Background",
|
||||||
"exportEmbedScene": "Embed scene into exported file",
|
"exportEmbedScene": "Embed scene",
|
||||||
"exportEmbedScene_details": "Scene data will be saved into the exported PNG/SVG file so that the scene can be restored from it.\nWill increase exported file size.",
|
"exportEmbedScene_details": "Scene data will be saved into the exported PNG/SVG file so that the scene can be restored from it.\nWill increase exported file size.",
|
||||||
"addWatermark": "Add \"Made with Excalidraw\"",
|
"addWatermark": "Add \"Made with Excalidraw\"",
|
||||||
"handDrawn": "Hand-drawn",
|
"handDrawn": "Hand-drawn",
|
||||||
@ -105,13 +105,15 @@
|
|||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Reset the canvas",
|
"clearReset": "Reset the canvas",
|
||||||
|
"exportJSON": "Export to file",
|
||||||
|
"exportImage": "Save as image",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"exportToPng": "Export to PNG",
|
"exportToPng": "Export to PNG",
|
||||||
"exportToSvg": "Export to SVG",
|
"exportToSvg": "Export to SVG",
|
||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"copyPngToClipboard": "Copy PNG to clipboard",
|
"copyPngToClipboard": "Copy PNG to clipboard",
|
||||||
"scale": "Scale",
|
"scale": "Scale",
|
||||||
"save": "Save",
|
"save": "Save to current file",
|
||||||
"saveAs": "Save as",
|
"saveAs": "Save as",
|
||||||
"load": "Load",
|
"load": "Load",
|
||||||
"getShareableLink": "Get shareable link",
|
"getShareableLink": "Get shareable link",
|
||||||
@ -215,6 +217,14 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Error"
|
"title": "Error"
|
||||||
},
|
},
|
||||||
|
"exportDialog": {
|
||||||
|
"disk_title": "Save to disk",
|
||||||
|
"disk_details": "Export the scene data to a file from which you can import later.",
|
||||||
|
"disk_button": "Save to file",
|
||||||
|
"link_title": "Shareable link",
|
||||||
|
"link_details": "Export as a read-only link.",
|
||||||
|
"link_button": "Export to Link"
|
||||||
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Read our blog",
|
"blog": "Read our blog",
|
||||||
"click": "click",
|
"click": "click",
|
||||||
|
@ -23,6 +23,34 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
class="Stack Stack_horizontal"
|
class="Stack Stack_horizontal"
|
||||||
style="--gap: 1; justify-content: space-between;"
|
style="--gap: 1; justify-content: space-between;"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Reset the canvas"
|
||||||
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
|
data-testid="clear-canvas-button"
|
||||||
|
title="Reset the canvas"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ToolIcon__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 448 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
style="width: .625em;"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
aria-label="Load"
|
aria-label="Load"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
@ -48,61 +76,10 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
aria-label="Save"
|
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
|
||||||
data-testid="save-button"
|
|
||||||
title="Save"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ToolIcon__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Save as"
|
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--hide"
|
|
||||||
data-testid="save-as-button"
|
|
||||||
hidden=""
|
|
||||||
title="Save as"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ToolIcon__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M252 54L203 8a28 27 0 00-20-8H28C12 0 0 12 0 27v195c0 15 12 26 28 26h204c15 0 28-11 28-26V73a28 27 0 00-8-19zM130 213c-21 0-37-16-37-36 0-19 16-35 37-35 20 0 37 16 37 35 0 20-17 36-37 36zm56-169v56c0 4-4 6-7 6H44c-4 0-7-2-7-6V42c0-4 3-7 7-7h133l4 2 3 2a7 7 0 012 5z M296 201l87 95-188 205-78 9c-10 1-19-8-18-20l9-84zm141-14l-41-44a31 31 0 00-46 0l-38 41 87 95 38-42c13-14 13-36 0-50z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
aria-label="Export"
|
aria-label="Export"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="export-button"
|
data-testid="json-export-button"
|
||||||
title="Export"
|
title="Export"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -125,10 +102,10 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Reset the canvas"
|
aria-label="Save as image"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="clear-canvas-button"
|
data-testid="image-export-button"
|
||||||
title="Reset the canvas"
|
title="Save as image"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -137,18 +114,25 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class=""
|
class="rtl-mirror"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
viewBox="0 0 448 512"
|
viewBox="0 0 576 512"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
|
d="M571 308l-95.7-96.4c-10.1-10.1-27.4-3-27.4 11.3V288h-64v64h64v65.2c0 14.3 17.3 21.4 27.4 11.3L571 332c6.6-6.6 6.6-17.4 0-24zm-187 44v-64 64z"
|
||||||
fill="currentColor"
|
fill-rule="nonzero"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M384 121.941V128H256V0h6.059c6.362 0 12.471 2.53 16.97 7.029l97.941 97.941a24.01 24.01 0 017.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160H248zm-135.455 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.545 328c4.686-4.686 11.799-4.201 16.485.485L160.545 368 264.06 264.485c4.686-4.686 12.284-4.686 16.971 0L320.545 304v112z"
|
||||||
|
fill-rule="nonzero"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<div
|
||||||
|
style="width: .625em;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="display: flex;"
|
style="display: flex;"
|
||||||
@ -186,17 +170,15 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
<div
|
<div
|
||||||
style="margin-inline-start: 0.25rem;"
|
style="margin-inline-start: 0.25rem;"
|
||||||
>
|
>
|
||||||
<label
|
<button
|
||||||
class="ToolIcon ToolIcon_type_floating ToolIcon_size_M"
|
aria-label="Dark mode"
|
||||||
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain"
|
||||||
data-testid="toggle-dark-mode"
|
data-testid="toggle-dark-mode"
|
||||||
title="Dark mode"
|
title="Dark mode"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
aria-label="Dark mode"
|
|
||||||
class="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
class="ToolIcon__icon"
|
class="ToolIcon__icon"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@ -211,7 +193,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -242,6 +224,34 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
class="Stack Stack_horizontal"
|
class="Stack Stack_horizontal"
|
||||||
style="--gap: 1; justify-content: space-between;"
|
style="--gap: 1; justify-content: space-between;"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Reset the canvas"
|
||||||
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
|
data-testid="clear-canvas-button"
|
||||||
|
title="Reset the canvas"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ToolIcon__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 448 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
style="width: .625em;"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
aria-label="Load"
|
aria-label="Load"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
@ -267,61 +277,10 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
aria-label="Save"
|
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
|
||||||
data-testid="save-button"
|
|
||||||
title="Save"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ToolIcon__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Save as"
|
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--hide"
|
|
||||||
data-testid="save-as-button"
|
|
||||||
hidden=""
|
|
||||||
title="Save as"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ToolIcon__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M252 54L203 8a28 27 0 00-20-8H28C12 0 0 12 0 27v195c0 15 12 26 28 26h204c15 0 28-11 28-26V73a28 27 0 00-8-19zM130 213c-21 0-37-16-37-36 0-19 16-35 37-35 20 0 37 16 37 35 0 20-17 36-37 36zm56-169v56c0 4-4 6-7 6H44c-4 0-7-2-7-6V42c0-4 3-7 7-7h133l4 2 3 2a7 7 0 012 5z M296 201l87 95-188 205-78 9c-10 1-19-8-18-20l9-84zm141-14l-41-44a31 31 0 00-46 0l-38 41 87 95 38-42c13-14 13-36 0-50z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
aria-label="Export"
|
aria-label="Export"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="export-button"
|
data-testid="json-export-button"
|
||||||
title="Export"
|
title="Export"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -344,10 +303,10 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Reset the canvas"
|
aria-label="Save as image"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="clear-canvas-button"
|
data-testid="image-export-button"
|
||||||
title="Reset the canvas"
|
title="Save as image"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -356,18 +315,25 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class=""
|
class="rtl-mirror"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
viewBox="0 0 448 512"
|
viewBox="0 0 576 512"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
|
d="M571 308l-95.7-96.4c-10.1-10.1-27.4-3-27.4 11.3V288h-64v64h64v65.2c0 14.3 17.3 21.4 27.4 11.3L571 332c6.6-6.6 6.6-17.4 0-24zm-187 44v-64 64z"
|
||||||
fill="currentColor"
|
fill-rule="nonzero"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M384 121.941V128H256V0h6.059c6.362 0 12.471 2.53 16.97 7.029l97.941 97.941a24.01 24.01 0 017.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160H248zm-135.455 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.545 328c4.686-4.686 11.799-4.201 16.485.485L160.545 368 264.06 264.485c4.686-4.686 12.284-4.686 16.971 0L320.545 304v112z"
|
||||||
|
fill-rule="nonzero"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<div
|
||||||
|
style="width: .625em;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="display: flex;"
|
style="display: flex;"
|
||||||
@ -405,17 +371,15 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
<div
|
<div
|
||||||
style="margin-inline-start: 0.25rem;"
|
style="margin-inline-start: 0.25rem;"
|
||||||
>
|
>
|
||||||
<label
|
<button
|
||||||
class="ToolIcon ToolIcon_type_floating ToolIcon_size_M"
|
aria-label="Dark mode"
|
||||||
|
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain"
|
||||||
data-testid="toggle-dark-mode"
|
data-testid="toggle-dark-mode"
|
||||||
title="Dark mode"
|
title="Dark mode"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
aria-label="Dark mode"
|
|
||||||
class="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
class="ToolIcon__icon"
|
class="ToolIcon__icon"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@ -430,7 +394,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -110,9 +110,9 @@ describe("<Excalidraw/>", () => {
|
|||||||
it('should allow editing name when the name prop is "undefined"', async () => {
|
it('should allow editing name when the name prop is "undefined"', async () => {
|
||||||
const { container } = await render(<Excalidraw />);
|
const { container } = await render(<Excalidraw />);
|
||||||
|
|
||||||
fireEvent.click(queryByTestId(container, "export-button")!);
|
fireEvent.click(queryByTestId(container, "image-export-button")!);
|
||||||
const textInput: HTMLInputElement | null = document.querySelector(
|
const textInput: HTMLInputElement | null = document.querySelector(
|
||||||
".ExportDialog__name .TextInput",
|
".ExportDialog .ProjectName .TextInput",
|
||||||
);
|
);
|
||||||
expect(textInput?.value).toContain(`${t("labels.untitled")}`);
|
expect(textInput?.value).toContain(`${t("labels.untitled")}`);
|
||||||
expect(textInput?.nodeName).toBe("INPUT");
|
expect(textInput?.nodeName).toBe("INPUT");
|
||||||
@ -122,9 +122,9 @@ describe("<Excalidraw/>", () => {
|
|||||||
const name = "test";
|
const name = "test";
|
||||||
const { container } = await render(<Excalidraw name={name} />);
|
const { container } = await render(<Excalidraw name={name} />);
|
||||||
|
|
||||||
await fireEvent.click(queryByTestId(container, "export-button")!);
|
await fireEvent.click(queryByTestId(container, "image-export-button")!);
|
||||||
const textInput = document.querySelector(
|
const textInput = document.querySelector(
|
||||||
".ExportDialog__name .TextInput--readonly",
|
".ExportDialog .ProjectName .TextInput--readonly",
|
||||||
);
|
);
|
||||||
expect(textInput?.textContent).toEqual(name);
|
expect(textInput?.textContent).toEqual(name);
|
||||||
expect(textInput?.nodeName).toBe("SPAN");
|
expect(textInput?.nodeName).toBe("SPAN");
|
||||||
@ -166,7 +166,8 @@ describe("<Excalidraw/>", () => {
|
|||||||
<Excalidraw UIOptions={{ canvasActions: { export: false } }} />,
|
<Excalidraw UIOptions={{ canvasActions: { export: false } }} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryByTestId(container, "export-button")).toBeNull();
|
expect(queryByTestId(container, "json-export-button")).toBeNull();
|
||||||
|
expect(queryByTestId(container, "image-export-button")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide load button when loadScene is false", async () => {
|
it("should hide load button when loadScene is false", async () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user