123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- import { load, questionCircle, saveAs } from "../components/icons";
- import { ProjectName } from "../components/ProjectName";
- import { ToolButton } from "../components/ToolButton";
- import "../components/ToolIcon.scss";
- import { Tooltip } from "../components/Tooltip";
- import { DarkModeToggle } from "../components/DarkModeToggle";
- import { loadFromJSON, saveAsJSON } from "../data";
- import { resaveAsImageWithScene } from "../data/resave";
- import { t } from "../i18n";
- import { useDeviceType } from "../components/App";
- import { KEYS } from "../keys";
- import { register } from "./register";
- import { CheckboxItem } from "../components/CheckboxItem";
- import { getExportSize } from "../scene/export";
- import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES, THEME } from "../constants";
- import { getSelectedElements, isSomeElementSelected } from "../scene";
- import { getNonDeletedElements } from "../element";
- import { ActiveFile } from "../components/ActiveFile";
- import { isImageFileHandle } from "../data/blob";
- import { nativeFileSystemSupported } from "../data/filesystem";
- import { Theme } from "../element/types";
- export const actionChangeProjectName = register({
- name: "changeProjectName",
- trackEvent: false,
- perform: (_elements, appState, value) => {
- return { appState: { ...appState, name: value }, commitToHistory: false };
- },
- PanelComponent: ({ appState, updateData, appProps }) => (
- <ProjectName
- label={t("labels.fileTitle")}
- value={appState.name || "Unnamed"}
- onChange={(name: string) => updateData(name)}
- isNameEditable={
- typeof appProps.name === "undefined" && !appState.viewModeEnabled
- }
- />
- ),
- });
- export const actionChangeExportScale = register({
- name: "changeExportScale",
- trackEvent: { category: "export", action: "scale" },
- perform: (_elements, appState, value) => {
- return {
- appState: { ...appState, exportScale: value },
- commitToHistory: false,
- };
- },
- PanelComponent: ({ elements: allElements, appState, updateData }) => {
- const elements = getNonDeletedElements(allElements);
- const exportSelected = isSomeElementSelected(elements, appState);
- const exportedElements = exportSelected
- ? getSelectedElements(elements, appState)
- : elements;
- return (
- <>
- {EXPORT_SCALES.map((s) => {
- const [width, height] = getExportSize(
- exportedElements,
- DEFAULT_EXPORT_PADDING,
- s,
- );
- const scaleButtonTitle = `${t(
- "buttons.scale",
- )} ${s}x (${width}x${height})`;
- return (
- <ToolButton
- key={s}
- size="small"
- type="radio"
- icon={`${s}x`}
- name="export-canvas-scale"
- title={scaleButtonTitle}
- aria-label={scaleButtonTitle}
- id="export-canvas-scale"
- checked={s === appState.exportScale}
- onChange={() => updateData(s)}
- />
- );
- })}
- </>
- );
- },
- });
- export const actionChangeExportBackground = register({
- name: "changeExportBackground",
- trackEvent: { category: "export", action: "toggleBackground" },
- perform: (_elements, appState, value) => {
- return {
- appState: { ...appState, exportBackground: value },
- commitToHistory: false,
- };
- },
- PanelComponent: ({ appState, updateData }) => (
- <CheckboxItem
- checked={appState.exportBackground}
- onChange={(checked) => updateData(checked)}
- >
- {t("labels.withBackground")}
- </CheckboxItem>
- ),
- });
- export const actionChangeExportEmbedScene = register({
- name: "changeExportEmbedScene",
- trackEvent: { category: "export", action: "embedScene" },
- perform: (_elements, appState, value) => {
- return {
- appState: { ...appState, exportEmbedScene: value },
- commitToHistory: false,
- };
- },
- PanelComponent: ({ appState, updateData }) => (
- <CheckboxItem
- checked={appState.exportEmbedScene}
- onChange={(checked) => updateData(checked)}
- >
- {t("labels.exportEmbedScene")}
- <Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
- <div className="excalidraw-tooltip-icon">{questionCircle}</div>
- </Tooltip>
- </CheckboxItem>
- ),
- });
- export const actionSaveToActiveFile = register({
- name: "saveToActiveFile",
- trackEvent: { category: "export" },
- perform: async (elements, appState, value, app) => {
- const fileHandleExists = !!appState.fileHandle;
- try {
- const { fileHandle } = isImageFileHandle(appState.fileHandle)
- ? await resaveAsImageWithScene(elements, appState, app.files)
- : await saveAsJSON(elements, appState, app.files);
- return {
- commitToHistory: false,
- appState: {
- ...appState,
- fileHandle,
- toastMessage: fileHandleExists
- ? fileHandle?.name
- ? t("toast.fileSavedToFilename").replace(
- "{filename}",
- `"${fileHandle.name}"`,
- )
- : t("toast.fileSaved")
- : null,
- },
- };
- } catch (error: any) {
- if (error?.name !== "AbortError") {
- console.error(error);
- } else {
- console.warn(error);
- }
- return { commitToHistory: false };
- }
- },
- keyTest: (event) =>
- event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
- PanelComponent: ({ updateData, appState }) => (
- <ActiveFile
- onSave={() => updateData(null)}
- fileName={appState.fileHandle?.name}
- />
- ),
- });
- export const actionSaveFileToDisk = register({
- name: "saveFileToDisk",
- trackEvent: { category: "export" },
- perform: async (elements, appState, value, app) => {
- try {
- const { fileHandle } = await saveAsJSON(
- elements,
- {
- ...appState,
- fileHandle: null,
- },
- app.files,
- );
- return { commitToHistory: false, appState: { ...appState, fileHandle } };
- } catch (error: any) {
- if (error?.name !== "AbortError") {
- console.error(error);
- } else {
- console.warn(error);
- }
- return { commitToHistory: false };
- }
- },
- keyTest: (event) =>
- event.key === KEYS.S && event.shiftKey && event[KEYS.CTRL_OR_CMD],
- PanelComponent: ({ updateData }) => (
- <ToolButton
- type="button"
- icon={saveAs}
- title={t("buttons.saveAs")}
- aria-label={t("buttons.saveAs")}
- showAriaLabel={useDeviceType().isMobile}
- hidden={!nativeFileSystemSupported}
- onClick={() => updateData(null)}
- data-testid="save-as-button"
- />
- ),
- });
- export const actionLoadScene = register({
- name: "loadScene",
- trackEvent: { category: "export" },
- perform: async (elements, appState, _, app) => {
- try {
- const {
- elements: loadedElements,
- appState: loadedAppState,
- files,
- } = await loadFromJSON(appState, elements);
- return {
- elements: loadedElements,
- appState: loadedAppState,
- files,
- commitToHistory: true,
- };
- } catch (error: any) {
- if (error?.name === "AbortError") {
- console.warn(error);
- return false;
- }
- return {
- elements,
- appState: { ...appState, errorMessage: error.message },
- files: app.files,
- commitToHistory: false,
- };
- }
- },
- keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
- PanelComponent: ({ updateData, appState }) => (
- <ToolButton
- type="button"
- icon={load}
- title={t("buttons.load")}
- aria-label={t("buttons.load")}
- showAriaLabel={useDeviceType().isMobile}
- onClick={updateData}
- data-testid="load-button"
- />
- ),
- });
- export const actionExportWithDarkMode = register({
- name: "exportWithDarkMode",
- trackEvent: { category: "export", action: "toggleTheme" },
- perform: (_elements, appState, value) => {
- return {
- appState: { ...appState, exportWithDarkMode: value },
- commitToHistory: false,
- };
- },
- PanelComponent: ({ appState, updateData }) => (
- <div
- style={{
- display: "flex",
- justifyContent: "flex-end",
- marginTop: "-45px",
- marginBottom: "10px",
- }}
- >
- <DarkModeToggle
- value={appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT}
- onChange={(theme: Theme) => {
- updateData(theme === THEME.DARK);
- }}
- title={t("labels.toggleExportColorScheme")}
- />
- </div>
- ),
- });
|