parent
084aff2bf3
commit
36980160ae
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ProjectName } from "../components/ProjectName";
|
||||||
import { saveAsJSON, loadFromJSON } from "../data";
|
import { saveAsJSON, loadFromJSON } from "../data";
|
||||||
import { load, save, saveAs } from "../components/icons";
|
import { load, save, saveAs } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
@ -8,6 +9,20 @@ import { register } from "./register";
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { muteFSAbortError } from "../utils";
|
import { muteFSAbortError } from "../utils";
|
||||||
|
|
||||||
|
export const actionChangeProjectName = register({
|
||||||
|
name: "changeProjectName",
|
||||||
|
perform: (_elements, appState, value) => {
|
||||||
|
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
|
<ProjectName
|
||||||
|
label={t("labels.fileTitle")}
|
||||||
|
value={appState.name || "Unnamed"}
|
||||||
|
onChange={(name: string) => updateData(name)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
export const actionChangeExportBackground = register({
|
export const actionChangeExportBackground = register({
|
||||||
name: "changeExportBackground",
|
name: "changeExportBackground",
|
||||||
perform: (_elements, appState, value) => {
|
perform: (_elements, appState, value) => {
|
||||||
|
@ -31,6 +31,7 @@ export {
|
|||||||
export { actionFinalize } from "./actionFinalize";
|
export { actionFinalize } from "./actionFinalize";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
actionChangeProjectName,
|
||||||
actionChangeExportBackground,
|
actionChangeExportBackground,
|
||||||
actionSaveScene,
|
actionSaveScene,
|
||||||
actionSaveAsScene,
|
actionSaveAsScene,
|
||||||
|
@ -42,6 +42,7 @@ export type ActionName =
|
|||||||
| "undo"
|
| "undo"
|
||||||
| "redo"
|
| "redo"
|
||||||
| "finalize"
|
| "finalize"
|
||||||
|
| "changeProjectName"
|
||||||
| "changeExportBackground"
|
| "changeExportBackground"
|
||||||
| "changeExportEmbedScene"
|
| "changeExportEmbedScene"
|
||||||
| "changeShouldAddWatermark"
|
| "changeShouldAddWatermark"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
|
import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
|
||||||
|
import { getDateTime } from "./utils";
|
||||||
|
import { t } from "./i18n";
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
@ -44,6 +46,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
cursorY: 0,
|
cursorY: 0,
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
scrolledOutside: false,
|
scrolledOutside: false,
|
||||||
|
name: `${t("labels.untitled")}-${getDateTime()}`,
|
||||||
username: "",
|
username: "",
|
||||||
isBindingEnabled: true,
|
isBindingEnabled: true,
|
||||||
isCollaborating: false,
|
isCollaborating: false,
|
||||||
@ -125,6 +128,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
isRotating: { browser: false, export: false },
|
isRotating: { browser: false, export: false },
|
||||||
lastPointerDownWith: { browser: true, export: false },
|
lastPointerDownWith: { browser: true, export: false },
|
||||||
multiElement: { browser: false, export: false },
|
multiElement: { browser: false, export: false },
|
||||||
|
name: { browser: true, export: false },
|
||||||
openMenu: { browser: true, export: false },
|
openMenu: { browser: true, export: false },
|
||||||
previousSelectedElementIds: { browser: true, export: false },
|
previousSelectedElementIds: { browser: true, export: false },
|
||||||
resizingElement: { browser: false, export: false },
|
resizingElement: { browser: false, export: false },
|
||||||
|
@ -165,6 +165,9 @@ const ExportModal = ({
|
|||||||
onClick={() => onExportToBackend(exportedElements)}
|
onClick={() => onExportToBackend(exportedElements)}
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
|
<div className="ExportDialog__name">
|
||||||
|
{actionManager.renderAction("changeProjectName")}
|
||||||
|
</div>
|
||||||
<Stack.Row gap={2}>
|
<Stack.Row gap={2}>
|
||||||
{scales.map((s) => {
|
{scales.map((s) => {
|
||||||
const [width, height] = getExportSize(
|
const [width, height] = getExportSize(
|
||||||
|
@ -326,6 +326,7 @@ const LayerUI = ({
|
|||||||
if (canvas) {
|
if (canvas) {
|
||||||
await exportCanvas(type, exportedElements, appState, canvas, {
|
await exportCanvas(type, exportedElements, appState, canvas, {
|
||||||
exportBackground: appState.exportBackground,
|
exportBackground: appState.exportBackground,
|
||||||
|
name: appState.name,
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
scale,
|
scale,
|
||||||
shouldAddWatermark: appState.shouldAddWatermark,
|
shouldAddWatermark: appState.shouldAddWatermark,
|
||||||
|
62
src/components/ProjectName.tsx
Normal file
62
src/components/ProjectName.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import "./TextInput.scss";
|
||||||
|
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { selectNode, removeSelection } from "../utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ProjectName extends Component<Props> {
|
||||||
|
private handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
||||||
|
selectNode(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
||||||
|
const value = event.currentTarget.innerText.trim();
|
||||||
|
if (value !== this.props.value) {
|
||||||
|
this.props.onChange(value);
|
||||||
|
}
|
||||||
|
removeSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.nativeEvent.isComposing || event.keyCode === 229) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.currentTarget.blur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private makeEditable = (editable: HTMLSpanElement | null) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
editable.contentEditable = "plaintext-only";
|
||||||
|
} catch {
|
||||||
|
editable.contentEditable = "true";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
suppressContentEditableWarning
|
||||||
|
ref={this.makeEditable}
|
||||||
|
data-type="wysiwyg"
|
||||||
|
className="TextInput"
|
||||||
|
role="textbox"
|
||||||
|
aria-label={this.props.label}
|
||||||
|
onBlur={this.handleBlur}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
onFocus={this.handleFocus}
|
||||||
|
>
|
||||||
|
{this.props.value}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -283,12 +283,14 @@ export const exportCanvas = async (
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
exportPadding = 10,
|
exportPadding = 10,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
|
name,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
shouldAddWatermark,
|
shouldAddWatermark,
|
||||||
}: {
|
}: {
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
viewBackgroundColor: string;
|
viewBackgroundColor: string;
|
||||||
|
name: string;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
shouldAddWatermark: boolean;
|
shouldAddWatermark: boolean;
|
||||||
},
|
},
|
||||||
@ -314,6 +316,7 @@ export const exportCanvas = async (
|
|||||||
});
|
});
|
||||||
if (type === "svg") {
|
if (type === "svg") {
|
||||||
await fileSave(new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }), {
|
await fileSave(new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }), {
|
||||||
|
fileName: `${name}.svg`,
|
||||||
extensions: [".svg"],
|
extensions: [".svg"],
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -334,6 +337,7 @@ export const exportCanvas = async (
|
|||||||
document.body.appendChild(tempCanvas);
|
document.body.appendChild(tempCanvas);
|
||||||
|
|
||||||
if (type === "png") {
|
if (type === "png") {
|
||||||
|
const fileName = `${name}.png`;
|
||||||
let blob = await canvasToBlob(tempCanvas);
|
let blob = await canvasToBlob(tempCanvas);
|
||||||
if (appState.exportEmbedScene) {
|
if (appState.exportEmbedScene) {
|
||||||
blob = await (
|
blob = await (
|
||||||
@ -345,6 +349,7 @@ export const exportCanvas = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fileSave(blob, {
|
await fileSave(blob, {
|
||||||
|
fileName,
|
||||||
extensions: [".png"],
|
extensions: [".png"],
|
||||||
});
|
});
|
||||||
} else if (type === "clipboard") {
|
} else if (type === "clipboard") {
|
||||||
|
@ -36,6 +36,7 @@ export const saveAsJSON = async (
|
|||||||
const fileHandle = await fileSave(
|
const fileHandle = await fileSave(
|
||||||
blob,
|
blob,
|
||||||
{
|
{
|
||||||
|
fileName: appState.name,
|
||||||
description: "Excalidraw file",
|
description: "Excalidraw file",
|
||||||
extensions: [".excalidraw"],
|
extensions: [".excalidraw"],
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,7 @@ const clearAppStatePropertiesForHistory = (appState: AppState) => {
|
|||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
editingLinearElement: appState.editingLinearElement,
|
editingLinearElement: appState.editingLinearElement,
|
||||||
editingGroupId: appState.editingGroupId,
|
editingGroupId: appState.editingGroupId,
|
||||||
|
name: appState.name,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
"architect": "Architect",
|
"architect": "Architect",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"cartoonist": "Cartoonist",
|
"cartoonist": "Cartoonist",
|
||||||
|
"fileTitle": "File title",
|
||||||
"colorPicker": "Color picker",
|
"colorPicker": "Color picker",
|
||||||
"canvasBackground": "Canvas background",
|
"canvasBackground": "Canvas background",
|
||||||
"drawingCanvas": "Drawing canvas",
|
"drawingCanvas": "Drawing canvas",
|
||||||
@ -62,6 +63,7 @@
|
|||||||
"language": "Language",
|
"language": "Language",
|
||||||
"createRoom": "Share a live-collaboration session",
|
"createRoom": "Share a live-collaboration session",
|
||||||
"duplicateSelection": "Duplicate",
|
"duplicateSelection": "Duplicate",
|
||||||
|
"untitled": "Untitled",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"yourName": "Your name",
|
"yourName": "Your name",
|
||||||
"madeWithExcalidraw": "Made with Excalidraw",
|
"madeWithExcalidraw": "Made with Excalidraw",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -68,6 +68,7 @@ export type AppState = {
|
|||||||
cursorY: number;
|
cursorY: number;
|
||||||
cursorButton: "up" | "down";
|
cursorButton: "up" | "down";
|
||||||
scrolledOutside: boolean;
|
scrolledOutside: boolean;
|
||||||
|
name: string;
|
||||||
username: string;
|
username: string;
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user