change dimension for multiple elements
This commit is contained in:
parent
7f4659339b
commit
f0c1e9707a
@ -20,6 +20,8 @@ const Angle = ({ element }: AngleProps) => {
|
||||
shouldChangeByStepSize,
|
||||
nextValue,
|
||||
) => {
|
||||
const _stateAtStart = stateAtStart[0];
|
||||
if (_stateAtStart) {
|
||||
if (nextValue !== undefined) {
|
||||
const nextAngle = degreeToRadian(nextValue);
|
||||
mutateElement(element, {
|
||||
@ -28,9 +30,8 @@ const Angle = ({ element }: AngleProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateAtStart) {
|
||||
const originalAngleInDegrees =
|
||||
Math.round(radianToDegree(stateAtStart.angle) * 100) / 100;
|
||||
Math.round(radianToDegree(_stateAtStart.angle) * 100) / 100;
|
||||
const changeInDegrees = Math.round(accumulatedChange);
|
||||
let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
|
||||
if (shouldChangeByStepSize) {
|
||||
@ -51,7 +52,7 @@ const Angle = ({ element }: AngleProps) => {
|
||||
<DragInput
|
||||
label="A"
|
||||
value={Math.round(radianToDegree(element.angle) * 100) / 100}
|
||||
element={element}
|
||||
elements={[element]}
|
||||
dragInputCallback={handleDegreeChange}
|
||||
editable={isPropertyEditable(element, "angle")}
|
||||
/>
|
||||
|
@ -14,7 +14,7 @@ const _shouldKeepAspectRatio = (element: ExcalidrawElement) => {
|
||||
return element.type === "image";
|
||||
};
|
||||
|
||||
const newOrigin = (
|
||||
export const newOrigin = (
|
||||
x1: number,
|
||||
y1: number,
|
||||
w1: number,
|
||||
@ -59,10 +59,11 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
shouldChangeByStepSize,
|
||||
nextValue,
|
||||
) => {
|
||||
if (stateAtStart) {
|
||||
const _stateAtStart = stateAtStart[0];
|
||||
if (_stateAtStart) {
|
||||
const keepAspectRatio =
|
||||
shouldKeepAspectRatio || _shouldKeepAspectRatio(element);
|
||||
const aspectRatio = stateAtStart.width / stateAtStart.height;
|
||||
const aspectRatio = _stateAtStart.width / _stateAtStart.height;
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
const nextWidth = Math.max(
|
||||
@ -70,7 +71,7 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
? nextValue
|
||||
: keepAspectRatio
|
||||
? nextValue * aspectRatio
|
||||
: stateAtStart.width,
|
||||
: _stateAtStart.width,
|
||||
0,
|
||||
);
|
||||
const nextHeight = Math.max(
|
||||
@ -78,7 +79,7 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
? nextValue
|
||||
: keepAspectRatio
|
||||
? nextValue / aspectRatio
|
||||
: stateAtStart.height,
|
||||
: _stateAtStart.height,
|
||||
0,
|
||||
);
|
||||
|
||||
@ -100,7 +101,7 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
const changeInWidth = property === "width" ? accumulatedChange : 0;
|
||||
const changeInHeight = property === "height" ? accumulatedChange : 0;
|
||||
|
||||
let nextWidth = Math.max(0, stateAtStart.width + changeInWidth);
|
||||
let nextWidth = Math.max(0, _stateAtStart.width + changeInWidth);
|
||||
if (property === "width") {
|
||||
if (shouldChangeByStepSize) {
|
||||
nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
|
||||
@ -109,7 +110,7 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
let nextHeight = Math.max(0, stateAtStart.height + changeInHeight);
|
||||
let nextHeight = Math.max(0, _stateAtStart.height + changeInHeight);
|
||||
if (property === "height") {
|
||||
if (shouldChangeByStepSize) {
|
||||
nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
|
||||
@ -130,13 +131,13 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
...newOrigin(
|
||||
stateAtStart.x,
|
||||
stateAtStart.y,
|
||||
stateAtStart.width,
|
||||
stateAtStart.height,
|
||||
_stateAtStart.x,
|
||||
_stateAtStart.y,
|
||||
_stateAtStart.width,
|
||||
_stateAtStart.height,
|
||||
nextWidth,
|
||||
nextHeight,
|
||||
stateAtStart.angle,
|
||||
_stateAtStart.angle,
|
||||
),
|
||||
});
|
||||
}
|
||||
@ -145,7 +146,7 @@ const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
return (
|
||||
<DragInput
|
||||
label={property === "width" ? "W" : "H"}
|
||||
element={element}
|
||||
elements={[element]}
|
||||
dragInputCallback={handleDimensionChange}
|
||||
value={
|
||||
Math.round(
|
||||
|
@ -11,7 +11,7 @@ import clsx from "clsx";
|
||||
export type DragInputCallbackType = (
|
||||
accumulatedChange: number,
|
||||
instantChange: number,
|
||||
stateAtStart: ExcalidrawElement,
|
||||
stateAtStart: ExcalidrawElement[],
|
||||
shouldKeepAspectRatio: boolean,
|
||||
shouldChangeByStepSize: boolean,
|
||||
nextValue?: number,
|
||||
@ -20,7 +20,7 @@ export type DragInputCallbackType = (
|
||||
interface StatsDragInputProps {
|
||||
label: string | React.ReactNode;
|
||||
value: number;
|
||||
element: ExcalidrawElement;
|
||||
elements: ExcalidrawElement[];
|
||||
editable?: boolean;
|
||||
shouldKeepAspectRatio?: boolean;
|
||||
dragInputCallback: DragInputCallbackType;
|
||||
@ -30,7 +30,7 @@ const StatsDragInput = ({
|
||||
label,
|
||||
dragInputCallback,
|
||||
value,
|
||||
element,
|
||||
elements,
|
||||
editable = true,
|
||||
shouldKeepAspectRatio,
|
||||
}: StatsDragInputProps) => {
|
||||
@ -64,7 +64,7 @@ const StatsDragInput = ({
|
||||
y: number;
|
||||
} | null = null;
|
||||
|
||||
let stateAtStart: ExcalidrawElement | null = null;
|
||||
let stateAtStart: ExcalidrawElement[] | null = null;
|
||||
|
||||
let accumulatedChange: number | null = null;
|
||||
|
||||
@ -72,7 +72,9 @@ const StatsDragInput = ({
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (!stateAtStart) {
|
||||
stateAtStart = deepCopyElement(element);
|
||||
stateAtStart = elements.map((element) =>
|
||||
deepCopyElement(element),
|
||||
);
|
||||
}
|
||||
|
||||
if (!accumulatedChange) {
|
||||
@ -146,11 +148,12 @@ const StatsDragInput = ({
|
||||
dragInputCallback(
|
||||
0,
|
||||
0,
|
||||
element,
|
||||
elements,
|
||||
shouldKeepAspectRatio!!,
|
||||
false,
|
||||
v,
|
||||
);
|
||||
eventTarget.blur();
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
@ -22,6 +22,8 @@ const FontSize = ({ element, elementsMap }: FontSizeProps) => {
|
||||
shouldChangeByStepSize,
|
||||
nextValue,
|
||||
) => {
|
||||
const _stateAtStart = stateAtStart[0];
|
||||
if (_stateAtStart) {
|
||||
if (nextValue) {
|
||||
const nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||
|
||||
@ -37,8 +39,8 @@ const FontSize = ({ element, elementsMap }: FontSizeProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateAtStart && stateAtStart.type === "text") {
|
||||
const originalFontSize = Math.round(stateAtStart.fontSize);
|
||||
if (_stateAtStart.type === "text") {
|
||||
const originalFontSize = Math.round(_stateAtStart.fontSize);
|
||||
const changeInFontSize = Math.round(accumulatedChange);
|
||||
let nextFontSize = Math.max(
|
||||
originalFontSize + changeInFontSize,
|
||||
@ -57,13 +59,14 @@ const FontSize = ({ element, elementsMap }: FontSizeProps) => {
|
||||
fontSize: nextFontSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StatsDragInput
|
||||
label="F"
|
||||
value={Math.round(element.fontSize * 10) / 10}
|
||||
element={element}
|
||||
elements={[element]}
|
||||
dragInputCallback={handleFontSizeChange}
|
||||
/>
|
||||
);
|
||||
|
143
packages/excalidraw/components/Stats/MultiDimension.tsx
Normal file
143
packages/excalidraw/components/Stats/MultiDimension.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import { getCommonBounds } from "../../element";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import DragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue } from "./utils";
|
||||
|
||||
interface MultiDimensionProps {
|
||||
property: "width" | "height";
|
||||
elements: ExcalidrawElement[];
|
||||
}
|
||||
|
||||
const STEP_SIZE = 10;
|
||||
|
||||
const MultiDimension = ({ property, elements }: MultiDimensionProps) => {
|
||||
const handleDimensionChange: DragInputCallbackType = (
|
||||
accumulatedChange,
|
||||
instantChange,
|
||||
stateAtStart,
|
||||
shouldKeepAspectRatio,
|
||||
shouldChangeByStepSize,
|
||||
nextValue,
|
||||
) => {
|
||||
const [x1, y1, x2, y2] = getCommonBounds(stateAtStart);
|
||||
const initialWidth = x2 - x1;
|
||||
const initialHeight = y2 - y1;
|
||||
const keepAspectRatio = true;
|
||||
const aspectRatio = initialWidth / initialHeight;
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
const nextHeight =
|
||||
property === "height" ? nextValue : nextValue / aspectRatio;
|
||||
|
||||
const scale = nextHeight / initialHeight;
|
||||
const anchorX = property === "width" ? x1 : x1 + width / 2;
|
||||
const anchorY = property === "height" ? y1 : y1 + height / 2;
|
||||
|
||||
let i = 0;
|
||||
while (i < stateAtStart.length) {
|
||||
const element = elements[i];
|
||||
const origElement = stateAtStart[i];
|
||||
|
||||
// it should never happen that element and origElement are different
|
||||
// but check just in case
|
||||
if (element.id === origElement.id) {
|
||||
const offsetX = origElement.x - anchorX;
|
||||
const offsetY = origElement.y - anchorY;
|
||||
const nextWidth = origElement.width * scale;
|
||||
const nextHeight = origElement.height * scale;
|
||||
const x = anchorX + offsetX * scale;
|
||||
const y = anchorY + offsetY * scale;
|
||||
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
x,
|
||||
y,
|
||||
},
|
||||
i === stateAtStart.length - 1,
|
||||
);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const changeInWidth = property === "width" ? accumulatedChange : 0;
|
||||
const changeInHeight = property === "height" ? accumulatedChange : 0;
|
||||
|
||||
let nextWidth = Math.max(0, initialWidth + changeInWidth);
|
||||
if (property === "width") {
|
||||
if (shouldChangeByStepSize) {
|
||||
nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
|
||||
} else {
|
||||
nextWidth = Math.round(nextWidth);
|
||||
}
|
||||
}
|
||||
|
||||
let nextHeight = Math.max(0, initialHeight + changeInHeight);
|
||||
if (property === "height") {
|
||||
if (shouldChangeByStepSize) {
|
||||
nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
|
||||
} else {
|
||||
nextHeight = Math.round(nextHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (keepAspectRatio) {
|
||||
if (property === "width") {
|
||||
nextHeight = Math.round((nextWidth / aspectRatio) * 100) / 100;
|
||||
} else {
|
||||
nextWidth = Math.round(nextHeight * aspectRatio * 100) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
const scale = nextHeight / initialHeight;
|
||||
const anchorX = property === "width" ? x1 : x1 + width / 2;
|
||||
const anchorY = property === "height" ? y1 : y1 + height / 2;
|
||||
|
||||
let i = 0;
|
||||
while (i < stateAtStart.length) {
|
||||
const element = elements[i];
|
||||
const origElement = stateAtStart[i];
|
||||
|
||||
const offsetX = origElement.x - anchorX;
|
||||
const offsetY = origElement.y - anchorY;
|
||||
const nextWidth = origElement.width * scale;
|
||||
const nextHeight = origElement.height * scale;
|
||||
const x = anchorX + offsetX * scale;
|
||||
const y = anchorY + offsetY * scale;
|
||||
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
x,
|
||||
y,
|
||||
},
|
||||
i === stateAtStart.length - 1,
|
||||
);
|
||||
i++;
|
||||
}
|
||||
};
|
||||
|
||||
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||
const width = x2 - x1;
|
||||
const height = y2 - y1;
|
||||
|
||||
return (
|
||||
<DragInput
|
||||
label={property === "width" ? "W" : "H"}
|
||||
elements={elements}
|
||||
dragInputCallback={handleDimensionChange}
|
||||
value={Math.round((property === "width" ? width : height) * 100) / 100}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiDimension;
|
@ -23,6 +23,14 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.elementsCount {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.statsItem {
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
|
@ -13,6 +13,8 @@ import Angle from "./Angle";
|
||||
|
||||
import "./index.scss";
|
||||
import FontSize from "./FontSize";
|
||||
import MultiDimension from "./MultiDimension";
|
||||
import { elementsAreInSameGroup } from "../../groups";
|
||||
|
||||
interface StatsProps {
|
||||
appState: AppState;
|
||||
@ -32,6 +34,9 @@ export const Stats = (props: StatsProps) => {
|
||||
const singleElement =
|
||||
selectedElements.length === 1 ? selectedElements[0] : null;
|
||||
|
||||
const multipleElements =
|
||||
selectedElements.length > 1 ? selectedElements : null;
|
||||
|
||||
const [sceneDimension, setSceneDimension] = useState<{
|
||||
width: number;
|
||||
height: number;
|
||||
@ -91,7 +96,7 @@ export const Stats = (props: StatsProps) => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{singleElement && (
|
||||
{selectedElements.length > 0 && (
|
||||
<div
|
||||
className="section"
|
||||
style={{
|
||||
@ -100,6 +105,7 @@ export const Stats = (props: StatsProps) => {
|
||||
>
|
||||
<h3>{t("stats.elementStats")}</h3>
|
||||
|
||||
{singleElement && (
|
||||
<div className="sectionContent">
|
||||
<div className="elementType">
|
||||
{t(`element.${singleElement.type}`)}
|
||||
@ -110,12 +116,40 @@ export const Stats = (props: StatsProps) => {
|
||||
<Dimension property="height" element={singleElement} />
|
||||
<Angle element={singleElement} />
|
||||
{singleElement.type === "text" && (
|
||||
<FontSize element={singleElement} elementsMap={elementsMap} />
|
||||
<FontSize
|
||||
element={singleElement}
|
||||
elementsMap={elementsMap}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{singleElement.type === "text" && <div></div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{multipleElements && (
|
||||
<div className="sectionContent">
|
||||
{elementsAreInSameGroup(multipleElements) && (
|
||||
<div className="elementType">{t("element.group")}</div>
|
||||
)}
|
||||
|
||||
<div className="elementsCount">
|
||||
<div>{t("stats.elements")}</div>
|
||||
<div>{selectedElements.length}</div>
|
||||
</div>
|
||||
|
||||
<div className="statsItem">
|
||||
<MultiDimension
|
||||
property="width"
|
||||
elements={multipleElements}
|
||||
/>
|
||||
<MultiDimension
|
||||
property="height"
|
||||
elements={multipleElements}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Island>
|
||||
|
Loading…
x
Reference in New Issue
Block a user