Sync panel props to editing element (#470)
* ensure panel props are sync to editing elem * ensure we don't create empty-text elements (fixes #468) * remove dead code Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
This commit is contained in:
parent
ff7a340d2f
commit
2340dddaad
@ -1,10 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Action } from "./types";
|
import { Action } from "./types";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||||
import { getSelectedAttribute } from "../scene";
|
import { getCommonAttributeOfSelectedElements } from "../scene";
|
||||||
import { ButtonSelect } from "../components/ButtonSelect";
|
import { ButtonSelect } from "../components/ButtonSelect";
|
||||||
import { isTextElement, redrawTextBoundingBox } from "../element";
|
import { isTextElement, redrawTextBoundingBox } from "../element";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
|
import { AppState } from "../../src/types";
|
||||||
|
|
||||||
const changeProperty = (
|
const changeProperty = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -18,6 +19,20 @@ const changeProperty = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFormValue = function<T>(
|
||||||
|
editingElement: AppState["editingElement"],
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
getAttribute: (element: ExcalidrawElement) => T,
|
||||||
|
defaultValue?: T
|
||||||
|
): T | null {
|
||||||
|
return (
|
||||||
|
(editingElement && getAttribute(editingElement)) ||
|
||||||
|
getCommonAttributeOfSelectedElements(elements, getAttribute) ||
|
||||||
|
defaultValue ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const actionChangeStrokeColor: Action = {
|
export const actionChangeStrokeColor: Action = {
|
||||||
name: "changeStrokeColor",
|
name: "changeStrokeColor",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
@ -30,21 +45,21 @@ export const actionChangeStrokeColor: Action = {
|
|||||||
appState: { ...appState, currentItemStrokeColor: value }
|
appState: { ...appState, currentItemStrokeColor: value }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => {
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
return (
|
<>
|
||||||
<>
|
<h5>{t("labels.stroke")}</h5>
|
||||||
<h5>{t("labels.stroke")}</h5>
|
<ColorPicker
|
||||||
<ColorPicker
|
type="elementStroke"
|
||||||
type="elementStroke"
|
color={getFormValue(
|
||||||
color={
|
appState.editingElement,
|
||||||
getSelectedAttribute(elements, element => element.strokeColor) ||
|
elements,
|
||||||
appState.currentItemStrokeColor
|
element => element.strokeColor,
|
||||||
}
|
appState.currentItemStrokeColor
|
||||||
onChange={updateData}
|
)}
|
||||||
/>
|
onChange={updateData}
|
||||||
</>
|
/>
|
||||||
);
|
</>
|
||||||
}
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeBackgroundColor: Action = {
|
export const actionChangeBackgroundColor: Action = {
|
||||||
@ -64,10 +79,12 @@ export const actionChangeBackgroundColor: Action = {
|
|||||||
<h5>{t("labels.background")}</h5>
|
<h5>{t("labels.background")}</h5>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
type="elementBackground"
|
type="elementBackground"
|
||||||
color={
|
color={getFormValue(
|
||||||
getSelectedAttribute(elements, element => element.backgroundColor) ||
|
appState.editingElement,
|
||||||
|
elements,
|
||||||
|
element => element.backgroundColor,
|
||||||
appState.currentItemBackgroundColor
|
appState.currentItemBackgroundColor
|
||||||
}
|
)}
|
||||||
onChange={updateData}
|
onChange={updateData}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -85,7 +102,7 @@ export const actionChangeFillStyle: Action = {
|
|||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<>
|
||||||
<h5>{t("labels.fill")}</h5>
|
<h5>{t("labels.fill")}</h5>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -94,7 +111,11 @@ export const actionChangeFillStyle: Action = {
|
|||||||
{ value: "hachure", text: "Hachure" },
|
{ value: "hachure", text: "Hachure" },
|
||||||
{ value: "cross-hatch", text: "Cross-hatch" }
|
{ value: "cross-hatch", text: "Cross-hatch" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedAttribute(elements, element => element.fillStyle)}
|
value={getFormValue(
|
||||||
|
appState.editingElement,
|
||||||
|
elements,
|
||||||
|
element => element.fillStyle
|
||||||
|
)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
updateData(value);
|
updateData(value);
|
||||||
}}
|
}}
|
||||||
@ -123,7 +144,11 @@ export const actionChangeStrokeWidth: Action = {
|
|||||||
{ value: 2, text: "Bold" },
|
{ value: 2, text: "Bold" },
|
||||||
{ value: 4, text: "Extra Bold" }
|
{ value: 4, text: "Extra Bold" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedAttribute(elements, element => element.strokeWidth)}
|
value={getFormValue(
|
||||||
|
appState.editingElement,
|
||||||
|
elements,
|
||||||
|
element => element.strokeWidth
|
||||||
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -150,7 +175,11 @@ export const actionChangeSloppiness: Action = {
|
|||||||
{ value: 1, text: "Artist" },
|
{ value: 1, text: "Artist" },
|
||||||
{ value: 3, text: "Cartoonist" }
|
{ value: 3, text: "Cartoonist" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedAttribute(elements, element => element.roughness)}
|
value={getFormValue(
|
||||||
|
appState.editingElement,
|
||||||
|
elements,
|
||||||
|
element => element.roughness
|
||||||
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -168,7 +197,7 @@ export const actionChangeOpacity: Action = {
|
|||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<>
|
||||||
<h5>{t("labels.oppacity")}</h5>
|
<h5>{t("labels.oppacity")}</h5>
|
||||||
<input
|
<input
|
||||||
@ -177,8 +206,12 @@ export const actionChangeOpacity: Action = {
|
|||||||
max="100"
|
max="100"
|
||||||
onChange={e => updateData(+e.target.value)}
|
onChange={e => updateData(+e.target.value)}
|
||||||
value={
|
value={
|
||||||
getSelectedAttribute(elements, element => element.opacity) ||
|
getFormValue(
|
||||||
0 /* Put the opacity at 0 if there are two conflicting ones */
|
appState.editingElement,
|
||||||
|
elements,
|
||||||
|
element => element.opacity,
|
||||||
|
100 /* default opacity */
|
||||||
|
) || undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -204,7 +237,7 @@ export const actionChangeFontSize: Action = {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<>
|
||||||
<h5>{t("labels.fontSize")}</h5>
|
<h5>{t("labels.fontSize")}</h5>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -214,7 +247,8 @@ export const actionChangeFontSize: Action = {
|
|||||||
{ value: 28, text: "Large" },
|
{ value: 28, text: "Large" },
|
||||||
{ value: 36, text: "Very Large" }
|
{ value: 36, text: "Very Large" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedAttribute(
|
value={getFormValue(
|
||||||
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => isTextElement(element) && +element.font.split("px ")[0]
|
element => isTextElement(element) && +element.font.split("px ")[0]
|
||||||
)}
|
)}
|
||||||
@ -243,7 +277,7 @@ export const actionChangeFontFamily: Action = {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<>
|
||||||
<h5>{t("labels.fontFamily")}</h5>
|
<h5>{t("labels.fontFamily")}</h5>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -252,7 +286,8 @@ export const actionChangeFontFamily: Action = {
|
|||||||
{ value: "Helvetica", text: t("labels.normal") },
|
{ value: "Helvetica", text: t("labels.normal") },
|
||||||
{ value: "Cascadia", text: t("labels.code") }
|
{ value: "Cascadia", text: t("labels.code") }
|
||||||
]}
|
]}
|
||||||
value={getSelectedAttribute(
|
value={getFormValue(
|
||||||
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => isTextElement(element) && element.font.split("px ")[1]
|
element => isTextElement(element) && element.font.split("px ")[1]
|
||||||
)}
|
)}
|
||||||
|
@ -12,7 +12,7 @@ const Picker = function({
|
|||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
colors: string[];
|
colors: string[];
|
||||||
color: string | undefined;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@ -55,7 +55,7 @@ function ColorInput({
|
|||||||
color,
|
color,
|
||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
color: string | undefined;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
|
const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
|
||||||
@ -93,7 +93,7 @@ export function ColorPicker({
|
|||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||||
color: string | undefined;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [isActive, setActive] = React.useState(false);
|
const [isActive, setActive] = React.useState(false);
|
||||||
@ -119,7 +119,7 @@ export function ColorPicker({
|
|||||||
<Popover onCloseRequest={() => setActive(false)}>
|
<Popover onCloseRequest={() => setActive(false)}>
|
||||||
<Picker
|
<Picker
|
||||||
colors={colors[type]}
|
colors={colors[type]}
|
||||||
color={color || undefined}
|
color={color || null}
|
||||||
onChange={changedColor => {
|
onChange={changedColor => {
|
||||||
onChange(changedColor);
|
onChange(changedColor);
|
||||||
}}
|
}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export { newElement, duplicateElement } from "./newElement";
|
export { newElement, newTextElement, duplicateElement } from "./newElement";
|
||||||
export {
|
export {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getDiamondPoints,
|
getDiamondPoints,
|
||||||
|
@ -2,6 +2,9 @@ import { randomSeed } from "roughjs/bin/math";
|
|||||||
import nanoid from "nanoid";
|
import nanoid from "nanoid";
|
||||||
import { Drawable } from "roughjs/bin/core";
|
import { Drawable } from "roughjs/bin/core";
|
||||||
|
|
||||||
|
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||||
|
import { measureText } from "../utils";
|
||||||
|
|
||||||
export function newElement(
|
export function newElement(
|
||||||
type: string,
|
type: string,
|
||||||
x: number,
|
x: number,
|
||||||
@ -35,6 +38,28 @@ export function newElement(
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function newTextElement(
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
text: string,
|
||||||
|
font: string
|
||||||
|
) {
|
||||||
|
const metrics = measureText(text, font);
|
||||||
|
const textElement: ExcalidrawTextElement = {
|
||||||
|
...element,
|
||||||
|
type: "text",
|
||||||
|
text: text,
|
||||||
|
font: font,
|
||||||
|
// Center the text
|
||||||
|
x: element.x - metrics.width / 2,
|
||||||
|
y: element.y - metrics.height / 2,
|
||||||
|
width: metrics.width,
|
||||||
|
height: metrics.height,
|
||||||
|
baseline: metrics.baseline
|
||||||
|
};
|
||||||
|
|
||||||
|
return textElement;
|
||||||
|
}
|
||||||
|
|
||||||
export function duplicateElement(element: ReturnType<typeof newElement>) {
|
export function duplicateElement(element: ReturnType<typeof newElement>) {
|
||||||
const copy = { ...element };
|
const copy = { ...element };
|
||||||
delete copy.shape;
|
delete copy.shape;
|
||||||
|
121
src/index.tsx
121
src/index.tsx
@ -6,6 +6,7 @@ import { RoughCanvas } from "roughjs/bin/canvas";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
newElement,
|
newElement,
|
||||||
|
newTextElement,
|
||||||
duplicateElement,
|
duplicateElement,
|
||||||
resizeTest,
|
resizeTest,
|
||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
@ -33,9 +34,9 @@ import {
|
|||||||
|
|
||||||
import { renderScene } from "./renderer";
|
import { renderScene } from "./renderer";
|
||||||
import { AppState } from "./types";
|
import { AppState } from "./types";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
|
import { ExcalidrawElement } from "./element/types";
|
||||||
|
|
||||||
import { isInputLike, measureText, debounce, capitalizeString } from "./utils";
|
import { isInputLike, debounce, capitalizeString } from "./utils";
|
||||||
import { KEYS, isArrowKey } from "./keys";
|
import { KEYS, isArrowKey } from "./keys";
|
||||||
|
|
||||||
import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
|
import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
|
||||||
@ -90,29 +91,6 @@ function resetCursor() {
|
|||||||
document.documentElement.style.cursor = "";
|
document.documentElement.style.cursor = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTextElement(
|
|
||||||
element: ExcalidrawTextElement,
|
|
||||||
text: string,
|
|
||||||
font: string
|
|
||||||
) {
|
|
||||||
resetCursor();
|
|
||||||
if (text === null || text === "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metrics = measureText(text, font);
|
|
||||||
element.text = text;
|
|
||||||
element.font = font;
|
|
||||||
// Center the text
|
|
||||||
element.x -= metrics.width / 2;
|
|
||||||
element.y -= metrics.height / 2;
|
|
||||||
element.width = metrics.width;
|
|
||||||
element.height = metrics.height;
|
|
||||||
element.baseline = metrics.baseline;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
||||||
const ELEMENT_TRANSLATE_AMOUNT = 1;
|
const ELEMENT_TRANSLATE_AMOUNT = 1;
|
||||||
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
||||||
@ -754,7 +732,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
||||||
|
|
||||||
const element = newElement(
|
let element = newElement(
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -766,6 +744,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
100
|
100
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isTextElement(element)) {
|
||||||
|
element = newTextElement(element, "", this.state.currentItemFont);
|
||||||
|
}
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
let resizeHandle: ResizeTestType = false;
|
let resizeHandle: ResizeTestType = false;
|
||||||
let isResizingElements = false;
|
let isResizingElements = false;
|
||||||
@ -851,8 +833,19 @@ export class App extends React.Component<any, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
font: this.state.currentItemFont,
|
font: this.state.currentItemFont,
|
||||||
onSubmit: text => {
|
onSubmit: text => {
|
||||||
addTextElement(element, text, this.state.currentItemFont);
|
if (text) {
|
||||||
elements = [...elements, { ...element, isSelected: true }];
|
elements = [
|
||||||
|
...elements,
|
||||||
|
{
|
||||||
|
...newTextElement(
|
||||||
|
element,
|
||||||
|
text,
|
||||||
|
this.state.currentItemFont
|
||||||
|
),
|
||||||
|
isSelected: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
editingElement: null,
|
editingElement: null,
|
||||||
@ -867,16 +860,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.elementType === "text") {
|
elements = [...elements, element];
|
||||||
elements = [...elements, { ...element, isSelected: true }];
|
this.setState({ draggingElement: element });
|
||||||
this.setState({
|
|
||||||
draggingElement: null,
|
|
||||||
elementType: "selection"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
elements = [...elements, element];
|
|
||||||
this.setState({ draggingElement: element });
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastX = x;
|
let lastX = x;
|
||||||
let lastY = y;
|
let lastY = y;
|
||||||
@ -1142,21 +1127,27 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
const elementAtPosition = getElementAtPosition(elements, x, y);
|
const elementAtPosition = getElementAtPosition(elements, x, y);
|
||||||
|
|
||||||
const element = newElement(
|
const element =
|
||||||
"text",
|
elementAtPosition && isTextElement(elementAtPosition)
|
||||||
x,
|
? elementAtPosition
|
||||||
y,
|
: newTextElement(
|
||||||
this.state.currentItemStrokeColor,
|
newElement(
|
||||||
this.state.currentItemBackgroundColor,
|
"text",
|
||||||
"hachure",
|
x,
|
||||||
1,
|
y,
|
||||||
1,
|
this.state.currentItemStrokeColor,
|
||||||
100
|
this.state.currentItemBackgroundColor,
|
||||||
) as ExcalidrawTextElement;
|
"hachure",
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
100
|
||||||
|
),
|
||||||
|
"", // default text
|
||||||
|
this.state.currentItemFont // default font
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({ editingElement: element });
|
this.setState({ editingElement: element });
|
||||||
|
|
||||||
let initText = "";
|
|
||||||
let textX = e.clientX;
|
let textX = e.clientX;
|
||||||
let textY = e.clientY;
|
let textY = e.clientY;
|
||||||
|
|
||||||
@ -1166,11 +1157,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
);
|
);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
|
||||||
Object.assign(element, elementAtPosition);
|
|
||||||
// x and y will change after calling addTextElement function
|
|
||||||
element.x = elementAtPosition.x + elementAtPosition.width / 2;
|
|
||||||
element.y = elementAtPosition.y + elementAtPosition.height / 2;
|
|
||||||
initText = elementAtPosition.text;
|
|
||||||
textX =
|
textX =
|
||||||
this.state.scrollX +
|
this.state.scrollX +
|
||||||
elementAtPosition.x +
|
elementAtPosition.x +
|
||||||
@ -1181,6 +1167,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elementAtPosition.y +
|
elementAtPosition.y +
|
||||||
CANVAS_WINDOW_OFFSET_TOP +
|
CANVAS_WINDOW_OFFSET_TOP +
|
||||||
elementAtPosition.height / 2;
|
elementAtPosition.height / 2;
|
||||||
|
|
||||||
|
// x and y will change after calling newTextElement function
|
||||||
|
element.x = elementAtPosition.x + elementAtPosition.width / 2;
|
||||||
|
element.y = elementAtPosition.y + elementAtPosition.height / 2;
|
||||||
} else if (!e.altKey) {
|
} else if (!e.altKey) {
|
||||||
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
||||||
x,
|
x,
|
||||||
@ -1196,18 +1186,23 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textWysiwyg({
|
textWysiwyg({
|
||||||
initText,
|
initText: element.text,
|
||||||
x: textX,
|
x: textX,
|
||||||
y: textY,
|
y: textY,
|
||||||
strokeColor: element.strokeColor,
|
strokeColor: element.strokeColor,
|
||||||
font: element.font || this.state.currentItemFont,
|
font: element.font,
|
||||||
onSubmit: text => {
|
onSubmit: text => {
|
||||||
addTextElement(
|
if (text) {
|
||||||
element,
|
elements = [
|
||||||
text,
|
...elements,
|
||||||
element.font || this.state.currentItemFont
|
{
|
||||||
);
|
// we need to recreate the element to update dimensions &
|
||||||
elements = [...elements, { ...element, isSelected: true }];
|
// position
|
||||||
|
...newTextElement(element, text, element.font),
|
||||||
|
isSelected: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
editingElement: null,
|
editingElement: null,
|
||||||
|
@ -5,7 +5,7 @@ export {
|
|||||||
deleteSelectedElements,
|
deleteSelectedElements,
|
||||||
someElementIsSelected,
|
someElementIsSelected,
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
getSelectedAttribute
|
getCommonAttributeOfSelectedElements
|
||||||
} from "./selection";
|
} from "./selection";
|
||||||
export {
|
export {
|
||||||
exportCanvas,
|
exportCanvas,
|
||||||
|
@ -56,7 +56,11 @@ export function getSelectedIndices(elements: readonly ExcalidrawElement[]) {
|
|||||||
export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) =>
|
export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) =>
|
||||||
elements.some(element => element.isSelected);
|
elements.some(element => element.isSelected);
|
||||||
|
|
||||||
export function getSelectedAttribute<T>(
|
/**
|
||||||
|
* Returns common attribute (picked by `getAttribute` callback) of selected
|
||||||
|
* elements. If elements don't share the same value, returns `null`.
|
||||||
|
*/
|
||||||
|
export function getCommonAttributeOfSelectedElements<T>(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
getAttribute: (element: ExcalidrawElement) => T
|
getAttribute: (element: ExcalidrawElement) => T
|
||||||
): T | null {
|
): T | null {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user