Compare commits

...

19 Commits

Author SHA1 Message Date
zsviczian b412e742e6 Update MobileMenu.tsx 2022-11-14 18:08:19 +01:00
zsviczian c246ccf9d9 Update LayerUI.tsx 2022-11-14 18:05:56 +01:00
dependabot[bot] 3c0b29d85f build(deps): bump loader-utils from 2.0.0 to 2.0.4 in /src/packages/utils (#5874)
build(deps): bump loader-utils in /src/packages/utils

Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.0 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.0...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 15:49:05 +05:30
DanielJGeiger bfbaeae67f fix: Correctly paste contents parsed by JSON.parse() as text. (#5868)
* Fix #5867

* Add test.

* Add tests to clipboard.test.ts

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2022-11-14 14:02:54 +05:30
dependabot[bot] 74b9885955 build(deps): bump minimatch from 3.0.4 to 3.1.2 in /src/packages/excalidraw (#5861)
build(deps): bump minimatch in /src/packages/excalidraw

Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:51:10 +05:30
dependabot[bot] 2cbe869a13 build(deps): bump socket.io-parser from 3.3.2 to 3.3.3 (#5862)
Bumps [socket.io-parser](https://github.com/socketio/socket.io-parser) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/socketio/socket.io-parser/releases)
- [Changelog](https://github.com/socketio/socket.io-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io-parser/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:50:15 +05:30
dependabot[bot] a48607eb25 build(deps): bump loader-utils from 2.0.2 to 2.0.3 in /src/packages/excalidraw (#5851)
build(deps): bump loader-utils in /src/packages/excalidraw

Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.3/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.3)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:49:39 +05:30
zsviczian 7831b6e74b fix: SVG element attributes in icons.tsx (#5871)
Update icons.tsx
2022-11-14 11:42:28 +05:30
dependabot[bot] 640affe7c0 build(deps): bump loader-utils from 2.0.2 to 2.0.3 in /dev-docs (#5853)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.3/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.3)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-10 15:02:18 +05:30
DanielJGeiger 335aff8838 fix: merge existing text with new when pasted (#5856)
* Fix #5855.

* fix test

* tweak

* Add specs

* Add more snaps

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2022-11-09 23:39:53 +05:30
Aakansha Doshi dc97dc30bf fix: disable FAST_REFRESH to fix live reload (#5852) 2022-11-09 17:13:20 +05:30
DanielJGeiger a0ecfed4cd fix: Paste clipboard contents into unbound text elements (#5849)
* Fix #5848.

* Add test.

* some tweaks

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2022-11-09 11:30:22 +05:30
Aakansha Doshi e201e79cd0 fix: compute dimensions of container correctly when text pasted on container (#5845)
* fix: compute dimensions of container correctly when text pasted on container

* add test

* remove only
2022-11-08 19:50:41 +05:30
Pritam Sangani e1c5c706c6 build: stops ignoring .env files from docker context so env variables get set during react app build. (#5809)
build: stops ignoring .env.development and .env.production files from docker context so env variables get set during react app build.
* this fixes the issue where Browse Libraries button link was broken in
  docker/self-hosted versions of excalidraw
2022-11-07 16:48:38 +05:30
David Luzar bdc56090d7 feat: reintroduce x shortcut for freedraw (#5840) 2022-11-06 23:07:15 +01:00
David Luzar 58accc9310 feat: tweak toolbar shortcuts & remove library shortcut (#5832) 2022-11-06 20:14:53 +01:00
David Luzar b91158198e feat: clean unused images only after 24hrs (local-only) (#5839)
* feat: clean unused images only after 24hrs (local-only)

* fix test

* make optional for now
2022-11-06 19:41:14 +01:00
David Luzar 938ce241ff feat: refetch errored/pending images on collab room init load (#5833) 2022-11-05 15:55:14 +01:00
David Luzar 0228646507 fix: line editor points rendering below elements (#5781)
* fix: line editor points rendering below elements

* add test
2022-11-05 11:35:53 +01:00
33 changed files with 677 additions and 328 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
*
!.env
!.env.development
!.env.production
!.eslintrc.json
!.npmrc
!.prettierrc
+2
View File
@@ -20,3 +20,5 @@ REACT_APP_DEV_ENABLE_SW=
# whether to disable live reload / HMR. Usuaully what you want to do when
# debugging Service Workers.
REACT_APP_DEV_DISABLE_LIVE_RELOAD=
FAST_REFRESH=false
+3 -3
View File
@@ -4755,9 +4755,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.3.tgz#d4b15b8504c63d1fc3f2ade52d41bc8459d6ede1"
integrity sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
+27
View File
@@ -0,0 +1,27 @@
import { parseClipboard } from "./clipboard";
describe("Test parseClipboard", () => {
it("should parse valid json correctly", async () => {
let text = "123";
let clipboardData = await parseClipboard({
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
expect(clipboardData.text).toBe(text);
text = "[123]";
clipboardData = await parseClipboard({
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
expect(clipboardData.text).toBe(text);
});
});
+7 -9
View File
@@ -156,15 +156,13 @@ export const parseClipboard = async (
files: systemClipboardData.files,
};
}
return appClipboardData;
} catch {
// system clipboard doesn't contain excalidraw elements → return plaintext
// unless we set a flag to prefer in-app clipboard because browser didn't
// support storing to system clipboard on copy
return PREFER_APP_CLIPBOARD && appClipboardData.elements
? appClipboardData
: { text: systemClipboard };
}
} catch (e) {}
// system clipboard doesn't contain excalidraw elements → return plaintext
// unless we set a flag to prefer in-app clipboard because browser didn't
// support storing to system clipboard on copy
return PREFER_APP_CLIPBOARD && appClipboardData.elements
? appClipboardData
: { text: systemClipboard };
};
export const copyBlobToClipboardAsPng = async (blob: Blob | Promise<Blob>) => {
+4 -5
View File
@@ -218,13 +218,12 @@ export const ShapesSwitcher = ({
appState: AppState;
}) => (
<>
{SHAPES.map(({ value, icon, key, fillable }, index) => {
const numberKey = value === "eraser" ? 0 : index + 1;
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
const label = t(`toolBar.${value}`);
const letter = key && (typeof key === "string" ? key : key[0]);
const shortcut = letter
? `${capitalizeString(letter)} ${t("helpDialog.or")} ${numberKey}`
: `${numberKey}`;
? `${capitalizeString(letter)} ${t("helpDialog.or")} ${numericKey}`
: `${numericKey}`;
return (
<ToolButton
className={clsx("Shape", { fillable })}
@@ -234,7 +233,7 @@ export const ShapesSwitcher = ({
checked={activeTool.type === value}
name="editor-current-shape"
title={`${capitalizeString(label)}${shortcut}`}
keyBindingLabel={`${numberKey}`}
keyBindingLabel={numericKey}
aria-label={capitalizeString(label)}
aria-keyshortcuts={shortcut}
data-testid={`toolbar-${value}`}
+1 -12
View File
@@ -1912,18 +1912,6 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ isBindingEnabled: false });
}
if (event.code === CODES.ZERO) {
const nextState = this.toggleMenu("library");
// track only openings
if (nextState) {
trackEvent(
"library",
"toggleLibrary (open)",
`keyboard (${this.device.isMobile ? "mobile" : "desktop"})`,
);
}
}
if (isArrowKey(event.key)) {
const step =
(this.state.gridSize &&
@@ -5243,6 +5231,7 @@ class App extends React.Component<AppProps, AppState> {
id: fileId,
dataURL,
created: Date.now(),
lastRetrieved: Date.now(),
},
};
const cachedImageData = this.imageCache.get(fileId);
+33 -13
View File
@@ -1,6 +1,6 @@
import React from "react";
import { t } from "../i18n";
import { isDarwin, isWindows } from "../keys";
import { isDarwin, isWindows, KEYS } from "../keys";
import { Dialog } from "./Dialog";
import { getShortcutKey } from "../utils";
import "./HelpDialog.scss";
@@ -118,22 +118,42 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
className="HelpDialog__island--tools"
caption={t("helpDialog.tools")}
>
<Shortcut label={t("toolBar.selection")} shortcuts={["V", "1"]} />
<Shortcut label={t("toolBar.rectangle")} shortcuts={["R", "2"]} />
<Shortcut label={t("toolBar.diamond")} shortcuts={["D", "3"]} />
<Shortcut label={t("toolBar.ellipse")} shortcuts={["O", "4"]} />
<Shortcut label={t("toolBar.arrow")} shortcuts={["A", "5"]} />
<Shortcut label={t("toolBar.line")} shortcuts={["P", "6"]} />
<Shortcut
label={t("toolBar.selection")}
shortcuts={[KEYS.V, KEYS["1"]]}
/>
<Shortcut
label={t("toolBar.rectangle")}
shortcuts={[KEYS.R, KEYS["2"]]}
/>
<Shortcut
label={t("toolBar.diamond")}
shortcuts={[KEYS.D, KEYS["3"]]}
/>
<Shortcut
label={t("toolBar.ellipse")}
shortcuts={[KEYS.O, KEYS["4"]]}
/>
<Shortcut
label={t("toolBar.arrow")}
shortcuts={[KEYS.A, KEYS["5"]]}
/>
<Shortcut
label={t("toolBar.line")}
shortcuts={[KEYS.P, KEYS["6"]]}
/>
<Shortcut
label={t("toolBar.freedraw")}
shortcuts={["Shift + P", "X", "7"]}
shortcuts={["Shift + P", KEYS["7"]]}
/>
<Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
<Shortcut label={t("toolBar.image")} shortcuts={["9"]} />
<Shortcut label={t("toolBar.library")} shortcuts={["0"]} />
<Shortcut
label={t("toolBar.text")}
shortcuts={[KEYS.T, KEYS["8"]]}
/>
<Shortcut label={t("toolBar.image")} shortcuts={[KEYS["9"]]} />
<Shortcut
label={t("toolBar.eraser")}
shortcuts={[getShortcutKey("E")]}
shortcuts={[KEYS.E, KEYS["0"]]}
/>
<Shortcut
label={t("helpDialog.editSelectedShape")}
@@ -173,7 +193,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
]}
isOr={false}
/>
<Shortcut label={t("toolBar.lock")} shortcuts={["Q"]} />
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
<Shortcut
label={t("helpDialog.preventBinding")}
shortcuts={[getShortcutKey("CtrlOrCmd")]}
+2 -1
View File
@@ -411,7 +411,8 @@ const LayerUI = ({
onClick={onCollabButtonClick}
/>
)}
{renderTopRightUI?.(device.isMobile, appState)}
{!appState.viewModeEnabled &&
renderTopRightUI?.(device.isMobile, appState)}
{!appState.viewModeEnabled && (
<LibraryButton appState={appState} setAppState={setAppState} />
)}
+1 -1
View File
@@ -22,7 +22,7 @@ export const LibraryButton: React.FC<{
}
return (
<label title={`${capitalizeString(t("toolBar.library"))} — 0`}>
<label title={`${capitalizeString(t("toolBar.library"))}`}>
<input
className="ToolIcon_type_checkbox"
type="checkbox"
+2 -1
View File
@@ -111,8 +111,9 @@ export const MobileMenu = ({
/>
</Stack.Row>
</Island>
{renderTopRightUI && renderTopRightUI(true, appState)}
<div className="mobile-misc-tools-container">
{!appState.viewModeEnabled &&
renderTopRightUI?.(true, appState)}
<PenModeButton
checked={appState.penMode}
onChange={onPenModeToggle}
+9 -9
View File
@@ -1470,11 +1470,11 @@ export const TextAlignRightIcon = createIcon(
export const TextAlignTopIcon = React.memo(({ theme }: { theme: Theme }) =>
createIcon(
<g
stroke-width="1.5"
strokeWidth="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="4" y1="4" x2="20" y2="4" />
@@ -1488,11 +1488,11 @@ export const TextAlignTopIcon = React.memo(({ theme }: { theme: Theme }) =>
export const TextAlignBottomIcon = React.memo(({ theme }: { theme: Theme }) =>
createIcon(
<g
stroke-width="2"
strokeWidth="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="4" y1="20" x2="20" y2="20" />
@@ -1506,11 +1506,11 @@ export const TextAlignBottomIcon = React.memo(({ theme }: { theme: Theme }) =>
export const TextAlignMiddleIcon = React.memo(({ theme }: { theme: Theme }) =>
createIcon(
<g
stroke-width="1.5"
strokeWidth="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="4" y1="12" x2="9" y2="12" />
+1 -3
View File
@@ -169,8 +169,7 @@ const getAdjustedDimensions = (
let maxWidth = null;
const container = getContainerElement(element);
if (container) {
const containerDims = getContainerDims(container);
maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
maxWidth = getMaxContainerWidth(container);
}
const {
width: nextWidth,
@@ -258,7 +257,6 @@ export const refreshTextDimensions = (
) => {
const container = getContainerElement(textElement);
if (container) {
// text = wrapText(text, getFontString(textElement), container.width);
text = wrapText(
text,
getFontString(textElement),
+2 -4
View File
@@ -19,13 +19,12 @@ export const redrawTextBoundingBox = (
) => {
let maxWidth = undefined;
let text = textElement.text;
if (container) {
maxWidth = getMaxContainerWidth(container);
text = wrapText(
textElement.originalText,
getFontString(textElement),
getMaxContainerWidth(container),
maxWidth,
);
}
const metrics = measureText(
@@ -230,10 +229,9 @@ export const measureText = (
const baseline = span.offsetTop + span.offsetHeight;
// Since span adds 1px extra width to the container
const width = container.offsetWidth + 1;
const height = container.offsetHeight;
document.body.removeChild(container);
document.body.removeChild(container);
return { width, height, baseline };
};
+158
View File
@@ -10,11 +10,13 @@ import { BOUND_TEXT_PADDING, FONT_FAMILY } from "../constants";
import {
ExcalidrawTextElement,
ExcalidrawTextElementWithContainer,
FontString,
} from "./types";
import * as textElementUtils from "./textElement";
import { API } from "../tests/helpers/api";
import { mutateElement } from "./mutateElement";
import { resize } from "../tests/utils";
import { getMaxContainerWidth } from "./newElement";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -433,6 +435,42 @@ describe("textWysiwyg", () => {
);
expect(h.state.zoom.value).toBe(1);
});
it("should paste text correctly", async () => {
Keyboard.keyPress(KEYS.ENTER);
await new Promise((r) => setTimeout(r, 0));
let text = "A quick brown fox jumps over the lazy dog.";
//@ts-ignore
textarea.onpaste({
preventDefault: () => {},
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
await new Promise((cb) => setTimeout(cb, 0));
textarea.blur();
expect(textElement.text).toBe(text);
Keyboard.keyPress(KEYS.ENTER);
await new Promise((r) => setTimeout(r, 0));
text = "Hello this text should get merged with the existing one";
//@ts-ignore
textarea.onpaste({
preventDefault: () => {},
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
await new Promise((cb) => setTimeout(cb, 0));
textarea.blur();
expect(textElement.text).toMatchInlineSnapshot(
`"A quick brown fox jumps over the lazy dog.Hello this text should get merged with the existing one"`,
);
});
});
describe("Test container-bound text", () => {
@@ -876,5 +914,125 @@ describe("textWysiwyg", () => {
]
`);
});
it("should compute the dimensions correctly when text pasted", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
await new Promise((r) => setTimeout(r, 0));
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
let text =
"Wikipedia is hosted by the Wikimedia Foundation, a non-profit organization that also hosts a range of other projects.";
let wrappedText = textElementUtils.wrapText(
text,
font,
getMaxContainerWidth(rectangle),
);
jest
.spyOn(textElementUtils, "measureText")
.mockImplementation((text, font, maxWidth) => {
if (text === wrappedText) {
return { width: rectangle.width, height: 200, baseline: 30 };
}
return { width: 0, height: 0, baseline: 0 };
});
//@ts-ignore
editor.onpaste({
preventDefault: () => {},
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
await new Promise((cb) => setTimeout(cb, 0));
editor.blur();
expect(rectangle.width).toBe(100);
expect(rectangle.height).toBe(210);
expect((h.elements[1] as ExcalidrawTextElement).text)
.toMatchInlineSnapshot(`
"Wikipedi
a is
hosted
by the
Wikimedi
a
Foundati
on, a
non-prof
it
organiza
tion
that
also
hosts a
range of
other
projects
."
`);
expect(
(h.elements[1] as ExcalidrawTextElement).originalText,
).toMatchInlineSnapshot(
`"Wikipedia is hosted by the Wikimedia Foundation, a non-profit organization that also hosts a range of other projects."`,
);
text = "Hello this text should get merged with the existing one";
wrappedText = textElementUtils.wrapText(
text,
font,
getMaxContainerWidth(rectangle),
);
//@ts-ignore
editor.onpaste({
preventDefault: () => {},
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
await new Promise((cb) => setTimeout(cb, 0));
editor.blur();
expect((h.elements[1] as ExcalidrawTextElement).text)
.toMatchInlineSnapshot(`
"Wikipedi
a is
hosted
by the
Wikimedi
a
Foundati
on, a
non-prof
it
organiza
tion
that
also
hosts a
range of
other
projects
.Hello
this
text
should
get
merged
with the
existing
one"
`);
expect(
(h.elements[1] as ExcalidrawTextElement).originalText,
).toMatchInlineSnapshot(
`"Wikipedia is hosted by the Wikimedia Foundation, a non-profit organization that also hosts a range of other projects.Hello this text should get merged with the existing one"`,
);
});
});
});
+34
View File
@@ -20,6 +20,7 @@ import {
getBoundTextElementId,
getContainerDims,
getContainerElement,
measureText,
wrapText,
} from "./textElement";
import {
@@ -29,6 +30,7 @@ import {
import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas";
import App from "../components/App";
import { getMaxContainerWidth } from "./newElement";
import { parseClipboard } from "../clipboard";
const normalizeText = (text: string) => {
return (
@@ -275,6 +277,38 @@ export const textWysiwyg = ({
updateWysiwygStyle();
if (onChange) {
editable.onpaste = async (event) => {
event.preventDefault();
const clipboardData = await parseClipboard(event);
if (!clipboardData.text) {
return;
}
const data = normalizeText(clipboardData.text);
if (!data) {
return;
}
const text = editable.value;
const start = Math.min(editable.selectionStart, editable.selectionEnd);
const end = Math.max(editable.selectionStart, editable.selectionEnd);
const newText = `${text.substring(0, start)}${data}${text.substring(
end,
)}`;
const container = getContainerElement(element);
const font = getFontString({
fontSize: app.state.currentItemFontSize,
fontFamily: app.state.currentItemFontFamily,
});
const wrappedText = container
? wrapText(newText, font, getMaxContainerWidth(container))
: newText;
const dimensions = measureText(wrappedText, font);
editable.style.height = `${dimensions.height}px`;
onChange(newText);
};
editable.oninput = () => {
const updatedTextElement = Scene.getScene(element)?.getElement(
id,
+14 -3
View File
@@ -310,16 +310,27 @@ class Collab extends PureComponent<Props, CollabState> {
}
};
private fetchImageFilesFromFirebase = async (scene: {
private fetchImageFilesFromFirebase = async (opts: {
elements: readonly ExcalidrawElement[];
/**
* Indicates whether to fetch files that are errored or pending and older
* than 10 seconds.
*
* Use this as a machanism to fetch files which may be ok but for some
* reason their status was not updated correctly.
*/
forceFetchFiles?: boolean;
}) => {
const unfetchedImages = scene.elements
const unfetchedImages = opts.elements
.filter((element) => {
return (
isInitializedImageElement(element) &&
!this.fileManager.isFileHandled(element.fileId) &&
!element.isDeleted &&
element.status === "saved"
(opts.forceFetchFiles
? element.status !== "pending" ||
Date.now() - element.updated > 10000
: element.status === "saved")
);
})
.map((element) => (element as InitializedExcalidrawImageElement).fileId);
+1
View File
@@ -195,6 +195,7 @@ export const encodeFilesForUpload = async ({
id,
mimeType: fileData.mimeType,
created: Date.now(),
lastRetrieved: Date.now(),
},
});
+32 -8
View File
@@ -10,7 +10,7 @@
* (localStorage, indexedDB).
*/
import { createStore, keys, del, getMany, set } from "idb-keyval";
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
import { clearAppStateForLocalStorage } from "../../appState";
import { clearElementsForLocalStorage } from "../../element";
import { ExcalidrawElement, FileId } from "../../element/types";
@@ -25,12 +25,21 @@ const filesStore = createStore("files-db", "files-store");
class LocalFileManager extends FileManager {
clearObsoleteFiles = async (opts: { currentFileIds: FileId[] }) => {
const allIds = await keys(filesStore);
for (const id of allIds) {
if (!opts.currentFileIds.includes(id as FileId)) {
del(id, filesStore);
await entries(filesStore).then((entries) => {
for (const [id, imageData] of entries as [FileId, BinaryFileData][]) {
// if image is unused (not on canvas) & is older than 1 day, delete it
// from storage. We check `lastRetrieved` we care about the last time
// the image was used (loaded on canvas), not when it was initially
// created.
if (
(!imageData.lastRetrieved ||
Date.now() - imageData.lastRetrieved > 24 * 3600 * 1000) &&
!opts.currentFileIds.includes(id as FileId)
) {
del(id, filesStore);
}
}
}
});
};
}
@@ -111,18 +120,33 @@ export class LocalData {
static fileStorage = new LocalFileManager({
getFiles(ids) {
return getMany(ids, filesStore).then(
(filesData: (BinaryFileData | undefined)[]) => {
async (filesData: (BinaryFileData | undefined)[]) => {
const loadedFiles: BinaryFileData[] = [];
const erroredFiles = new Map<FileId, true>();
const filesToSave: [FileId, BinaryFileData][] = [];
filesData.forEach((data, index) => {
const id = ids[index];
if (data) {
loadedFiles.push(data);
const _data: BinaryFileData = {
...data,
lastRetrieved: Date.now(),
};
filesToSave.push([id, _data]);
loadedFiles.push(_data);
} else {
erroredFiles.set(id, true);
}
});
try {
// save loaded files back to storage with updated `lastRetrieved`
setMany(filesToSave, filesStore);
} catch (error) {
console.warn(error);
}
return { loadedFiles, erroredFiles };
},
);
+1
View File
@@ -330,6 +330,7 @@ export const loadFilesFromFirebase = async (
id,
dataURL,
created: metadata?.created || Date.now(),
lastRetrieved: metadata?.created || Date.now(),
});
} else {
erroredFiles.set(id, true);
+1
View File
@@ -285,6 +285,7 @@ const ExcalidrawWrapper = () => {
collabAPI
.fetchImageFilesFromFirebase({
elements: data.scene.elements,
forceFetchFiles: true,
})
.then(({ loadedFiles, erroredFiles }) => {
excalidrawAPI.addFiles(loadedFiles);
+11
View File
@@ -63,6 +63,17 @@ export const KEYS = {
Y: "y",
Z: "z",
K: "k",
0: "0",
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
9: "9",
} as const;
export type Key = keyof typeof KEYS;
+1
View File
@@ -148,6 +148,7 @@ export default function App() {
dataURL: reader.result as BinaryFileData["dataURL"],
mimeType: MIME_TYPES.jpg,
created: 1644915140367,
lastRetrieved: 1644915140367,
},
];
+7 -7
View File
@@ -1873,7 +1873,7 @@ compression@^1.7.4:
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
connect-history-api-fallback@^2.0.0:
version "2.0.0"
@@ -2697,9 +2697,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.3.tgz#d4b15b8504c63d1fc3f2ade52d41bc8459d6ede1"
integrity sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@@ -2823,9 +2823,9 @@ minimalistic-assert@^1.0.0:
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
+3 -3
View File
@@ -1915,9 +1915,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
+13 -7
View File
@@ -406,6 +406,8 @@ export const _renderScene = ({
}),
);
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
undefined;
visibleElements.forEach((element) => {
try {
renderElement(element, rc, context, renderConfig);
@@ -414,15 +416,10 @@ export const _renderScene = ({
// correct element from visible elements
if (appState.editingLinearElement?.elementId === element.id) {
if (element) {
renderLinearPointHandles(
context,
appState,
renderConfig,
element as NonDeleted<ExcalidrawLinearElement>,
);
editingLinearElement =
element as NonDeleted<ExcalidrawLinearElement>;
}
}
if (!isExporting) {
renderLinkIcon(element, context, appState);
}
@@ -431,6 +428,15 @@ export const _renderScene = ({
}
});
if (editingLinearElement) {
renderLinearPointHandles(
context,
appState,
renderConfig,
editingLinearElement,
);
}
// Paint selection element
if (appState.selectionElement) {
try {
+13 -3
View File
@@ -17,60 +17,70 @@ export const SHAPES = [
icon: SelectionIcon,
value: "selection",
key: KEYS.V,
numericKey: KEYS["1"],
fillable: true,
},
{
icon: RectangleIcon,
value: "rectangle",
key: KEYS.R,
numericKey: KEYS["2"],
fillable: true,
},
{
icon: DiamondIcon,
value: "diamond",
key: KEYS.D,
numericKey: KEYS["3"],
fillable: true,
},
{
icon: EllipseIcon,
value: "ellipse",
key: KEYS.O,
numericKey: KEYS["4"],
fillable: true,
},
{
icon: ArrowIcon,
value: "arrow",
key: KEYS.A,
numericKey: KEYS["5"],
fillable: true,
},
{
icon: LineIcon,
value: "line",
key: [KEYS.P, KEYS.L],
key: KEYS.L,
numericKey: KEYS["6"],
fillable: true,
},
{
icon: FreedrawIcon,
value: "freedraw",
key: [KEYS.X, KEYS.P.toUpperCase()],
key: [KEYS.P, KEYS.X],
numericKey: KEYS["7"],
fillable: false,
},
{
icon: TextIcon,
value: "text",
key: KEYS.T,
numericKey: KEYS["8"],
fillable: false,
},
{
icon: ImageIcon,
value: "image",
key: null,
numericKey: KEYS["9"],
fillable: false,
},
{
icon: EraserIcon,
value: "eraser",
key: KEYS.E,
numericKey: KEYS["0"],
fillable: false,
},
] as const;
@@ -78,7 +88,7 @@ export const SHAPES = [
export const findShapeByKey = (key: string) => {
const shape = SHAPES.find((shape, index) => {
return (
key === (shape.value === "eraser" ? 0 : index + 1).toString() ||
(shape.numericKey != null && key === shape.numericKey.toString()) ||
(shape.key &&
(typeof shape.key === "string"
? shape.key === key
@@ -12410,6 +12410,234 @@ exports[`regression tests key o selects ellipse tool: [end of test] number of el
exports[`regression tests key o selects ellipse tool: [end of test] number of renders 1`] = `9`;
exports[`regression tests key p selects freedraw tool: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
"lastActiveToolBeforeEraser": null,
"locked": false,
"type": "freedraw",
},
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
"currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
"currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
"cursorButton": "up",
"draggingElement": null,
"editingElement": null,
"editingGroupId": null,
"editingLinearElement": null,
"errorMessage": null,
"exportBackground": true,
"exportEmbedScene": false,
"exportScale": 1,
"exportWithDarkMode": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLoading": false,
"isResizing": false,
"isRotating": false,
"isSidebarDocked": false,
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"offsetLeft": 0,
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
"openPopup": null,
"openSidebar": null,
"pasteDialog": Object {
"data": null,
"shown": false,
},
"penDetected": false,
"penMode": false,
"pendingImageElementId": null,
"previousSelectedElementIds": Object {},
"resizingElement": null,
"scrollX": 0,
"scrollY": 0,
"scrolledOutside": false,
"selectedElementIds": Object {
"id0": false,
},
"selectedGroupIds": Object {},
"selectedLinearElement": null,
"selectionElement": null,
"shouldCacheIgnoreZoom": false,
"showHyperlinkPopup": false,
"showStats": false,
"showWelcomeScreen": true,
"startBoundElement": null,
"suggestedBindings": Array [],
"theme": "light",
"toast": null,
"viewBackgroundColor": "#ffffff",
"viewModeEnabled": false,
"width": 1024,
"zenModeEnabled": false,
"zoom": Object {
"value": 1,
},
}
`;
exports[`regression tests key p selects freedraw tool: [end of test] element 0 1`] = `
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"lastCommittedPoint": Array [
10,
10,
],
"link": null,
"locked": false,
"opacity": 100,
"points": Array [
Array [
0,
0,
],
Array [
10,
10,
],
Array [
10,
10,
],
],
"pressures": Array [
0,
0,
0,
],
"roughness": 1,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
"updated": 1,
"version": 4,
"versionNonce": 453191,
"width": 10,
"x": 10,
"y": 10,
}
`;
exports[`regression tests key p selects freedraw tool: [end of test] history 1`] = `
Object {
"recording": false,
"redoStack": Array [],
"stateHistory": Array [
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {},
"selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": false,
},
"selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"lastCommittedPoint": Array [
10,
10,
],
"link": null,
"locked": false,
"opacity": 100,
"points": Array [
Array [
0,
0,
],
Array [
10,
10,
],
Array [
10,
10,
],
],
"pressures": Array [
0,
0,
0,
],
"roughness": 1,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
"updated": 1,
"version": 4,
"versionNonce": 453191,
"width": 10,
"x": 10,
"y": 10,
},
],
},
],
}
`;
exports[`regression tests key p selects freedraw tool: [end of test] number of elements 1`] = `1`;
exports[`regression tests key p selects freedraw tool: [end of test] number of renders 1`] = `9`;
exports[`regression tests key r selects rectangle tool: [end of test] appState 1`] = `
Object {
"activeTool": Object {
@@ -12590,234 +12818,6 @@ exports[`regression tests key r selects rectangle tool: [end of test] number of
exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `9`;
exports[`regression tests key x selects freedraw tool: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
"lastActiveToolBeforeEraser": null,
"locked": false,
"type": "freedraw",
},
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
"currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
"currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
"cursorButton": "up",
"draggingElement": null,
"editingElement": null,
"editingGroupId": null,
"editingLinearElement": null,
"errorMessage": null,
"exportBackground": true,
"exportEmbedScene": false,
"exportScale": 1,
"exportWithDarkMode": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLoading": false,
"isResizing": false,
"isRotating": false,
"isSidebarDocked": false,
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"offsetLeft": 0,
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
"openPopup": null,
"openSidebar": null,
"pasteDialog": Object {
"data": null,
"shown": false,
},
"penDetected": false,
"penMode": false,
"pendingImageElementId": null,
"previousSelectedElementIds": Object {},
"resizingElement": null,
"scrollX": 0,
"scrollY": 0,
"scrolledOutside": false,
"selectedElementIds": Object {
"id0": false,
},
"selectedGroupIds": Object {},
"selectedLinearElement": null,
"selectionElement": null,
"shouldCacheIgnoreZoom": false,
"showHyperlinkPopup": false,
"showStats": false,
"showWelcomeScreen": true,
"startBoundElement": null,
"suggestedBindings": Array [],
"theme": "light",
"toast": null,
"viewBackgroundColor": "#ffffff",
"viewModeEnabled": false,
"width": 1024,
"zenModeEnabled": false,
"zoom": Object {
"value": 1,
},
}
`;
exports[`regression tests key x selects freedraw tool: [end of test] element 0 1`] = `
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"lastCommittedPoint": Array [
10,
10,
],
"link": null,
"locked": false,
"opacity": 100,
"points": Array [
Array [
0,
0,
],
Array [
10,
10,
],
Array [
10,
10,
],
],
"pressures": Array [
0,
0,
0,
],
"roughness": 1,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
"updated": 1,
"version": 4,
"versionNonce": 453191,
"width": 10,
"x": 10,
"y": 10,
}
`;
exports[`regression tests key x selects freedraw tool: [end of test] history 1`] = `
Object {
"recording": false,
"redoStack": Array [],
"stateHistory": Array [
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {},
"selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": false,
},
"selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"lastCommittedPoint": Array [
10,
10,
],
"link": null,
"locked": false,
"opacity": 100,
"points": Array [
Array [
0,
0,
],
Array [
10,
10,
],
Array [
10,
10,
],
],
"pressures": Array [
0,
0,
0,
],
"roughness": 1,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
"updated": 1,
"version": 4,
"versionNonce": 453191,
"width": 10,
"x": 10,
"y": 10,
},
],
},
],
}
`;
exports[`regression tests key x selects freedraw tool: [end of test] number of elements 1`] = `1`;
exports[`regression tests key x selects freedraw tool: [end of test] number of renders 1`] = `9`;
exports[`regression tests make a group and duplicate it: [end of test] appState 1`] = `
Object {
"activeTool": Object {
+1
View File
@@ -158,6 +158,7 @@ describe("export", () => {
dataURL: await getDataURL(await API.loadFile("./fixtures/deer.png")),
mimeType: "image/png",
created: Date.now(),
lastRetrieved: Date.now(),
},
} as const;
+48 -3
View File
@@ -16,7 +16,7 @@ const renderScene = jest.spyOn(Renderer, "renderScene");
const { h } = window;
describe(" Test Linear Elements", () => {
describe("Test Linear Elements", () => {
let container: HTMLElement;
let canvas: HTMLCanvasElement;
@@ -89,8 +89,15 @@ describe(" Test Linear Elements", () => {
mouse.clickAt(p1[0], p1[1]);
};
const enterLineEditingMode = (line: ExcalidrawLinearElement) => {
mouse.clickAt(p1[0], p1[1]);
const enterLineEditingMode = (
line: ExcalidrawLinearElement,
selectProgrammatically = false,
) => {
if (selectProgrammatically) {
API.setSelectedElements([line]);
} else {
mouse.clickAt(p1[0], p1[1]);
}
Keyboard.keyPress(KEYS.ENTER);
expect(h.state.editingLinearElement?.elementId).toEqual(line.id);
};
@@ -604,5 +611,43 @@ describe(" Test Linear Elements", () => {
`);
});
});
it("in-editor dragging a line point covered by another element", () => {
createTwoPointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
h.elements = [
line,
API.createElement({
type: "rectangle",
x: line.x - 50,
y: line.y - 50,
width: 100,
height: 100,
backgroundColor: "red",
fillStyle: "solid",
}),
];
const origPoints = line.points.map((point) => [...point]);
const dragEndPositionOffset = [100, 100] as const;
API.setSelectedElements([line]);
enterLineEditingMode(line, true);
drag(
[line.points[0][0] + line.x, line.points[0][1] + line.y],
[dragEndPositionOffset[0] + line.x, dragEndPositionOffset[1] + line.y],
);
expect(line.points).toMatchInlineSnapshot(`
Array [
Array [
0,
0,
],
Array [
${origPoints[1][0] - dragEndPositionOffset[0]},
${origPoints[1][1] - dragEndPositionOffset[1]},
],
]
`);
});
});
});
+1 -1
View File
@@ -138,7 +138,7 @@ describe("regression tests", () => {
[`4${KEYS.O}`, "ellipse", true],
[`5${KEYS.A}`, "arrow", true],
[`6${KEYS.L}`, "line", true],
[`7${KEYS.X}`, "freedraw", false],
[`7${KEYS.P}`, "freedraw", false],
] as [string, ExcalidrawElement["type"], boolean][]) {
for (const key of keys) {
it(`key ${key} selects ${shape} tool`, () => {
+11
View File
@@ -61,7 +61,18 @@ export type BinaryFileData = {
| typeof MIME_TYPES.binary;
id: FileId;
dataURL: DataURL;
/**
* Epoch timestamp in milliseconds
*/
created: number;
/**
* Indicates when the file was last retrieved from storage to be loaded
* onto the scene. We use this flag to determine whether to delete unused
* files from storage.
*
* Epoch timestamp in milliseconds.
*/
lastRetrieved?: number;
};
export type BinaryFileMetadata = Omit<BinaryFileData, "dataURL">;
+3 -3
View File
@@ -11263,9 +11263,9 @@ socket.io-client@2.3.1:
to-array "0.1.4"
socket.io-parser@~3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6"
integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==
version "3.3.3"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.3.tgz#3a8b84823eba87f3f7624e64a8aaab6d6318a72f"
integrity sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==
dependencies:
component-emitter "~1.3.0"
debug "~3.1.0"