Compare commits

...

6 Commits

Author SHA1 Message Date
zsviczian
56d1653d97 reintroduce copy/paste on Apple devices and on touch screens 2022-11-01 10:28:04 +01:00
David Luzar
4d26993c8f chore: fix yarn.lock file (#5803)
* chore: fix yarn.lock file

* ts fix
2022-10-31 13:44:08 +01:00
zsviczian
1e69609ce4 fix: consistent use of ZOOM_STEP (#5801)
introduce MIN_ZOOM, consistent use of ZOOM_STEP
2022-10-31 06:23:05 +01:00
Alex Kim
f5379d1563 fix: multiple elements resizing regressions (#5586) 2022-10-29 13:01:38 +02:00
David Luzar
c8f6e3faa8 fix: restore text dimensions (#5432)
* fix: restore text dimensions

* fix tests

* update readme & changelog

* reduce API surface area by always refreshing dimensions for full `restore()`
2022-10-28 23:31:56 +02:00
Aakansha Doshi
36bf17cf59 fix: changelog typo (#5795)
* fix: changelog typo

* fix

* fix

* Empty-Commit
2022-10-27 20:43:03 +05:30
16 changed files with 4175 additions and 3920 deletions

View File

@@ -74,9 +74,6 @@
"prettier": "2.6.2",
"rewire": "6.0.0"
},
"resolutions": {
"@typescript-eslint/typescript-estree": "5.10.2"
},
"engines": {
"node": ">=14.0.0"
},

View File

@@ -2,7 +2,7 @@ import { ColorPicker } from "../components/ColorPicker";
import { eraser, zoomIn, zoomOut } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { DarkModeToggle } from "../components/DarkModeToggle";
import { THEME, ZOOM_STEP } from "../constants";
import { MIN_ZOOM, THEME, ZOOM_STEP } from "../constants";
import { getCommonBounds, getNonDeletedElements } from "../element";
import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
@@ -206,7 +206,7 @@ const zoomValueToFitBoundsOnViewport = (
const zoomAdjustedToSteps =
Math.floor(smallestZoomValue / ZOOM_STEP) * ZOOM_STEP;
const clampedZoomValueToFitElements = Math.min(
Math.max(zoomAdjustedToSteps, ZOOM_STEP),
Math.max(zoomAdjustedToSteps, MIN_ZOOM),
1,
);
return clampedZoomValueToFitElements as NormalizedZoomValue;

View File

@@ -76,6 +76,7 @@ import {
THEME,
TOUCH_CTX_MENU_TIMEOUT,
VERTICAL_ALIGN,
ZOOM_STEP,
} from "../constants";
import { loadFromBlob } from "../data";
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
@@ -168,6 +169,7 @@ import {
isArrowKey,
KEYS,
isAndroid,
isDarwin,
} from "../keys";
import { distance2d, getGridPoint, isPathALoop } from "../math";
import { renderScene } from "../renderer/renderScene";
@@ -5977,7 +5979,7 @@ class App extends React.Component<AppProps, AppState> {
} else {
ContextMenu.push({
options: [
this.device.isMobile &&
(this.device.isTouchScreen || isDarwin) &&
navigator.clipboard && {
trackEvent: false,
name: "paste",
@@ -5989,7 +5991,9 @@ class App extends React.Component<AppProps, AppState> {
},
contextItemLabel: "labels.paste",
},
this.device.isMobile && navigator.clipboard && separator,
(this.device.isTouchScreen || isDarwin) &&
navigator.clipboard &&
separator,
probablySupportsClipboardBlob &&
elements.length > 0 &&
actionCopyAsPng,
@@ -6034,9 +6038,11 @@ class App extends React.Component<AppProps, AppState> {
} else {
ContextMenu.push({
options: [
this.device.isMobile && actionCut,
this.device.isMobile && navigator.clipboard && actionCopy,
this.device.isMobile &&
(this.device.isTouchScreen || isDarwin) && actionCut,
(this.device.isTouchScreen || isDarwin) &&
navigator.clipboard &&
actionCopy,
(this.device.isTouchScreen || isDarwin) &&
navigator.clipboard && {
name: "paste",
trackEvent: false,
@@ -6048,7 +6054,7 @@ class App extends React.Component<AppProps, AppState> {
},
contextItemLabel: "labels.paste",
},
this.device.isMobile && separator,
(this.device.isTouchScreen || isDarwin) && separator,
...options,
separator,
actionCopyStyles,
@@ -6097,7 +6103,7 @@ class App extends React.Component<AppProps, AppState> {
// note that event.ctrlKey is necessary to handle pinch zooming
if (event.metaKey || event.ctrlKey) {
const sign = Math.sign(deltaY);
const MAX_STEP = 10;
const MAX_STEP = ZOOM_STEP * 100;
const absDelta = Math.abs(deltaY);
let delta = deltaY;
if (absDelta > MAX_STEP) {

View File

@@ -122,6 +122,7 @@ export const TITLE_TIMEOUT = 10000;
export const VERSION_TIMEOUT = 30000;
export const SCROLL_TIMEOUT = 100;
export const ZOOM_STEP = 0.1;
export const MIN_ZOOM = 0.1;
export const HYPERLINK_TOOLTIP_DELAY = 300;
// Report a user inactive after IDLE_THRESHOLD milliseconds

View File

@@ -14,6 +14,7 @@ import {
getNonDeletedElements,
getNormalizedDimensions,
isInvisiblySmallElement,
refreshTextDimensions,
} from "../element";
import { isLinearElementType } from "../element/typeChecks";
import { randomId } from "../random";
@@ -138,6 +139,7 @@ const restoreElementWithProperties = <
const restoreElement = (
element: Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
refreshDimensions = true,
): typeof element | null => {
switch (element.type) {
case "text":
@@ -150,7 +152,7 @@ const restoreElement = (
fontSize = parseInt(fontPx, 10);
fontFamily = getFontFamilyByName(_fontFamily);
}
return restoreElementWithProperties(element, {
element = restoreElementWithProperties(element, {
fontSize,
fontFamily,
text: element.text ?? "",
@@ -160,6 +162,11 @@ const restoreElement = (
containerId: element.containerId ?? null,
originalText: element.originalText || element.text,
});
if (refreshDimensions) {
element = { ...element, ...refreshTextDimensions(element) };
}
return element;
case "freedraw": {
return restoreElementWithProperties(element, {
points: element.points,
@@ -232,13 +239,17 @@ export const restoreElements = (
elements: ImportedDataState["elements"],
/** NOTE doesn't serve for reconciliation */
localElements: readonly ExcalidrawElement[] | null | undefined,
refreshDimensions = true,
): ExcalidrawElement[] => {
const localElementsMap = localElements ? arrayToMap(localElements) : null;
return (elements || []).reduce((elements, element) => {
// filtering out selection, which is legacy, no longer kept in elements,
// and causing issues if retained
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
let migratedElement: ExcalidrawElement | null = restoreElement(element);
let migratedElement: ExcalidrawElement | null = restoreElement(
element,
refreshDimensions,
);
if (migratedElement) {
const localElement = localElementsMap?.get(element.id);
if (localElement && localElement.version > migratedElement.version) {
@@ -376,7 +387,7 @@ export const restore = (
localElements: readonly ExcalidrawElement[] | null | undefined,
): RestoredDataState => {
return {
elements: restoreElements(data?.elements, localElements),
elements: restoreElements(data?.elements, localElements, true),
appState: restoreAppState(data?.appState, localAppState || null),
files: data?.files || {},
};

View File

@@ -22,7 +22,7 @@ const _ce = ({
backgroundColor: "#000",
fillStyle: "solid",
strokeWidth: 1,
roughness: 1,
roughness: 0,
opacity: 1,
x,
y,
@@ -106,7 +106,7 @@ describe("getElementBounds", () => {
} as ExcalidrawLinearElement);
expect(x1).toEqual(360.3176068760539);
expect(y1).toEqual(185.90654264413516);
expect(x2).toEqual(473.8171188951176);
expect(y2).toEqual(320.391865303557);
expect(x2).toEqual(480.87005902729743);
expect(y2).toEqual(320.4751269334226);
});
});

View File

@@ -387,34 +387,49 @@ export const getArrowheadPoints = (
return [x2, y2, x3, y3, x4, y4];
};
const generateLinearElementShape = (
element: ExcalidrawLinearElement,
): Drawable => {
const generator = rough.generator();
const options = generateRoughOptions(element);
const method = (() => {
if (element.strokeSharpness !== "sharp") {
return "curve";
}
if (options.fill) {
return "polygon";
}
return "linearPath";
})();
return generator[method](element.points as Mutable<Point>[], options);
};
const getLinearElementRotatedBounds = (
element: ExcalidrawLinearElement,
cx: number,
cy: number,
): [number, number, number, number] => {
if (element.points.length < 2 || !getShapeForElement(element)) {
// XXX this is just a poor estimate and not very useful
const { minX, minY, maxX, maxY } = element.points.reduce(
(limits, [x, y]) => {
[x, y] = rotate(element.x + x, element.y + y, cx, cy, element.angle);
limits.minY = Math.min(limits.minY, y);
limits.minX = Math.min(limits.minX, x);
limits.maxX = Math.max(limits.maxX, x);
limits.maxY = Math.max(limits.maxY, y);
return limits;
},
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
if (element.points.length < 2) {
const [pointX, pointY] = element.points[0];
const [x, y] = rotate(
element.x + pointX,
element.y + pointY,
cx,
cy,
element.angle,
);
return [minX, minY, maxX, maxY];
return [x, y, x, y];
}
const shape = getShapeForElement(element)!;
// first element is always the curve
const ops = getCurvePathOps(shape[0]);
const cachedShape = getShapeForElement(element)?.[0];
const shape = cachedShape ?? generateLinearElementShape(element);
const ops = getCurvePathOps(shape);
const transformXY = (x: number, y: number) =>
rotate(element.x + x, element.y + y, cx, cy, element.angle);
return getMinMaxXYFromCurvePathOps(ops, transformXY);
};
@@ -538,6 +553,7 @@ export const getResizedElementAbsoluteCoords = (
points as [number, number][],
generateRoughOptions(element),
);
const ops = getCurvePathOps(curve);
bounds = getMinMaxXYFromCurvePathOps(ops);
}

View File

@@ -10,6 +10,7 @@ export {
newElement,
newTextElement,
updateTextElement,
refreshTextDimensions,
newLinearElement,
newImageElement,
duplicateElement,

View File

@@ -252,6 +252,23 @@ const getAdjustedDimensions = (
};
};
export const refreshTextDimensions = (
textElement: ExcalidrawTextElement,
text = textElement.text,
) => {
const container = getContainerElement(textElement);
if (container) {
// text = wrapText(text, getFontString(textElement), container.width);
text = wrapText(
text,
getFontString(textElement),
getMaxContainerWidth(container),
);
}
const dimensions = getAdjustedDimensions(textElement, text);
return { text, ...dimensions };
};
export const getMaxContainerWidth = (container: ExcalidrawElement) => {
return getContainerDims(container).width - BOUND_TEXT_PADDING * 2;
};
@@ -272,20 +289,10 @@ export const updateTextElement = (
originalText: string;
},
): ExcalidrawTextElement => {
const container = getContainerElement(textElement);
if (container) {
text = wrapText(
originalText,
getFontString(textElement),
getMaxContainerWidth(container),
);
}
const dimensions = getAdjustedDimensions(textElement, text);
return newElementWith(textElement, {
text,
originalText,
isDeleted: isDeleted ?? textElement.isDeleted,
...dimensions,
...refreshTextDimensions(textElement, originalText),
});
};

View File

@@ -721,7 +721,7 @@ const resizeMultipleElements = (
(pointerSideY * Math.abs(pointerY - anchorY)) / (maxY - minY),
) * (shouldResizeFromCenter ? 2 : 1);
if (scale === 1) {
if (scale === 0) {
return;
}
@@ -766,21 +766,26 @@ const resizeMultipleElements = (
width - optionalPadding,
height - optionalPadding,
);
if (textMeasurements) {
if (isTextElement(element.orig)) {
update.fontSize = textMeasurements.size;
update.baseline = textMeasurements.baseline;
}
if (boundTextElement) {
boundTextUpdates = {
fontSize: textMeasurements.size,
baseline: textMeasurements.baseline,
};
}
if (!textMeasurements) {
return;
}
if (isTextElement(element.orig)) {
update.fontSize = textMeasurements.size;
update.baseline = textMeasurements.baseline;
}
if (boundTextElement) {
boundTextUpdates = {
fontSize: textMeasurements.size,
baseline: textMeasurements.baseline,
};
}
}
updateBoundElements(element.latest, { newSize: { width, height } });
mutateElement(element.latest, update);
if (boundTextElement && boundTextUpdates) {

View File

@@ -583,7 +583,7 @@ class Collab extends PureComponent<Props, CollabState> {
const localElements = this.getSceneElementsIncludingDeleted();
const appState = this.excalidrawAPI.getAppState();
remoteElements = restoreElements(remoteElements, null);
remoteElements = restoreElements(remoteElements, null, false);
const reconciledElements = _reconcileElements(
localElements,

View File

@@ -17,11 +17,12 @@ Please add the latest change on the top under the correct section.
#### Features
- `restoreElements()` now takes an optional parameter to indicate whether we should also recalculate text element dimensions. Defaults to `true`, but since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration [#5432](https://github.com/excalidraw/excalidraw/pull/5432).
- Support rendering custom sidebar using [`renderSidebar`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderSidebar) prop ([#5663](https://github.com/excalidraw/excalidraw/pull/5663)).
- Add [`toggleMenu`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onMenuToggle) prop to toggle specific menu open/close state ([#5663](https://github.com/excalidraw/excalidraw/pull/5663)).
- Add [`toggleMenu`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#toggleMenu) prop to toggle specific menu open/close state ([#5663](https://github.com/excalidraw/excalidraw/pull/5663)).
- Support [theme](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#theme) to be semi-controlled [#5660](https://github.com/excalidraw/excalidraw/pull/5660).
- Added support for storing [`customData`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#storing-custom-data-to-excalidraw-elements) on Excalidraw elements [#5592].
- Added `exportPadding?: number;` to [exportToCanvas](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttocanvas) and [exportToBlob](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttoblob). The default value of the padding is 10.
- Added support for storing [`customData`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#storing-custom-data-on-excalidraw-elements) on Excalidraw elements [#5592].
- Added `exportPadding?: number;` to [exportToCanvas](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttocanvas) and [exportToBlob](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttoblob). The default value of the padding is `10`.
#### Breaking Changes

View File

@@ -915,7 +915,11 @@ When `localAppState` is supplied, it's used in place of values that are missing
**_Signature_**
<pre>
restoreElements(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L16">ImportedDataState["elements"]</a>, localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L16">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106">ExcalidrawElement[]</a>
restoreElements(
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L16">ImportedDataState["elements"]</a>,
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L16">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106">ExcalidrawElement[]</a>,
refreshDimensions: boolean
)
</pre>
**_How to use_**
@@ -928,12 +932,18 @@ This function will make sure all properties of element is correctly set and if a
When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. Use this when you import elements which may already be present in the scene to ensure that you do not disregard the newly imported elements if you're using element version to detect the updates.
Parameter `refreshDimensions` indicates whether we should also recalculate text element dimensions. Defaults to `true`, but since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration.
#### `restore`
**_Signature_**
<pre>
restoreElements(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L12">ImportedDataState</a>, localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79">AppState</a>> | null | undefined, localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L16">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a>
restoreElements(
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L12">ImportedDataState</a>,
localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79">AppState</a>> | null | undefined,
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L16">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a>
)
</pre>
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.

View File

@@ -1,7 +1,8 @@
import { MIN_ZOOM } from "../constants";
import { AppState, NormalizedZoomValue } from "../types";
export const getNormalizedZoom = (zoom: number): NormalizedZoomValue => {
return Math.max(0.1, Math.min(zoom, 30)) as NormalizedZoomValue;
return Math.max(MIN_ZOOM, Math.min(zoom, 30)) as NormalizedZoomValue;
};
export const getStateForZoom = (

View File

@@ -275,7 +275,7 @@ Object {
"fontFamily": 1,
"fontSize": 14,
"groupIds": Array [],
"height": 100,
"height": 0,
"id": "id-text01",
"isDeleted": false,
"link": null,
@@ -295,7 +295,7 @@ Object {
"version": 1,
"versionNonce": 0,
"verticalAlign": "middle",
"width": 100,
"width": 1,
"x": -0.5,
"y": 0,
}
@@ -312,7 +312,7 @@ Object {
"fontFamily": 1,
"fontSize": 10,
"groupIds": Array [],
"height": 100,
"height": 0,
"id": "id-text01",
"isDeleted": false,
"link": null,
@@ -332,7 +332,7 @@ Object {
"version": 1,
"versionNonce": 0,
"verticalAlign": "top",
"width": 100,
"width": 1,
"x": 0,
"y": 0,
}

7901
yarn.lock

File diff suppressed because it is too large Load Diff