mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge branch 'master' of github.com:excalidraw/excalidraw into arnost/export-image-background
This commit is contained in:
commit
5afa3fe31d
101 changed files with 2672 additions and 426 deletions
|
@ -60,6 +60,7 @@ const flipSelectedElements = (
|
|||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -95,7 +95,7 @@ import { register } from "./register";
|
|||
|
||||
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
||||
|
||||
const changeProperty = (
|
||||
export const changeProperty = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
callback: (element: ExcalidrawElement) => ExcalidrawElement,
|
||||
|
@ -118,7 +118,7 @@ const changeProperty = (
|
|||
});
|
||||
};
|
||||
|
||||
const getFormValue = function <T>(
|
||||
export const getFormValue = function <T>(
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
getAttribute: (element: ExcalidrawElement) => T,
|
||||
|
|
|
@ -85,6 +85,7 @@ import {
|
|||
VERTICAL_ALIGN,
|
||||
YOUTUBE_STATES,
|
||||
ZOOM_STEP,
|
||||
POINTER_EVENTS,
|
||||
} from "../constants";
|
||||
import { exportCanvas, loadFromBlob } from "../data";
|
||||
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||
|
@ -857,7 +858,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
width: isVisible ? `${el.width}px` : 0,
|
||||
height: isVisible ? `${el.height}px` : 0,
|
||||
transform: isVisible ? `rotate(${el.angle}rad)` : "none",
|
||||
pointerEvents: isActive ? "auto" : "none",
|
||||
pointerEvents: isActive
|
||||
? POINTER_EVENTS.enabled
|
||||
: POINTER_EVENTS.disabled,
|
||||
}}
|
||||
>
|
||||
{isHovered && (
|
||||
|
@ -1081,9 +1084,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
cursor: CURSOR_TYPE.MOVE,
|
||||
// disable all interaction (e.g. cursor change) when in view
|
||||
// mode
|
||||
pointerEvents: this.state.viewModeEnabled ? "none" : "all",
|
||||
pointerEvents: this.state.viewModeEnabled
|
||||
? POINTER_EVENTS.disabled
|
||||
: POINTER_EVENTS.inheritFromUI,
|
||||
}}
|
||||
onPointerDown={(event) => this.handleCanvasPointerDown(event)}
|
||||
onWheel={(event) => this.handleWheel(event)}
|
||||
|
@ -1125,6 +1128,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||
"excalidraw--view-mode": this.state.viewModeEnabled,
|
||||
"excalidraw--mobile": this.device.isMobile,
|
||||
})}
|
||||
style={{
|
||||
["--ui-pointerEvents" as any]:
|
||||
this.state.selectionElement ||
|
||||
this.state.draggingElement ||
|
||||
this.state.resizingElement ||
|
||||
(this.state.editingElement &&
|
||||
!isTextElement(this.state.editingElement))
|
||||
? POINTER_EVENTS.disabled
|
||||
: POINTER_EVENTS.enabled,
|
||||
}}
|
||||
ref={this.excalidrawContainerRef}
|
||||
onDrop={this.handleAppOnDrop}
|
||||
tabIndex={0}
|
||||
|
@ -3944,7 +3957,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [gridX, gridY] = getGridPoint(
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const [lastCommittedX, lastCommittedY] =
|
||||
|
@ -4761,7 +4774,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||
origin,
|
||||
withCmdOrCtrl: event[KEYS.CTRL_OR_CMD],
|
||||
originInGrid: tupleToCoors(
|
||||
getGridPoint(origin.x, origin.y, this.state.gridSize),
|
||||
getGridPoint(
|
||||
origin.x,
|
||||
origin.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
),
|
||||
),
|
||||
scrollbars: isOverScrollBars(
|
||||
currentScrollBars,
|
||||
|
@ -5285,7 +5302,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||
sceneY: number;
|
||||
link: string;
|
||||
}) => {
|
||||
const [gridX, gridY] = getGridPoint(sceneX, sceneY, this.state.gridSize);
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
sceneX,
|
||||
sceneY,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const embedLink = getEmbedLink(link);
|
||||
|
||||
|
@ -5331,7 +5352,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||
sceneX: number;
|
||||
sceneY: number;
|
||||
}) => {
|
||||
const [gridX, gridY] = getGridPoint(sceneX, sceneY, this.state.gridSize);
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
sceneX,
|
||||
sceneY,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
x: gridX,
|
||||
|
@ -5414,7 +5439,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [gridX, gridY] = getGridPoint(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
|
@ -5507,7 +5532,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [gridX, gridY] = getGridPoint(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.state.gridSize,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
|
@ -5565,7 +5590,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [gridX, gridY] = getGridPoint(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.state.gridSize,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const frame = newFrameElement({
|
||||
|
@ -5648,7 +5673,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [gridX, gridY] = getGridPoint(
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
// for arrows/lines, don't start dragging until a given threshold
|
||||
|
@ -5694,6 +5719,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.state.selectedLinearElement,
|
||||
pointerCoords,
|
||||
this.state,
|
||||
!event[KEYS.CTRL_OR_CMD],
|
||||
);
|
||||
if (!ret) {
|
||||
return;
|
||||
|
@ -5819,7 +5845,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [dragX, dragY] = getGridPoint(
|
||||
pointerCoords.x - pointerDownState.drag.offset.x,
|
||||
pointerCoords.y - pointerDownState.drag.offset.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const [dragDistanceX, dragDistanceY] = [
|
||||
|
@ -5886,7 +5912,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [originDragX, originDragY] = getGridPoint(
|
||||
pointerDownState.origin.x - pointerDownState.drag.offset.x,
|
||||
pointerDownState.origin.y - pointerDownState.drag.offset.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
mutateElement(duplicatedElement, {
|
||||
x: duplicatedElement.x + (originDragX - dragX),
|
||||
|
@ -6501,7 +6527,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
|
||||
nextElements = updateFrameMembershipOfSelectedElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
nextElements,
|
||||
this.state,
|
||||
this,
|
||||
);
|
||||
|
@ -7679,7 +7705,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [gridX, gridY] = getGridPoint(
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const image =
|
||||
|
@ -7748,7 +7774,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const [resizeX, resizeY] = getGridPoint(
|
||||
pointerCoords.x - pointerDownState.resize.offset.x,
|
||||
pointerCoords.y - pointerDownState.resize.offset.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const frameElementsOffsetsMap = new Map<
|
||||
|
|
|
@ -15,9 +15,15 @@ interface ColorInputProps {
|
|||
color: string;
|
||||
onChange: (color: string) => void;
|
||||
label: string;
|
||||
eyeDropperType: "strokeColor" | "backgroundColor";
|
||||
}
|
||||
|
||||
export const ColorInput = ({ color, onChange, label }: ColorInputProps) => {
|
||||
export const ColorInput = ({
|
||||
color,
|
||||
onChange,
|
||||
label,
|
||||
eyeDropperType,
|
||||
}: ColorInputProps) => {
|
||||
const device = useDevice();
|
||||
const [innerValue, setInnerValue] = useState(color);
|
||||
const [activeSection, setActiveColorPickerSection] = useAtom(
|
||||
|
@ -110,6 +116,7 @@ export const ColorInput = ({ color, onChange, label }: ColorInputProps) => {
|
|||
: {
|
||||
keepOpenOnAlt: false,
|
||||
onSelect: (color) => onChange(color),
|
||||
previewType: eyeDropperType,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -82,7 +82,14 @@ const ColorPickerPopupContent = ({
|
|||
const { container } = useExcalidrawContainer();
|
||||
const { isMobile, isLandscape } = useDevice();
|
||||
|
||||
const colorInputJSX = (
|
||||
const eyeDropperType =
|
||||
type === "canvasBackground"
|
||||
? undefined
|
||||
: type === "elementBackground"
|
||||
? "backgroundColor"
|
||||
: "strokeColor";
|
||||
|
||||
const colorInputJSX = eyeDropperType && (
|
||||
<div>
|
||||
<PickerHeading>{t("colorPicker.hexCode")}</PickerHeading>
|
||||
<ColorInput
|
||||
|
@ -91,6 +98,7 @@ const ColorPickerPopupContent = ({
|
|||
onChange={(color) => {
|
||||
onChange(color);
|
||||
}}
|
||||
eyeDropperType={eyeDropperType}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -140,7 +148,7 @@ const ColorPickerPopupContent = ({
|
|||
alignOffset={-16}
|
||||
sideOffset={20}
|
||||
style={{
|
||||
zIndex: 9999,
|
||||
zIndex: "var(--zIndex-layerUI)",
|
||||
backgroundColor: "var(--popup-bg-color)",
|
||||
maxWidth: "208px",
|
||||
maxHeight: window.innerHeight,
|
||||
|
@ -152,7 +160,7 @@ const ColorPickerPopupContent = ({
|
|||
"0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)",
|
||||
}}
|
||||
>
|
||||
{palette ? (
|
||||
{palette && eyeDropperType ? (
|
||||
<Picker
|
||||
palette={palette}
|
||||
color={color}
|
||||
|
@ -165,6 +173,7 @@ const ColorPickerPopupContent = ({
|
|||
state = state || {
|
||||
keepOpenOnAlt: true,
|
||||
onSelect: onChange,
|
||||
previewType: eyeDropperType,
|
||||
};
|
||||
state.keepOpenOnAlt = true;
|
||||
return state;
|
||||
|
@ -175,6 +184,7 @@ const ColorPickerPopupContent = ({
|
|||
: {
|
||||
keepOpenOnAlt: false,
|
||||
onSelect: onChange,
|
||||
previewType: eyeDropperType,
|
||||
};
|
||||
});
|
||||
}}
|
||||
|
|
73
src/components/ExcalidrawLogo.scss
Normal file
73
src/components/ExcalidrawLogo.scss
Normal file
|
@ -0,0 +1,73 @@
|
|||
.excalidraw {
|
||||
.ExcalidrawLogo {
|
||||
--logo-icon--xs: 2rem;
|
||||
--logo-text--xs: 1.5rem;
|
||||
|
||||
--logo-icon--small: 2.5rem;
|
||||
--logo-text--small: 1.75rem;
|
||||
|
||||
--logo-icon--normal: 3rem;
|
||||
--logo-text--normal: 2.2rem;
|
||||
|
||||
--logo-icon--large: 90px;
|
||||
--logo-text--large: 65px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ExcalidrawLogo-icon {
|
||||
width: auto;
|
||||
color: var(--color-logo-icon);
|
||||
}
|
||||
|
||||
.ExcalidrawLogo-text {
|
||||
margin-left: 0.75rem;
|
||||
width: auto;
|
||||
color: var(--color-logo-text);
|
||||
}
|
||||
|
||||
&.is-xs {
|
||||
.ExcalidrawLogo-icon {
|
||||
height: var(--logo-icon--xs);
|
||||
}
|
||||
|
||||
.ExcalidrawLogo-text {
|
||||
height: var(--logo-text--xs);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-small {
|
||||
.ExcalidrawLogo-icon {
|
||||
height: var(--logo-icon--small);
|
||||
}
|
||||
|
||||
.ExcalidrawLogo-text {
|
||||
height: var(--logo-text--small);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-normal {
|
||||
.ExcalidrawLogo-icon {
|
||||
height: var(--logo-icon--normal);
|
||||
}
|
||||
|
||||
.ExcalidrawLogo-text {
|
||||
height: var(--logo-text--normal);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-large {
|
||||
.ExcalidrawLogo-icon {
|
||||
height: var(--logo-icon--large);
|
||||
}
|
||||
|
||||
.ExcalidrawLogo-text {
|
||||
height: var(--logo-text--large);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
src/components/ExcalidrawLogo.tsx
Normal file
69
src/components/ExcalidrawLogo.tsx
Normal file
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,7 @@
|
|||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
z-index: var(--zIndex-eyeDropperBackdrop);
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
|||
width: 3rem;
|
||||
height: 3rem;
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
z-index: var(--zIndex-eyeDropperPreview);
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--default-border-color);
|
||||
filter: var(--theme-filter);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { atom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { COLOR_PALETTE, rgbToHex } from "../colors";
|
||||
import { rgbToHex } from "../colors";
|
||||
import { EVENT } from "../constants";
|
||||
import { useUIAppState } from "../context/ui-appState";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
|
@ -18,8 +18,8 @@ import "./EyeDropper.scss";
|
|||
type EyeDropperProperties = {
|
||||
keepOpenOnAlt: boolean;
|
||||
swapPreviewOnAlt?: boolean;
|
||||
onSelect?: (color: string, event: PointerEvent) => void;
|
||||
previewType?: "strokeColor" | "backgroundColor";
|
||||
onSelect: (color: string, event: PointerEvent) => void;
|
||||
previewType: "strokeColor" | "backgroundColor";
|
||||
};
|
||||
|
||||
export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null);
|
||||
|
@ -28,13 +28,8 @@ export const EyeDropper: React.FC<{
|
|||
onCancel: () => void;
|
||||
onSelect: Required<EyeDropperProperties>["onSelect"];
|
||||
swapPreviewOnAlt?: EyeDropperProperties["swapPreviewOnAlt"];
|
||||
previewType?: EyeDropperProperties["previewType"];
|
||||
}> = ({
|
||||
onCancel,
|
||||
onSelect,
|
||||
swapPreviewOnAlt,
|
||||
previewType = "backgroundColor",
|
||||
}) => {
|
||||
previewType: EyeDropperProperties["previewType"];
|
||||
}> = ({ onCancel, onSelect, swapPreviewOnAlt, previewType }) => {
|
||||
const eyeDropperContainer = useCreatePortalContainer({
|
||||
className: "excalidraw-eye-dropper-backdrop",
|
||||
parentSelector: ".excalidraw-eye-dropper-container",
|
||||
|
@ -58,11 +53,27 @@ export const EyeDropper: React.FC<{
|
|||
return;
|
||||
}
|
||||
|
||||
let currentColor: string = COLOR_PALETTE.black;
|
||||
let isHoldingPointerDown = false;
|
||||
|
||||
const ctx = app.canvas.getContext("2d")!;
|
||||
|
||||
const getCurrentColor = ({
|
||||
clientX,
|
||||
clientY,
|
||||
}: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}) => {
|
||||
const pixel = ctx.getImageData(
|
||||
(clientX - appState.offsetLeft) * window.devicePixelRatio,
|
||||
(clientY - appState.offsetTop) * window.devicePixelRatio,
|
||||
1,
|
||||
1,
|
||||
).data;
|
||||
|
||||
return rgbToHex(pixel[0], pixel[1], pixel[2]);
|
||||
};
|
||||
|
||||
const mouseMoveListener = ({
|
||||
clientX,
|
||||
clientY,
|
||||
|
@ -76,14 +87,7 @@ export const EyeDropper: React.FC<{
|
|||
colorPreviewDiv.style.top = `${clientY + 20}px`;
|
||||
colorPreviewDiv.style.left = `${clientX + 20}px`;
|
||||
|
||||
const pixel = ctx.getImageData(
|
||||
(clientX - appState.offsetLeft) * window.devicePixelRatio,
|
||||
(clientY - appState.offsetTop) * window.devicePixelRatio,
|
||||
1,
|
||||
1,
|
||||
).data;
|
||||
|
||||
currentColor = rgbToHex(pixel[0], pixel[1], pixel[2]);
|
||||
const currentColor = getCurrentColor({ clientX, clientY });
|
||||
|
||||
if (isHoldingPointerDown) {
|
||||
for (const element of metaStuffRef.current.selectedElements) {
|
||||
|
@ -125,7 +129,7 @@ export const EyeDropper: React.FC<{
|
|||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
|
||||
onSelect(currentColor, event);
|
||||
onSelect(getCurrentColor(event), event);
|
||||
};
|
||||
|
||||
const keyDownListener = (event: KeyboardEvent) => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
|
||||
.FixedSideContainer > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
.FixedSideContainer_side_top {
|
||||
|
|
|
@ -83,27 +83,36 @@ const getHints = ({ appState, isMobile, device, app }: HintViewerProps) => {
|
|||
if (activeTool.type === "selection") {
|
||||
if (
|
||||
appState.draggingElement?.type === "selection" &&
|
||||
!selectedElements.length &&
|
||||
!appState.editingElement &&
|
||||
!appState.editingLinearElement
|
||||
) {
|
||||
return t("hints.deepBoxSelect");
|
||||
}
|
||||
|
||||
if (appState.gridSize && appState.draggingElement) {
|
||||
return t("hints.disableSnapping");
|
||||
}
|
||||
|
||||
if (!selectedElements.length && !isMobile) {
|
||||
return t("hints.canvasPanning");
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedElements.length === 1) {
|
||||
if (isLinearElement(selectedElements[0])) {
|
||||
if (appState.editingLinearElement) {
|
||||
return appState.editingLinearElement.selectedPointsIndices
|
||||
? t("hints.lineEditor_pointSelected")
|
||||
: t("hints.lineEditor_nothingSelected");
|
||||
if (selectedElements.length === 1) {
|
||||
if (isLinearElement(selectedElements[0])) {
|
||||
if (appState.editingLinearElement) {
|
||||
return appState.editingLinearElement.selectedPointsIndices
|
||||
? t("hints.lineEditor_pointSelected")
|
||||
: t("hints.lineEditor_nothingSelected");
|
||||
}
|
||||
return t("hints.lineEditor_info");
|
||||
}
|
||||
if (
|
||||
!appState.draggingElement &&
|
||||
isTextBindableContainer(selectedElements[0])
|
||||
) {
|
||||
return t("hints.bindTextToElement");
|
||||
}
|
||||
return t("hints.lineEditor_info");
|
||||
}
|
||||
if (isTextBindableContainer(selectedElements[0])) {
|
||||
return t("hints.bindTextToElement");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,34 +56,52 @@
|
|||
}
|
||||
|
||||
.disable-zen-mode {
|
||||
height: 30px;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
bottom: 0;
|
||||
[dir="ltr"] & {
|
||||
right: 15px;
|
||||
right: 1rem;
|
||||
}
|
||||
[dir="rtl"] & {
|
||||
left: 15px;
|
||||
left: 1rem;
|
||||
}
|
||||
font-size: 10px;
|
||||
padding: 10px;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: visibility 0s linear 0s, opacity 0.5s;
|
||||
|
||||
font-family: var(--ui-font);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
|
||||
border-radius: var(--border-radius-lg);
|
||||
border: 1px solid var(--default-border-color);
|
||||
background-color: var(--island-bg-color);
|
||||
color: var(--text-primary-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
}
|
||||
&:active {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
&--visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: visibility 0s linear 300ms, opacity 0.5s;
|
||||
transition-delay: 0.8s;
|
||||
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-ui__wrapper__footer-left,
|
||||
.layer-ui__wrapper__footer-right,
|
||||
.disable-zen-mode--visible {
|
||||
pointer-events: all;
|
||||
.footer-center,
|
||||
.layer-ui__wrapper__footer-right {
|
||||
& > * {
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-ui__wrapper__footer-right {
|
||||
|
|
|
@ -2,7 +2,7 @@ import clsx from "clsx";
|
|||
import React from "react";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants";
|
||||
import { isTextElement, showSelectedShapeActions } from "../element";
|
||||
import { showSelectedShapeActions } from "../element";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { Language, t } from "../i18n";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
|
@ -427,13 +427,7 @@ const LayerUI = ({
|
|||
{!device.isMobile && (
|
||||
<>
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper", {
|
||||
"disable-pointerEvents":
|
||||
appState.draggingElement ||
|
||||
appState.resizingElement ||
|
||||
(appState.editingElement &&
|
||||
!isTextElement(appState.editingElement)),
|
||||
})}
|
||||
className="layer-ui__wrapper"
|
||||
style={
|
||||
appState.openSidebar &&
|
||||
isSidebarDocked &&
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
background-color: var(--sidebar-bg-color);
|
||||
box-shadow: var(--sidebar-shadow);
|
||||
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
left: 0;
|
||||
right: auto;
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
}
|
||||
|
||||
.default-sidebar-trigger .sidebar-trigger__label {
|
||||
display: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
display: block;
|
||||
}
|
||||
&.excalidraw--mobile .default-sidebar-trigger .sidebar-trigger__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
right: 12px;
|
||||
font-size: 12px;
|
||||
z-index: 10;
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
h3 {
|
||||
margin: 0 24px 8px 0;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
|
||||
.UserList > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
.UserList_mobile {
|
||||
|
|
|
@ -37,10 +37,25 @@ const StaticCanvas = (props: StaticCanvasProps) => {
|
|||
canvas.classList.add("excalidraw__canvas", "static");
|
||||
}
|
||||
|
||||
canvas.style.width = `${props.appState.width}px`;
|
||||
canvas.style.height = `${props.appState.height}px`;
|
||||
canvas.width = props.appState.width * props.scale;
|
||||
canvas.height = props.appState.height * props.scale;
|
||||
const widthString = `${props.appState.width}px`;
|
||||
const heightString = `${props.appState.height}px`;
|
||||
if (canvas.style.width !== widthString) {
|
||||
canvas.style.width = widthString;
|
||||
}
|
||||
if (canvas.style.height !== heightString) {
|
||||
canvas.style.height = heightString;
|
||||
}
|
||||
|
||||
const scaledWidth = props.appState.width * props.scale;
|
||||
const scaledHeight = props.appState.height * props.scale;
|
||||
// setting width/height resets the canvas even if dimensions not changed,
|
||||
// which would cause flicker when we skip frame (due to throttling)
|
||||
if (canvas.width !== scaledWidth) {
|
||||
canvas.width = scaledWidth;
|
||||
}
|
||||
if (canvas.height !== scaledHeight) {
|
||||
canvas.height = scaledHeight;
|
||||
}
|
||||
|
||||
renderStaticScene(
|
||||
{
|
||||
|
|
|
@ -73,7 +73,7 @@ const Footer = ({
|
|||
<FooterCenterTunnel.Out />
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
|
||||
"transition-right disable-pointerEvents": appState.zenModeEnabled,
|
||||
"transition-right": appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
<div style={{ position: "relative" }}>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
.footer-center {
|
||||
pointer-events: none;
|
||||
& > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
margin-inline-end: 0.6rem;
|
||||
}
|
||||
|
|
|
@ -255,14 +255,12 @@ export const WelcomeScreenTopToolbarArrow = createIcon(
|
|||
// custom
|
||||
export const ExcalLogo = createIcon(
|
||||
<g fill="currentColor">
|
||||
<path d="M24.296 12.214c0 .112-.134.224-.291.224-.135 0-.516.629-.808 1.392-.897 2.335-9.867 20.096-9.89 19.534 0-.292-.134-.494-.359-.494-.313 0-.358.18-.224 1.055.135 1.01.045 1.236-3.14 7.432-1.793 3.525-3.722 7.208-4.282 8.196-.584 1.032-1.032 2.155-1.077 2.626-.067.809.022.92 1.973 2.605 1.122.988 2.557 2.223 3.185 2.784 2.826 2.582 4.149 3.615 4.508 3.547.538-.09 8.858-8.823 8.88-9.317 0-.225-.403-3.638-.897-7.59-.852-6.735-1.66-14.616-1.57-15.38.068-.47-.269-2.85-.516-3.884-.201-.808-.112-1.145 1.503-4.827.942-2.178 2.176-4.85 2.714-5.928.515-1.077.964-2.02.964-2.088 0-.067-.157-.112-.336-.112-.18 0-.337.09-.337.225Zm-5.158 16.772c.247 1.572.74 5.344 1.099 8.375.695 5.568 1.503 11.742 1.727 13.314.135.786.045.943-1.413 2.56-2.534 2.851-5.225 5.658-6.145 6.376l-.852.674-4.373-4.086c-4.037-3.728-4.373-4.11-4.127-4.558a5154.2 5154.2 0 0 1 2.535-4.626 727.864 727.864 0 0 0 3.678-6.78c.784-1.46 1.502-2.717 1.637-2.785.156-.09.201 2.178.156 7.006-.09 7.207-.067 7.23.651 7.072.09 0 .157-3.637.157-8.06V35.43l2.355-4.715c1.3-2.605 2.377-4.693 2.422-4.67.045.022.27 1.347.493 2.94ZM9.562 1.818C7.903 3.143 5.346 5.388 3.328 7.32L1.735 8.823l.292 1.976c.157 1.078.449 3.188.628 4.67.202 1.482.404 2.874.47 3.077.09.269 0 .404-.246.404-.426 0-.449-.113.718 3.592.286.952.577 1.903.875 2.851.044.158.224.225.425.158.202-.09.314-.27.247-.427-.067-.18.045-.36.224-.427.247-.09.225-.269-.157-.92-.605-1.01-2.152-9.633-2.242-12.416-.067-1.976-.067-1.999.762-3.121.808-1.1 2.67-2.762 5.54-4.873.807-.605 1.614-1.28 1.839-1.504.336-.404.493-.292 3.319 2.717 1.637 1.729 3.453 3.502 4.037 3.952l1.076.808-.83 1.75c-.448.944-2.265 4.581-4.059 8.04-3.745 7.274-2.983 6.578-7.333 6.645l-2.826.023-.942 1.077c-.987 1.146-1.121 1.572-.65 2.29.18.248.313.652.313.898 0 .405.157.472 1.055.517.56.023 1.076.09 1.144.157.067.068.156 1.46.224 3.098l.09 2.965-1.503 3.232C1.735 45.422.749 47.891.749 48.7c0 .427.09.786.18.786.224 0 .224-.022 9.35-19.085a4398.495 4398.495 0 0 1 8.927-18.546c.672-1.369 1.278-2.626 1.323-2.806.045-.202-1.503-1.751-3.97-3.93-2.22-1.975-4.171-3.772-4.35-3.974-.516-.628-1.279-.426-2.647.674ZM8.441 31.231c-.18.472-.65 1.46-1.031 2.2-.629 1.258-.696 1.303-.853.786-.09-.314-.157-1.235-.18-2.066-.022-1.639-.067-1.616 1.817-1.728L8.8 30.4l-.358.831Zm1.884-3.592c-1.032 1.998-1.077 2.02-3.903 2.155-2.489.135-2.533.112-2.533-.36 0-.269-.09-.628-.203-.808-.134-.202-.044-.56.27-1.055l.493-.763H6.69c1.234-.023 2.647-.113 3.14-.202.494-.09.92-.135.965-.113.045.023-.18.54-.471 1.146Zm-.09-20.477c-.404.292-.516.584-.516 1.325 0 .875.067 1.01.673 1.257.605.247.763.224 1.458-.247.92-.629.941-.786.269-1.796-.583-.876-1.166-1.033-1.884-.54Z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M23.703 11.793c.166-.291.501-.514.93-.514.38 0 .698.161.82.283.161.162.225.35.225.54a.822.822 0 0 1-.056.289c-.08.218-.5 1.106-.983 2.116-.535 1.071-1.76 3.727-2.699 5.895-.79 1.802-1.209 2.784-1.404 3.416-.142.461-.132.665-.058.961.264 1.103.6 3.647.53 4.132-.088.756.727 8.547 1.57 15.21.5 3.997.903 7.45.903 7.676l-.001.033c-.004.087-.041.288-.211.54-.24.354-.914 1.143-1.8 2.119-2.004 2.21-5.107 5.423-6.463 6.653-.322.292-.566.485-.696.56a.884.884 0 0 1-.289.111c-.194.037-.579-.007-1.11-.349-.707-.453-1.981-1.522-4-3.366-.627-.561-2.061-1.794-3.176-2.776-.81-.699-1.308-1.138-1.612-1.466-.32-.343-.47-.61-.549-.87-.078-.257-.085-.515-.055-.874.05-.52.521-1.769 1.166-2.91.559-.985 2.48-4.654 4.269-8.17 1.579-3.071 2.392-4.663 2.792-5.612.32-.759.329-1 .277-1.387-.085-.553-.092-.891-.052-1.092a.942.942 0 0 1 .274-.52c.164-.157.384-.261.704-.261.094 0 .184.011.27.033 1.924-3.44 8.554-16.632 9.316-18.616.276-.724.64-1.336.848-1.556a.965.965 0 0 1 .32-.228Zm-5.399 16.402c-.49.942-.971 1.888-1.446 2.837l-2.28 4.565v7.871c0 4.023-.06 7.404-.136 8.04-.067.552-.474.691-.654.722l.075-.008c-.317.07-.574.063-.778-.023-.234-.098-.5-.297-.63-.857-.156-.681-.158-2.462-.103-6.893.019-2.022.022-3.592.008-4.725-.156.276-.315.562-.467.843a737.624 737.624 0 0 1-3.682 6.79 3618.972 3618.972 0 0 0-2.462 4.493c.062.088.169.231.289.364.55.61 1.631 1.623 3.624 3.462l3.931 3.674.377-.298c.907-.709 3.554-3.479 6.055-6.293.425-.47.73-.814.946-1.084.175-.22.28-.36.319-.501.031-.117.002-.227-.024-.379l-.004-.02c-.224-1.572-1.032-7.753-1.728-13.33-.358-3.022-.85-6.782-1.096-8.349l-.002-.01c-.042-.301-.087-.603-.132-.891ZM9.118 1.264C9.91.628 10.537.27 11.028.144c.727-.186 1.27.003 1.713.53.186.209 2.107 1.972 4.287 3.912 2.02 1.783 3.434 3.16 3.897 3.743.326.41.322.756.296.873a1.046 1.046 0 0 1-.005.018c-.047.188-.669 1.512-1.374 2.947a4348.55 4348.55 0 0 0-8.923 18.54c-7.335 15.32-8.808 18.396-9.217 19.015-.235.355-.419.404-.525.437a.815.815 0 0 1-.249.036.745.745 0 0 1-.647-.363C.176 49.67.04 49.222.04 48.7c0-.286.09-.754.316-1.434.452-1.356 1.466-3.722 3.225-7.53l1.432-3.083-.084-2.787a72.902 72.902 0 0 0-.156-2.53 7.307 7.307 0 0 0-.539-.046c-.463-.024-.764-.062-.96-.124-.304-.096-.48-.252-.598-.438-.105-.165-.17-.374-.17-.663 0-.134-.081-.348-.178-.481l-.019-.028c-.293-.448-.406-.831-.373-1.234.04-.484.34-1.052 1.08-1.91l.759-.869c-.103-.325-.471-1.513-.854-2.787-.737-2.339-1.004-3.238-1.018-3.578-.016-.393.134-.59.27-.715a.721.721 0 0 1 .192-.125 89.87 89.87 0 0 1-.414-2.782 231.651 231.651 0 0 0-.625-4.652l-.292-1.976a.71.71 0 0 1 .215-.62l1.589-1.501C4.87 4.86 7.446 2.599 9.118 1.264Zm-1.833 33.75a.819.819 0 0 1-.406.208.73.73 0 0 1-.491-.063l.048 1.618v.009l.849-1.773Zm5.874.697c-.035.087-.07.175-.107.261a20.92 20.92 0 0 1-.36.798.688.688 0 0 1 .457.007l.01.004v-1.07Zm.72-1.892-.015.018a.745.745 0 0 1-.407.236c.02.195.027.378 0 .592l.422-.846ZM7.7 31.175c-.268.027-.489.055-.6.07-.006.056-.013.13-.016.194-.005.19 0 .42.004.694.003.111.006.225.011.338.232-.471.459-.956.6-1.296Zm2.12-1.456a2.04 2.04 0 0 1-.415.31c.064.104.099.222.104.341l.132-.277.18-.374Zm-.14-2.374c-.654.079-1.882.153-2.974.173h-1.87l-.281.435c-.09.141-.17.331-.203.414.102.21.189.508.226.788h.007c.364.006.928-.023 1.805-.07 1.243-.06 1.88-.052 2.315-.291.154-.086.266-.215.387-.393.176-.261.354-.605.587-1.056Zm2.136-1.784c-.157.16-.331.3-.52.422a.631.631 0 0 1 .182.281l.337-.703Zm7.205-1.478c-.222.442-.445.883-.667 1.32a.787.787 0 0 1 .61.007c.036.018.145.07.243.2-.032-.165-.067-.33-.105-.493-.088-.351-.137-.633-.08-1.034h-.001ZM11.415 2.546c-.358.319-1.039.879-1.725 1.394C6.903 5.989 5.087 7.59 4.301 8.662c-.28.38-.458.605-.556.852-.15.38-.103.798-.068 1.824.063 1.923.833 6.669 1.493 9.686.262 1.199.483 2.11.654 2.394.25.426.364.71.398.894a.923.923 0 0 1-.184.764l1.27-.01c.863-.014 1.523.003 2.056-.019.424-.017.75-.052 1.034-.187.336-.159.596-.458.921-.955.62-.948 1.373-2.515 2.705-5.103 1.789-3.448 3.6-7.076 4.047-8.015l.582-1.227-.62-.466c-.595-.458-2.45-2.263-4.12-4.027a59.654 59.654 0 0 0-2.498-2.52ZM5.81 24.876v-.001l-.013-.03.013.031Zm-.71-.835.027-.011a.55.55 0 0 0-.028.011Zm19.904-11.777v.01-.01Zm.002-.016v-.034.034ZM9.82 6.587c-.587.424-.81.823-.81 1.9 0 .787.12 1.157.344 1.42.158.186.388.339.77.494.352.144.603.207.838.209.347.002.688-.12 1.285-.525.707-.483.98-.864 1.036-1.238.052-.352-.09-.812-.574-1.54-.412-.619-.853-.95-1.29-1.072-.489-.139-1.016-.05-1.586.342l-.013.01Zm2.015 2.028a6.288 6.288 0 0 0-.306-.52c-.19-.284-.326-.488-.531-.5-.113-.007-.224.058-.352.146-.218.159-.218.34-.218.745 0 .198.02.419.028.504.047.025.133.068.204.097.133.054.222.102.312.103.04 0 .071-.027.12-.054a4.29 4.29 0 0 0 .358-.225c.147-.1.299-.223.385-.296ZM9.12 1.263l-.002.002.002-.002Z"
|
||||
d="M39.9 32.889a.326.326 0 0 0-.279-.056c-2.094-3.083-4.774-6-7.343-8.833l-.419-.472a.212.212 0 0 0-.056-.139.586.586 0 0 0-.167-.111l-.084-.083-.056-.056c-.084-.167-.28-.278-.475-.167-.782.39-1.507.973-2.206 1.528-.92.722-1.842 1.445-2.708 2.25a8.405 8.405 0 0 0-.977 1.028c-.14.194-.028.361.14.444-.615.611-1.23 1.223-1.843 1.861a.315.315 0 0 0-.084.223c0 .083.056.166.111.194l1.09.833v.028c1.535 1.528 4.244 3.611 7.12 5.861.418.334.865.667 1.284 1 .195.223.39.473.558.695.084.11.28.139.391.055.056.056.14.111.196.167a.398.398 0 0 0 .167.056.255.255 0 0 0 .224-.111.394.394 0 0 0 .055-.167c.029 0 .028.028.056.028a.318.318 0 0 0 .224-.084l5.082-5.528a.309.309 0 0 0 0-.444Zm-14.63-1.917a.485.485 0 0 0 .111.14c.586.5 1.2 1 1.843 1.555l-2.569-1.945-.251-.166c-.056-.028-.112-.084-.168-.111l-.195-.167.056-.056.055-.055.112-.111c.866-.861 2.346-2.306 3.1-3.028-.81.805-2.43 3.167-2.095 3.944Zm8.767 6.89-2.122-1.612a44.713 44.713 0 0 0-2.625-2.5c1.145.861 2.122 1.611 2.262 1.75 1.117.972 1.06.806 1.815 1.445l.921.666a1.06 1.06 0 0 1-.251.25Zm.558.416-.056-.028c.084-.055.168-.111.252-.194l-.196.222ZM1.089 5.75c.055.361.14.722.195 1.056.335 1.833.67 3.5 1.284 4.75l.252.944c.084.361.223.806.363.917 1.424 1.25 3.602 3.11 5.947 4.889a.295.295 0 0 0 .363 0s0 .027.028.027a.254.254 0 0 0 .196.084.318.318 0 0 0 .223-.084c2.988-3.305 5.221-6.027 6.813-8.305.112-.111.14-.278.14-.417.111-.111.195-.25.307-.333.111-.111.111-.306 0-.39l-.028-.027c0-.055-.028-.139-.084-.167-.698-.666-1.2-1.138-1.731-1.638-.922-.862-1.871-1.75-3.881-3.75l-.028-.028c-.028-.028-.056-.056-.112-.056-.558-.194-1.703-.389-3.127-.639C6.087 2.223 3.21 1.723.614.944c0 0-.168 0-.196.028l-.083.084c-.028.027-.056.055-.224.11h.056-.056c.028.167.028.278.084.473 0 .055.112.5.112.555l.782 3.556Zm15.496 3.278-.335-.334c.084.112.196.195.335.334Zm-3.546 4.666-.056.056c0-.028.028-.056.056-.056Zm-2.038-10c.168.167.866.834 1.033.973-.726-.334-2.54-1.167-3.379-1.445.838.167 1.983.334 2.346.472ZM1.424 2.306c.419.722.754 3.222 1.089 5.666-.196-.778-.335-1.555-.503-2.278-.251-1.277-.503-2.416-.838-3.416.056 0 .14 0 .252.028Zm-.168-.584c-.112 0-.223-.028-.307-.028 0-.027 0-.055-.028-.055.14 0 .223.028.335.083Zm-1.089.222c0-.027 0-.027 0 0ZM39.453 1.333c.028-.11-.558-.61-.363-.639.42-.027.42-.666 0-.666-.558.028-1.144.166-1.675.25-.977.194-1.982.389-2.96.61-2.205.473-4.383.973-6.561 1.557-.67.194-1.424.333-2.066.666-.224.111-.196.333-.084.472-.056.028-.084.028-.14.056-.195.028-.363.056-.558.083-.168.028-.252.167-.224.334 0 .027.028.083.028.11-1.173 1.556-2.485 3.195-3.909 4.945-1.396 1.611-2.876 3.306-4.356 5.056-4.719 5.5-10.052 11.75-15.943 17.25a.268.268 0 0 0 0 .389c.028.027.056.055.084.055-.084.084-.168.14-.252.222-.056.056-.084.111-.084.167a.605.605 0 0 0-.111.139c-.112.111-.112.305.028.389.111.11.307.11.39-.028.029-.028.029-.056.056-.056a.44.44 0 0 1 .615 0c.335.362.67.723.977 1.028l-.698-.583c-.112-.111-.307-.083-.39.028-.113.11-.085.305.027.389l7.427 6.194c.056.056.112.056.196.056s.14-.028.195-.084l.168-.166c.028.027.083.027.111.027.084 0 .14-.027.196-.083 10.052-10.055 18.15-17.639 27.42-24.417.083-.055.111-.166.111-.25.112 0 .196-.083.251-.194 1.704-5.194 2.039-9.806 2.15-12.083v-.028c0-.028.028-.056.028-.083.028-.056.028-.084.028-.084a1.626 1.626 0 0 0-.111-1.028ZM21.472 9.5c.446-.5.893-1.028 1.34-1.5-2.876 3.778-7.65 9.583-14.408 16.5 4.607-5.083 9.242-10.333 13.068-15ZM5.193 35.778h.084-.084Zm3.462 3.194c-.027-.028-.027-.028 0-.028v.028Zm4.16-3.583c.224-.25.448-.472.699-.722 0 0 0 .027.028.027-.252.223-.475.445-.726.695Zm1.146-1.111c.14-.14.279-.334.446-.5l.028-.028c1.648-1.694 3.351-3.389 5.082-5.111l.028-.028c.419-.333.921-.694 1.368-1.028a379.003 379.003 0 0 0-6.952 6.695ZM24.794 6.472c-.921 1.195-1.954 2.778-2.82 4.028-2.736 3.944-11.532 13.583-11.727 13.75a1976.983 1976.983 0 0 1-8.042 7.639l-.167.167c-.14-.167-.14-.417.028-.556C14.49 19.861 22.03 10.167 25.074 5.917c-.084.194-.14.36-.28.555Zm4.83 5.695c-1.116-.64-1.646-1.64-1.34-2.611l.084-.334c.028-.083.084-.194.14-.277.307-.5.754-.917 1.257-1.167.027 0 .055 0 .083-.028-.028-.056-.028-.139-.028-.222.028-.167.14-.278.335-.278.335 0 1.369.306 1.76.639.111.083.223.194.335.305.14.167.363.445.474.667.056.028.112.306.196.445.056.222.111.472.084.694-.028.028 0 .194-.028.194a2.668 2.668 0 0 1-.363 1.028c-.028.028-.028.056-.056.084l-.028.027c-.14.223-.335.417-.53.556-.643.444-1.369.583-2.095.389 0 0-.195-.084-.28-.111Zm8.154-.834a39.098 39.098 0 0 1-.893 3.167c0 .028-.028.083 0 .111-.056 0-.084.028-.14.056-2.206 1.61-4.356 3.305-6.506 5.028 1.843-1.64 3.686-3.306 5.613-4.945.558-.5.949-1.139 1.06-1.861l.28-1.667v-.055c.14-.334.67-.195.586.166Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>,
|
||||
{ width: 26, height: 62, fill: "none" },
|
||||
{ width: 40, height: 40, fill: "none" },
|
||||
);
|
||||
|
||||
// custom
|
||||
|
|
|
@ -3,8 +3,9 @@ import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
|||
import { t, useI18n } from "../../i18n";
|
||||
import { useDevice, useExcalidrawActionManager } from "../App";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
|
||||
import { HelpIcon, LoadIcon, usersIcon } from "../icons";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
import { ExcalidrawLogo } from "../ExcalidrawLogo";
|
||||
|
||||
const WelcomeScreenMenuItemContent = ({
|
||||
icon,
|
||||
|
@ -109,7 +110,7 @@ Center.displayName = "Center";
|
|||
const Logo = ({ children }: { children?: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="welcome-screen-center__logo virgil welcome-screen-decor">
|
||||
{children || <>{ExcalLogo} Excalidraw</>}
|
||||
{children || <ExcalidrawLogo withText />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
pointer-events: none;
|
||||
|
||||
color: var(--color-gray-40);
|
||||
|
||||
a {
|
||||
--color: var(--color-primary);
|
||||
color: var(--color);
|
||||
text-decoration: none;
|
||||
margin-bottom: -6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.theme--dark {
|
||||
|
@ -136,11 +143,6 @@
|
|||
align-items: center;
|
||||
column-gap: 0.75rem;
|
||||
font-size: 2.25rem;
|
||||
|
||||
svg {
|
||||
width: 1.625rem;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-screen-center__heading {
|
||||
|
@ -159,7 +161,7 @@
|
|||
.welcome-screen-menu-item {
|
||||
box-sizing: border-box;
|
||||
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
color: var(--color-gray-50);
|
||||
font-size: 0.875rem;
|
||||
|
@ -200,7 +202,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:not(:active) .welcome-screen-menu-item:hover {
|
||||
.welcome-screen-menu-item:hover {
|
||||
text-decoration: none;
|
||||
background: var(--color-gray-10);
|
||||
|
||||
|
@ -244,7 +246,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:not(:active) .welcome-screen-menu-item:hover {
|
||||
.welcome-screen-menu-item:hover {
|
||||
background: var(--color-gray-85);
|
||||
|
||||
.welcome-screen-menu-item__shortcut {
|
||||
|
|
|
@ -41,6 +41,14 @@ export const POINTER_BUTTON = {
|
|||
TOUCH: -1,
|
||||
} as const;
|
||||
|
||||
export const POINTER_EVENTS = {
|
||||
enabled: "all",
|
||||
disabled: "none",
|
||||
// asserted as any so it can be freely assigned to React Element
|
||||
// "pointerEnvets" CSS prop
|
||||
inheritFromUI: "var(--ui-pointerEvents)" as any,
|
||||
} as const;
|
||||
|
||||
export enum EVENT {
|
||||
COPY = "copy",
|
||||
PASTE = "paste",
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
--zIndex-interactiveCanvas: 2;
|
||||
--zIndex-wysiwyg: 3;
|
||||
--zIndex-layerUI: 4;
|
||||
--zIndex-eyeDropperBackdrop: 5;
|
||||
--zIndex-eyeDropperPreview: 6;
|
||||
|
||||
--zIndex-modal: 1000;
|
||||
--zIndex-popup: 1001;
|
||||
|
@ -251,7 +253,7 @@
|
|||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: initial;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
.panelColumn {
|
||||
padding: 8px 8px 0 8px;
|
||||
|
@ -299,7 +301,7 @@
|
|||
pointer-events: none !important;
|
||||
|
||||
& > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,16 +312,16 @@
|
|||
cursor: default;
|
||||
pointer-events: none !important;
|
||||
|
||||
& > * {
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_top > * {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.App-menu_top > *:first-child {
|
||||
justify-self: flex-start;
|
||||
}
|
||||
|
@ -423,17 +425,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.disable-zen-mode {
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--color-gray-20);
|
||||
border: 1px solid var(--color-gray-30);
|
||||
padding: 10px 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-gray-30);
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-back-to-content {
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--island-bg-color);
|
||||
|
@ -445,7 +436,7 @@
|
|||
left: 50%;
|
||||
bottom: 30px;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -126,6 +126,9 @@
|
|||
--color-success: #268029;
|
||||
--color-success-lighter: #cafccc;
|
||||
|
||||
--color-logo-icon: var(--color-primary);
|
||||
--color-logo-text: #190064;
|
||||
|
||||
--border-radius-md: 0.375rem;
|
||||
--border-radius-lg: 0.5rem;
|
||||
|
||||
|
@ -219,5 +222,7 @@
|
|||
--color-muted-background-darker: var(--color-gray-20);
|
||||
|
||||
--color-promo: #d297ff;
|
||||
|
||||
--color-logo-text: #e2dfff;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ import {
|
|||
} from "./binding";
|
||||
import { tupleToCoors } from "../utils";
|
||||
import { isBindingElement } from "./typeChecks";
|
||||
import { shouldRotateWithDiscreteAngle } from "../keys";
|
||||
import { KEYS, shouldRotateWithDiscreteAngle } from "../keys";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import { DRAGGING_THRESHOLD } from "../constants";
|
||||
import { Mutable } from "../utility-types";
|
||||
|
@ -221,7 +221,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
referencePoint,
|
||||
[scenePointerX, scenePointerY],
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
|
||||
LinearElementEditor.movePoints(element, [
|
||||
|
@ -238,7 +238,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
|
||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||
|
@ -254,7 +254,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
)
|
||||
: ([
|
||||
element.points[pointIndex][0] + deltaX,
|
||||
|
@ -647,7 +647,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
),
|
||||
],
|
||||
});
|
||||
|
@ -798,7 +798,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
lastCommittedPoint,
|
||||
[scenePointerX, scenePointerY],
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
|
||||
newPoint = [
|
||||
|
@ -810,7 +810,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
||||
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1176,6 +1176,7 @@ export class LinearElementEditor {
|
|||
linearElementEditor: LinearElementEditor,
|
||||
pointerCoords: PointerCoords,
|
||||
appState: AppState,
|
||||
snapToGrid: boolean,
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
|
@ -1196,7 +1197,7 @@ export class LinearElementEditor {
|
|||
element,
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
appState.gridSize,
|
||||
snapToGrid ? appState.gridSize : null,
|
||||
);
|
||||
const points = [
|
||||
...element.points.slice(0, segmentMidpoint.index!),
|
||||
|
|
|
@ -1509,4 +1509,30 @@ describe("textWysiwyg", () => {
|
|||
expect(text.text).toBe("Excalidraw");
|
||||
});
|
||||
});
|
||||
|
||||
it("should bump the version of labelled arrow when label updated", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
const arrow = UI.createElement("arrow", {
|
||||
width: 300,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
mouse.select(arrow);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
let editor = getTextEditor();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
|
||||
const { version } = arrow;
|
||||
|
||||
mouse.select(arrow);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
editor = getTextEditor();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello\nworld!");
|
||||
editor.blur();
|
||||
|
||||
expect(arrow.version).toEqual(version + 1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
ExcalidrawTextContainer,
|
||||
} from "./types";
|
||||
import { AppState } from "../types";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { bumpVersion, mutateElement } from "./mutateElement";
|
||||
import {
|
||||
getBoundTextElementId,
|
||||
getContainerElement,
|
||||
|
@ -541,6 +541,9 @@ export const textWysiwyg = ({
|
|||
id: element.id,
|
||||
}),
|
||||
});
|
||||
} else if (isArrowElement(container)) {
|
||||
// updating an arrow label may change bounds, prevent stale cache:
|
||||
bumpVersion(container);
|
||||
}
|
||||
} else {
|
||||
mutateElement(container, {
|
||||
|
|
|
@ -121,6 +121,7 @@ export const isBindableElement = (
|
|||
element.type === "ellipse" ||
|
||||
element.type === "image" ||
|
||||
element.type === "embeddable" ||
|
||||
element.type === "frame" ||
|
||||
(element.type === "text" && !element.containerId))
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { Footer } from "../../packages/excalidraw/index";
|
||||
import { EncryptedIcon } from "./EncryptedIcon";
|
||||
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
|
||||
export const AppFooter = React.memo(() => {
|
||||
return (
|
||||
|
@ -13,8 +14,11 @@ export const AppFooter = React.memo(() => {
|
|||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ExcalidrawPlusAppLink />
|
||||
<EncryptedIcon />
|
||||
{isExcalidrawPlusSignedUser ? (
|
||||
<ExcalidrawPlusAppLink />
|
||||
) : (
|
||||
<EncryptedIcon />
|
||||
)}
|
||||
</div>
|
||||
</Footer>
|
||||
);
|
||||
|
|
|
@ -26,7 +26,9 @@ export const AppMainMenu: React.FC<{
|
|||
<MainMenu.Separator />
|
||||
<MainMenu.ItemLink
|
||||
icon={PlusPromoIcon}
|
||||
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger"
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_LP
|
||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger`}
|
||||
className="ExcalidrawPlus"
|
||||
>
|
||||
Excalidraw+
|
||||
|
|
|
@ -3,6 +3,7 @@ import { PlusPromoIcon } from "../../components/icons";
|
|||
import { useI18n } from "../../i18n";
|
||||
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
import { POINTER_EVENTS } from "../../constants";
|
||||
|
||||
export const AppWelcomeScreen: React.FC<{
|
||||
setCollabDialogShown: (toggle: boolean) => any;
|
||||
|
@ -18,7 +19,7 @@ export const AppWelcomeScreen: React.FC<{
|
|||
if (bit === "Excalidraw+") {
|
||||
return (
|
||||
<a
|
||||
style={{ pointerEvents: "all" }}
|
||||
style={{ pointerEvents: POINTER_EVENTS.inheritFromUI }}
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_APP
|
||||
}?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}
|
||||
|
@ -56,7 +57,9 @@ export const AppWelcomeScreen: React.FC<{
|
|||
)}
|
||||
{!isExcalidrawPlusSignedUser && (
|
||||
<WelcomeScreen.Center.MenuItemLink
|
||||
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_LP
|
||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest`}
|
||||
shortcut={null}
|
||||
icon={PlusPromoIcon}
|
||||
>
|
||||
|
|
|
@ -7,7 +7,6 @@ import { FileId, NonDeletedExcalidrawElement } from "../../element/types";
|
|||
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { excalidrawPlusIcon } from "./icons";
|
||||
import { encryptData, generateEncryptionKey } from "../../data/encryption";
|
||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
||||
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
||||
|
@ -15,6 +14,7 @@ import { encodeFilesForUpload } from "../data/FileManager";
|
|||
import { MIME_TYPES } from "../../constants";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { getFrame } from "../../utils";
|
||||
import { ExcalidrawLogo } from "../../components/ExcalidrawLogo";
|
||||
|
||||
export const exportToExcalidrawPlus = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
|
@ -69,7 +69,9 @@ export const exportToExcalidrawPlus = async (
|
|||
}
|
||||
|
||||
window.open(
|
||||
`https://plus.excalidraw.com/import?excalidraw=${id},${encryptionKey}`,
|
||||
`${
|
||||
import.meta.env.VITE_APP_PLUS_APP
|
||||
}/import?excalidraw=${id},${encryptionKey}`,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -82,7 +84,15 @@ export const ExportToExcalidrawPlus: React.FC<{
|
|||
const { t } = useI18n();
|
||||
return (
|
||||
<Card color="primary">
|
||||
<div className="Card-icon">{excalidrawPlusIcon}</div>
|
||||
<div className="Card-icon">
|
||||
<ExcalidrawLogo
|
||||
style={{
|
||||
[`--color-logo-icon` as any]: "#fff",
|
||||
width: "2.8rem",
|
||||
height: "2.8rem",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<h2>Excalidraw+</h2>
|
||||
<div className="Card-details">
|
||||
{t("exportDialog.excalidrawplus_description")}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -77,13 +77,14 @@
|
|||
align-items: center;
|
||||
border: 1px solid var(--color-primary);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--space-factor);
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--island-bg-color);
|
||||
color: var(--color-primary) !important;
|
||||
text-decoration: none !important;
|
||||
|
||||
font-size: 0.75rem;
|
||||
box-sizing: border-box;
|
||||
height: var(--default-button-size);
|
||||
height: var(--lg-button-size);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary);
|
||||
|
|
440
src/frame.test.tsx
Normal file
440
src/frame.test.tsx
Normal file
|
@ -0,0 +1,440 @@
|
|||
import { ExcalidrawElement } from "./element/types";
|
||||
import {
|
||||
convertToExcalidrawElements,
|
||||
Excalidraw,
|
||||
} from "./packages/excalidraw/index";
|
||||
import { API } from "./tests/helpers/api";
|
||||
import { Keyboard, Pointer } from "./tests/helpers/ui";
|
||||
import { render } from "./tests/test-utils";
|
||||
|
||||
const { h } = window;
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
describe("adding elements to frames", () => {
|
||||
type ElementType = string;
|
||||
const assertOrder = (
|
||||
els: readonly { type: ElementType }[],
|
||||
order: ElementType[],
|
||||
) => {
|
||||
expect(els.map((el) => el.type)).toEqual(order);
|
||||
};
|
||||
|
||||
const reorderElements = <T extends { type: ElementType }>(
|
||||
els: readonly T[],
|
||||
order: ElementType[],
|
||||
) => {
|
||||
return order.reduce((acc: T[], el) => {
|
||||
acc.push(els.find((e) => e.type === el)!);
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
function resizeFrameOverElement(
|
||||
frame: ExcalidrawElement,
|
||||
element: ExcalidrawElement,
|
||||
) {
|
||||
mouse.clickAt(0, 0);
|
||||
mouse.downAt(frame.x + frame.width, frame.y + frame.height);
|
||||
mouse.moveTo(
|
||||
element.x + element.width + 50,
|
||||
element.y + element.height + 50,
|
||||
);
|
||||
mouse.up();
|
||||
}
|
||||
|
||||
function dragElementIntoFrame(
|
||||
frame: ExcalidrawElement,
|
||||
element: ExcalidrawElement,
|
||||
) {
|
||||
mouse.clickAt(element.x, element.y);
|
||||
mouse.downAt(element.x + element.width / 2, element.y + element.height / 2);
|
||||
mouse.moveTo(frame.x + frame.width / 2, frame.y + frame.height / 2);
|
||||
mouse.up();
|
||||
}
|
||||
|
||||
function selectElementAndDuplicate(
|
||||
element: ExcalidrawElement,
|
||||
moveTo: [number, number] = [element.x + 25, element.y + 25],
|
||||
) {
|
||||
const [x, y] = [
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
];
|
||||
|
||||
Keyboard.withModifierKeys({ alt: true }, () => {
|
||||
mouse.downAt(x, y);
|
||||
mouse.moveTo(moveTo[0], moveTo[1]);
|
||||
mouse.up();
|
||||
});
|
||||
}
|
||||
|
||||
function expectEqualIds(expected: ExcalidrawElement[]) {
|
||||
expect(h.elements.map((x) => x.id)).toEqual(expected.map((x) => x.id));
|
||||
}
|
||||
|
||||
let frame: ExcalidrawElement;
|
||||
let rect1: ExcalidrawElement;
|
||||
let rect2: ExcalidrawElement;
|
||||
let rect3: ExcalidrawElement;
|
||||
let rect4: ExcalidrawElement;
|
||||
let text: ExcalidrawElement;
|
||||
let arrow: ExcalidrawElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(<Excalidraw />);
|
||||
|
||||
frame = API.createElement({ id: "id0", type: "frame", x: 0, width: 150 });
|
||||
rect1 = API.createElement({
|
||||
id: "id1",
|
||||
type: "rectangle",
|
||||
x: -1000,
|
||||
});
|
||||
rect2 = API.createElement({
|
||||
id: "id2",
|
||||
type: "rectangle",
|
||||
x: 200,
|
||||
width: 50,
|
||||
});
|
||||
rect3 = API.createElement({
|
||||
id: "id3",
|
||||
type: "rectangle",
|
||||
x: 400,
|
||||
width: 50,
|
||||
});
|
||||
rect4 = API.createElement({
|
||||
id: "id4",
|
||||
type: "rectangle",
|
||||
x: 1000,
|
||||
width: 50,
|
||||
});
|
||||
text = API.createElement({
|
||||
id: "id5",
|
||||
type: "text",
|
||||
x: 100,
|
||||
});
|
||||
arrow = API.createElement({
|
||||
id: "id6",
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
boundElements: [{ id: text.id, type: "text" }],
|
||||
});
|
||||
});
|
||||
|
||||
const commonTestCases = async (
|
||||
func: typeof resizeFrameOverElement | typeof dragElementIntoFrame,
|
||||
) => {
|
||||
describe("when frame is in a layer below", async () => {
|
||||
it("should add an element", async () => {
|
||||
h.elements = [frame, rect2];
|
||||
|
||||
func(frame, rect2);
|
||||
|
||||
expect(h.elements[0].frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, frame]);
|
||||
});
|
||||
|
||||
it("should add elements", async () => {
|
||||
h.elements = [frame, rect2, rect3];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, rect3, frame]);
|
||||
});
|
||||
|
||||
it("should add elements when there are other other elements in between", async () => {
|
||||
h.elements = [frame, rect1, rect2, rect4, rect3];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, rect3, frame, rect1, rect4]);
|
||||
});
|
||||
|
||||
it("should add elements when there are other elements in between and the order is reversed", async () => {
|
||||
h.elements = [frame, rect3, rect4, rect2, rect1];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, rect3, frame, rect4, rect1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when frame is in a layer above", async () => {
|
||||
it("should add an element", async () => {
|
||||
h.elements = [rect2, frame];
|
||||
|
||||
func(frame, rect2);
|
||||
|
||||
expect(h.elements[0].frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, frame]);
|
||||
});
|
||||
|
||||
it("should add elements", async () => {
|
||||
h.elements = [rect2, rect3, frame];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect3, rect2, frame]);
|
||||
});
|
||||
|
||||
it("should add elements when there are other other elements in between", async () => {
|
||||
h.elements = [rect1, rect2, rect4, rect3, frame];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect1, rect4, rect3, rect2, frame]);
|
||||
});
|
||||
|
||||
it("should add elements when there are other elements in between and the order is reversed", async () => {
|
||||
h.elements = [rect3, rect4, rect2, rect1, frame];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect4, rect1, rect3, rect2, frame]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when frame is in an inner layer", async () => {
|
||||
it("should add elements", async () => {
|
||||
h.elements = [rect2, frame, rect3];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, rect3, frame]);
|
||||
});
|
||||
|
||||
it("should add elements when there are other other elements in between", async () => {
|
||||
h.elements = [rect2, rect1, frame, rect4, rect3];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect1, rect2, rect3, frame, rect4]);
|
||||
});
|
||||
|
||||
it("should add elements when there are other elements in between and the order is reversed", async () => {
|
||||
h.elements = [rect3, rect4, frame, rect2, rect1];
|
||||
|
||||
func(frame, rect2);
|
||||
func(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect4, rect3, rect2, frame, rect1]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const resizingTest = async (
|
||||
containerType: "arrow" | "rectangle",
|
||||
initialOrder: ElementType[],
|
||||
expectedOrder: ElementType[],
|
||||
) => {
|
||||
await render(<Excalidraw />);
|
||||
|
||||
const frame = API.createElement({ type: "frame", x: 0, y: 0 });
|
||||
|
||||
h.elements = reorderElements(
|
||||
[
|
||||
frame,
|
||||
...convertToExcalidrawElements([
|
||||
{
|
||||
type: containerType,
|
||||
x: 100,
|
||||
y: 100,
|
||||
height: 10,
|
||||
label: { text: "xx" },
|
||||
},
|
||||
]),
|
||||
],
|
||||
initialOrder,
|
||||
);
|
||||
|
||||
assertOrder(h.elements, initialOrder);
|
||||
|
||||
expect(h.elements[1].frameId).toBe(null);
|
||||
expect(h.elements[2].frameId).toBe(null);
|
||||
|
||||
const container = h.elements[1];
|
||||
|
||||
resizeFrameOverElement(frame, container);
|
||||
assertOrder(h.elements, expectedOrder);
|
||||
|
||||
expect(h.elements[0].frameId).toBe(frame.id);
|
||||
expect(h.elements[1].frameId).toBe(frame.id);
|
||||
};
|
||||
|
||||
describe("resizing frame over elements", async () => {
|
||||
await commonTestCases(resizeFrameOverElement);
|
||||
|
||||
it("resizing over text containers and labelled arrows", async () => {
|
||||
await resizingTest(
|
||||
"rectangle",
|
||||
["frame", "rectangle", "text"],
|
||||
["rectangle", "text", "frame"],
|
||||
);
|
||||
await resizingTest(
|
||||
"rectangle",
|
||||
["frame", "text", "rectangle"],
|
||||
["rectangle", "text", "frame"],
|
||||
);
|
||||
await resizingTest(
|
||||
"rectangle",
|
||||
["rectangle", "text", "frame"],
|
||||
["rectangle", "text", "frame"],
|
||||
);
|
||||
await resizingTest(
|
||||
"rectangle",
|
||||
["text", "rectangle", "frame"],
|
||||
["rectangle", "text", "frame"],
|
||||
);
|
||||
await resizingTest(
|
||||
"arrow",
|
||||
["frame", "arrow", "text"],
|
||||
["arrow", "text", "frame"],
|
||||
);
|
||||
await resizingTest(
|
||||
"arrow",
|
||||
["text", "arrow", "frame"],
|
||||
["arrow", "text", "frame"],
|
||||
);
|
||||
await resizingTest(
|
||||
"arrow",
|
||||
["frame", "arrow", "text"],
|
||||
["arrow", "text", "frame"],
|
||||
);
|
||||
|
||||
// FIXME failing in tests (it fails to add elements to frame for some
|
||||
// reason) but works in browser. (╯°□°)╯︵ ┻━┻
|
||||
//
|
||||
// Looks like the `getElementsCompletelyInFrame()` doesn't work
|
||||
// in these cases.
|
||||
//
|
||||
// await testElements(
|
||||
// "arrow",
|
||||
// ["arrow", "text", "frame"],
|
||||
// ["arrow", "text", "frame"],
|
||||
// );
|
||||
});
|
||||
|
||||
it("should add arrow bound with text when frame is in a layer below", async () => {
|
||||
h.elements = [frame, arrow, text];
|
||||
|
||||
resizeFrameOverElement(frame, arrow);
|
||||
|
||||
expect(arrow.frameId).toBe(frame.id);
|
||||
expect(text.frameId).toBe(frame.id);
|
||||
expectEqualIds([arrow, text, frame]);
|
||||
});
|
||||
|
||||
it("should add arrow bound with text when frame is in a layer above", async () => {
|
||||
h.elements = [arrow, text, frame];
|
||||
|
||||
resizeFrameOverElement(frame, arrow);
|
||||
|
||||
expect(arrow.frameId).toBe(frame.id);
|
||||
expect(text.frameId).toBe(frame.id);
|
||||
expectEqualIds([arrow, text, frame]);
|
||||
});
|
||||
|
||||
it("should add arrow bound with text when frame is in an inner layer", async () => {
|
||||
h.elements = [arrow, frame, text];
|
||||
|
||||
resizeFrameOverElement(frame, arrow);
|
||||
|
||||
expect(arrow.frameId).toBe(frame.id);
|
||||
expect(text.frameId).toBe(frame.id);
|
||||
expectEqualIds([arrow, text, frame]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resizing frame over elements but downwards", async () => {
|
||||
it("should add elements when frame is in a layer below", async () => {
|
||||
h.elements = [frame, rect1, rect2, rect3, rect4];
|
||||
|
||||
resizeFrameOverElement(frame, rect4);
|
||||
resizeFrameOverElement(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2, rect3, frame, rect4, rect1]);
|
||||
});
|
||||
|
||||
it("should add elements when frame is in a layer above", async () => {
|
||||
h.elements = [rect1, rect2, rect3, rect4, frame];
|
||||
|
||||
resizeFrameOverElement(frame, rect4);
|
||||
resizeFrameOverElement(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect1, rect2, rect3, frame, rect4]);
|
||||
});
|
||||
|
||||
it("should add elements when frame is in an inner layer", async () => {
|
||||
h.elements = [rect1, rect2, frame, rect3, rect4];
|
||||
|
||||
resizeFrameOverElement(frame, rect4);
|
||||
resizeFrameOverElement(frame, rect3);
|
||||
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expect(rect3.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect1, rect2, rect3, frame, rect4]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("dragging elements into the frame", async () => {
|
||||
await commonTestCases(dragElementIntoFrame);
|
||||
|
||||
it("should drag element inside, duplicate it and keep it in frame", () => {
|
||||
h.elements = [frame, rect2];
|
||||
|
||||
dragElementIntoFrame(frame, rect2);
|
||||
|
||||
const rect2_copy = { ...rect2, id: `${rect2.id}_copy` };
|
||||
|
||||
selectElementAndDuplicate(rect2);
|
||||
|
||||
expect(rect2_copy.frameId).toBe(frame.id);
|
||||
expect(rect2.frameId).toBe(frame.id);
|
||||
expectEqualIds([rect2_copy, rect2, frame]);
|
||||
});
|
||||
|
||||
it("should drag element inside, duplicate it and remove it from frame", () => {
|
||||
h.elements = [frame, rect2];
|
||||
|
||||
dragElementIntoFrame(frame, rect2);
|
||||
|
||||
const rect2_copy = { ...rect2, id: `${rect2.id}_copy` };
|
||||
|
||||
// move the rect2 outside the frame
|
||||
selectElementAndDuplicate(rect2, [-1000, -1000]);
|
||||
|
||||
expect(rect2_copy.frameId).toBe(frame.id);
|
||||
expect(rect2.frameId).toBe(null);
|
||||
expectEqualIds([rect2_copy, frame, rect2]);
|
||||
});
|
||||
});
|
||||
});
|
75
src/frame.ts
75
src/frame.ts
|
@ -468,22 +468,39 @@ export const addElementsToFrame = (
|
|||
}
|
||||
}
|
||||
|
||||
let nextElements = allElements.slice();
|
||||
// Optimisation since findIndex on "newElements" is slow
|
||||
const nextElementsIndex = nextElements.reduce(
|
||||
(acc: Record<string, number | undefined>, element, index) => {
|
||||
const allElementsIndex = allElements.reduce(
|
||||
(acc: Record<string, number>, element, index) => {
|
||||
acc[element.id] = index;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const frameBoundary = findIndex(nextElements, (e) => e.frameId === frame.id);
|
||||
const frameIndex = allElementsIndex[frame.id];
|
||||
// need to be calculated before the mutation below occurs
|
||||
const leftFrameBoundaryIndex = findIndex(
|
||||
allElements,
|
||||
(e) => e.frameId === frame.id,
|
||||
);
|
||||
|
||||
const existingFrameChildren = allElements.filter(
|
||||
(element) => element.frameId === frame.id,
|
||||
);
|
||||
|
||||
const addedFrameChildren_left: ExcalidrawElement[] = [];
|
||||
const addedFrameChildren_right: ExcalidrawElement[] = [];
|
||||
|
||||
for (const element of omitGroupsContainingFrames(
|
||||
allElements,
|
||||
_elementsToAdd,
|
||||
)) {
|
||||
if (element.frameId !== frame.id && !isFrameElement(element)) {
|
||||
if (allElementsIndex[element.id] > frameIndex) {
|
||||
addedFrameChildren_right.push(element);
|
||||
} else {
|
||||
addedFrameChildren_left.push(element);
|
||||
}
|
||||
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
|
@ -491,28 +508,35 @@ export const addElementsToFrame = (
|
|||
},
|
||||
false,
|
||||
);
|
||||
|
||||
const frameIndex = nextElementsIndex[frame.id] ?? -1;
|
||||
const elementIndex = nextElementsIndex[element.id] ?? -1;
|
||||
|
||||
if (elementIndex < frameBoundary) {
|
||||
nextElements = [
|
||||
...nextElements.slice(0, elementIndex),
|
||||
...nextElements.slice(elementIndex + 1, frameBoundary),
|
||||
element,
|
||||
...nextElements.slice(frameBoundary),
|
||||
];
|
||||
} else if (elementIndex > frameIndex) {
|
||||
nextElements = [
|
||||
...nextElements.slice(0, frameIndex),
|
||||
element,
|
||||
...nextElements.slice(frameIndex, elementIndex),
|
||||
...nextElements.slice(elementIndex + 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const frameElement = allElements[frameIndex];
|
||||
const nextFrameChildren = addedFrameChildren_left
|
||||
.concat(existingFrameChildren)
|
||||
.concat(addedFrameChildren_right);
|
||||
|
||||
const nextFrameChildrenMap = nextFrameChildren.reduce(
|
||||
(acc: Record<string, boolean>, element) => {
|
||||
acc[element.id] = true;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const nextOtherElements_left = allElements
|
||||
.slice(0, leftFrameBoundaryIndex >= 0 ? leftFrameBoundaryIndex : frameIndex)
|
||||
.filter((element) => !nextFrameChildrenMap[element.id]);
|
||||
|
||||
const nextOtherElement_right = allElements
|
||||
.slice(frameIndex + 1)
|
||||
.filter((element) => !nextFrameChildrenMap[element.id]);
|
||||
|
||||
const nextElements = nextOtherElements_left
|
||||
.concat(nextFrameChildren)
|
||||
.concat([frameElement])
|
||||
.concat(nextOtherElement_right);
|
||||
|
||||
return nextElements;
|
||||
};
|
||||
|
||||
|
@ -526,6 +550,7 @@ export const removeElementsFromFrame = (
|
|||
for (const element of elementsToRemove) {
|
||||
if (element.frameId) {
|
||||
_elementsToRemove.push(element);
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
_elementsToRemove.push(boundTextElement);
|
||||
|
@ -574,7 +599,7 @@ export const replaceAllElementsInFrame = (
|
|||
);
|
||||
};
|
||||
|
||||
/** does not mutate elements, but return new ones */
|
||||
/** does not mutate elements, but returns new ones */
|
||||
export const updateFrameMembershipOfSelectedElements = (
|
||||
allElements: ExcalidrawElementsIncludingDeleted,
|
||||
appState: AppState,
|
||||
|
|
|
@ -71,6 +71,7 @@ export const selectGroupsForSelectedElements = (function () {
|
|||
selectedElements: readonly NonDeleted<ExcalidrawElement>[],
|
||||
elements: readonly NonDeleted<ExcalidrawElement>[],
|
||||
appState: Pick<AppState, "selectedElementIds" | "editingGroupId">,
|
||||
prevAppState: InteractiveCanvasAppState,
|
||||
): SelectGroupsReturnType => {
|
||||
if (
|
||||
lastReturnValue !== undefined &&
|
||||
|
@ -134,10 +135,13 @@ export const selectGroupsForSelectedElements = (function () {
|
|||
lastReturnValue = {
|
||||
editingGroupId: appState.editingGroupId,
|
||||
selectedGroupIds,
|
||||
selectedElementIds: {
|
||||
...appState.selectedElementIds,
|
||||
...selectedElementIdsInGroups,
|
||||
},
|
||||
selectedElementIds: makeNextSelectedElementIds(
|
||||
{
|
||||
...appState.selectedElementIds,
|
||||
...selectedElementIdsInGroups,
|
||||
},
|
||||
prevAppState,
|
||||
),
|
||||
};
|
||||
|
||||
return lastReturnValue;
|
||||
|
@ -181,7 +185,7 @@ export const selectGroupsForSelectedElements = (function () {
|
|||
};
|
||||
}
|
||||
|
||||
return _selectGroups(selectedElements, elements, appState);
|
||||
return _selectGroups(selectedElements, elements, appState, prevAppState);
|
||||
};
|
||||
|
||||
selectGroupsForSelectedElements.clearCache = () => {
|
||||
|
|
|
@ -264,7 +264,8 @@
|
|||
"bindTextToElement": "Press enter to add text",
|
||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page."
|
||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
||||
"disableSnapping": "Hold CtrlOrCmd to disable snapping"
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Cannot show preview",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[
|
||||
{
|
||||
"path": "dist/excalidraw.production.min.js",
|
||||
"limit": "291 kB"
|
||||
"limit": "305 kB"
|
||||
},
|
||||
{
|
||||
"path": "dist/excalidraw-assets/locales",
|
||||
|
|
|
@ -11,28 +11,10 @@ The change should be grouped under one of the below section and must contain PR
|
|||
Please add the latest change on the top under the correct section.
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
|
||||
### renderEmbeddable
|
||||
|
||||
```tsx
|
||||
(element: NonDeletedExcalidrawElement, radius: number, appState: UIAppState) => JSX.Element | null;`
|
||||
```
|
||||
|
||||
The renderEmbeddable function allows you to customize the rendering of a JSX component instead of using the default `<iframe>`. By setting props.renderEmbeddable, you can provide a custom implementation for rendering the element.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- element (NonDeletedExcalidrawElement): The element to be rendered.
|
||||
- radius (number): The calculated border radius in pixels.
|
||||
- appState (UIAppState): The current state of the UI.
|
||||
|
||||
#### Return value:
|
||||
|
||||
JSX.Element | null: The JSX component representing the custom rendering, or null if the default `<iframe>` should be rendered.
|
||||
|
||||
### Features
|
||||
## 0.16.0 (2023-09-19)
|
||||
|
||||
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
||||
- Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||
- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateEmbeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||
- Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581).
|
||||
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
|
||||
|
@ -48,6 +30,235 @@ JSX.Element | null: The JSX component representing the custom rendering, or null
|
|||
- `props.onClose` replaced with `props.onStateChange`.
|
||||
- `restore()`/`restoreAppState()` now retains `appState.openSidebar` regardless of docked state.
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Features
|
||||
|
||||
- allow `avif`, `jfif`, `webp`, `bmp`, `ico` image types [#6500](https://github.com/excalidraw/excalidraw/pull/6500)
|
||||
- Zen-mode/go-to-plus button style tweaks [#7006](https://github.com/excalidraw/excalidraw/pull/7006)
|
||||
|
||||
- Holding down CMD/CTRL will disable snap to grid when grid is active [#6983](https://github.com/excalidraw/excalidraw/pull/6983)
|
||||
|
||||
- Update logo [#6979](https://github.com/excalidraw/excalidraw/pull/6979)
|
||||
|
||||
- Export `changeProperty()` and `getFormValue()`. [#6957](https://github.com/excalidraw/excalidraw/pull/6957)
|
||||
|
||||
- Partition main canvas vertically [#6759](https://github.com/excalidraw/excalidraw/pull/6759)
|
||||
|
||||
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
||||
|
||||
- Add support for simplePDF in Web-Embeds [#6810](https://github.com/excalidraw/excalidraw/pull/6810)
|
||||
|
||||
- Add support for val.town embeds [#6821](https://github.com/excalidraw/excalidraw/pull/6821)
|
||||
|
||||
- Render bold lines in grid [#6779](https://github.com/excalidraw/excalidraw/pull/6779)
|
||||
|
||||
- Adds support for stackblitz.com embeds [#6813](https://github.com/excalidraw/excalidraw/pull/6813)
|
||||
|
||||
- Cache most of element selection [#6747](https://github.com/excalidraw/excalidraw/pull/6747)
|
||||
|
||||
- Support customizing what parts of frames are rendered [#6752](https://github.com/excalidraw/excalidraw/pull/6752)
|
||||
|
||||
- Make `appState.selectedElementIds` more stable [#6745](https://github.com/excalidraw/excalidraw/pull/6745)
|
||||
|
||||
- Overwrite confirmation dialogs [#6658](https://github.com/excalidraw/excalidraw/pull/6658)
|
||||
|
||||
- Simple analitycs [#6683](https://github.com/excalidraw/excalidraw/pull/6683)
|
||||
|
||||
- Introduce frames [#6123](https://github.com/excalidraw/excalidraw/pull/6123)
|
||||
|
||||
- Add canvas-roundrect-polyfill package [#6675](https://github.com/excalidraw/excalidraw/pull/6675)
|
||||
|
||||
- Polyfill `CanvasRenderingContext2D.roundRect` [#6673](https://github.com/excalidraw/excalidraw/pull/6673)
|
||||
|
||||
- Disable collab feature when running in iframe [#6646](https://github.com/excalidraw/excalidraw/pull/6646)
|
||||
|
||||
- Assign random user name when not set [#6663](https://github.com/excalidraw/excalidraw/pull/6663)
|
||||
|
||||
- Redesigned collab cursors [#6659](https://github.com/excalidraw/excalidraw/pull/6659)
|
||||
|
||||
- Eye dropper [#6615](https://github.com/excalidraw/excalidraw/pull/6615)
|
||||
|
||||
- Redesign of Live Collaboration dialog [#6635](https://github.com/excalidraw/excalidraw/pull/6635)
|
||||
|
||||
- Recover scrolled position after Library re-opening [#6624](https://github.com/excalidraw/excalidraw/pull/6624)
|
||||
|
||||
- Clearing library cache [#6621](https://github.com/excalidraw/excalidraw/pull/6621)
|
||||
|
||||
- Update design of ImageExportDialog [#6614](https://github.com/excalidraw/excalidraw/pull/6614)
|
||||
|
||||
- Add flipping for multiple elements [#5578](https://github.com/excalidraw/excalidraw/pull/5578)
|
||||
|
||||
- Color picker redesign [#6216](https://github.com/excalidraw/excalidraw/pull/6216)
|
||||
|
||||
- Add "unlock all elements" to canvas contextMenu [#5894](https://github.com/excalidraw/excalidraw/pull/5894)
|
||||
|
||||
- Library sidebar design tweaks [#6582](https://github.com/excalidraw/excalidraw/pull/6582)
|
||||
|
||||
- Add Trans component for interpolating JSX in translations [#6534](https://github.com/excalidraw/excalidraw/pull/6534)
|
||||
|
||||
- Testing simple analytics and fathom analytics for better privacy of the users [#6529](https://github.com/excalidraw/excalidraw/pull/6529)
|
||||
|
||||
- Retain `seed` on shift-paste [#6509](https://github.com/excalidraw/excalidraw/pull/6509)
|
||||
|
||||
- Allow `avif`, `jfif`, `webp`, `bmp`, `ico` image types (#6500
|
||||
|
||||
### Fixes
|
||||
|
||||
- Improperly disabling UI pointer-events on canvas interaction [#7005](https://github.com/excalidraw/excalidraw/pull/7005)
|
||||
|
||||
- Several eyeDropper fixes [#7002](https://github.com/excalidraw/excalidraw/pull/7002)
|
||||
|
||||
- IsBindableElement to affirm frames [#6900](https://github.com/excalidraw/excalidraw/pull/6900)
|
||||
|
||||
- Use `device.isMobile` for sidebar trigger label breakpoint [#6994](https://github.com/excalidraw/excalidraw/pull/6994)
|
||||
|
||||
- Export to plus url [#6980](https://github.com/excalidraw/excalidraw/pull/6980)
|
||||
|
||||
- Z-index inconsistencies during addition / deletion in frames [#6914](https://github.com/excalidraw/excalidraw/pull/6914)
|
||||
|
||||
- Update size-limit so react is not installed as dependency [#6964](https://github.com/excalidraw/excalidraw/pull/6964)
|
||||
|
||||
- Stale labeled arrow bounds cache after editing the label [#6893](https://github.com/excalidraw/excalidraw/pull/6893)
|
||||
|
||||
- Canvas flickering due to resetting canvas on skipped frames [#6960](https://github.com/excalidraw/excalidraw/pull/6960)
|
||||
|
||||
- Grid jittery after partition PR [#6935](https://github.com/excalidraw/excalidraw/pull/6935)
|
||||
|
||||
- Regression in indexing when adding elements to frame [#6904](https://github.com/excalidraw/excalidraw/pull/6904)
|
||||
|
||||
- Stabilize `selectedElementIds` when box selecting [#6912](https://github.com/excalidraw/excalidraw/pull/6912)
|
||||
|
||||
- Resetting deleted elements on duplication [#6906](https://github.com/excalidraw/excalidraw/pull/6906)
|
||||
|
||||
- Make canvas compos memoize appState on props they declare [#6897](https://github.com/excalidraw/excalidraw/pull/6897)
|
||||
|
||||
- Scope `--color-selection` retrieval to given instance [#6886](https://github.com/excalidraw/excalidraw/pull/6886)
|
||||
|
||||
- Webpack config exclude statement to system agnostic [#6857](https://github.com/excalidraw/excalidraw/pull/6857)
|
||||
|
||||
- Remove `embeddable` from generic elements [#6853](https://github.com/excalidraw/excalidraw/pull/6853)
|
||||
|
||||
- Resizing arrow labels [#6789](https://github.com/excalidraw/excalidraw/pull/6789)
|
||||
|
||||
- Eye-dropper not working with app offset correctly on non-1 dPR [#6835](https://github.com/excalidraw/excalidraw/pull/6835)
|
||||
|
||||
- Add self destroying service-worker.js to migrate everyone from CRA to Vite [#6833](https://github.com/excalidraw/excalidraw/pull/6833)
|
||||
|
||||
- Forgotten REACT_APP env variables [#6834](https://github.com/excalidraw/excalidraw/pull/6834)
|
||||
|
||||
- Refresh sw when browser refreshed [#6824](https://github.com/excalidraw/excalidraw/pull/6824)
|
||||
|
||||
- Adding to selection via shift box-select [#6815](https://github.com/excalidraw/excalidraw/pull/6815)
|
||||
|
||||
- Prevent binding focus NaN value [#6803](https://github.com/excalidraw/excalidraw/pull/6803)
|
||||
|
||||
- Use pull request in semantic workflow for better security [#6799](https://github.com/excalidraw/excalidraw/pull/6799)
|
||||
|
||||
- Don't show `canvasBackground` label when `UIOptions.canvasActions.changeViewBackgroundColor` is false [#6781](https://github.com/excalidraw/excalidraw/pull/6781)
|
||||
|
||||
- Use subdirectory for @excalidraw/excalidraw size limit [#6787](https://github.com/excalidraw/excalidraw/pull/6787)
|
||||
|
||||
- Use actual dock state to not close docked library on insert [#6766](https://github.com/excalidraw/excalidraw/pull/6766)
|
||||
|
||||
- UI disappears when pressing the eyedropper shortcut on mobile [#6725](https://github.com/excalidraw/excalidraw/pull/6725)
|
||||
|
||||
- Elements in non-existing frame getting removed [#6708](https://github.com/excalidraw/excalidraw/pull/6708)
|
||||
|
||||
- Scrollbars renders but disable [#6706](https://github.com/excalidraw/excalidraw/pull/6706)
|
||||
|
||||
- Typo in chart.ts [#6696](https://github.com/excalidraw/excalidraw/pull/6696)
|
||||
|
||||
- Do not bind text to container using text tool when it has text already [#6694](https://github.com/excalidraw/excalidraw/pull/6694)
|
||||
|
||||
- Don't allow binding text to images [#6693](https://github.com/excalidraw/excalidraw/pull/6693)
|
||||
|
||||
- Updated link for documentation page under help section [#6654](https://github.com/excalidraw/excalidraw/pull/6654)
|
||||
|
||||
- Collab username style fixes [#6668](https://github.com/excalidraw/excalidraw/pull/6668)
|
||||
|
||||
- Bound arrows not updated when rotating multiple elements [#6662](https://github.com/excalidraw/excalidraw/pull/6662)
|
||||
|
||||
- Delete setCursor when resize [#6660](https://github.com/excalidraw/excalidraw/pull/6660)
|
||||
|
||||
- Creating text while color picker open [#6651](https://github.com/excalidraw/excalidraw/pull/6651)
|
||||
|
||||
- Cleanup textWysiwyg and getAdjustedDimensions [#6520](https://github.com/excalidraw/excalidraw/pull/6520)
|
||||
|
||||
- Eye dropper not accounting for offsets [#6640](https://github.com/excalidraw/excalidraw/pull/6640)
|
||||
|
||||
- Color picker input closing problem [#6599](https://github.com/excalidraw/excalidraw/pull/6599)
|
||||
|
||||
- Export dialog shortcut toggles console on firefox [#6620](https://github.com/excalidraw/excalidraw/pull/6620)
|
||||
|
||||
- Add react v17 `useTransition` polyfill [#6618](https://github.com/excalidraw/excalidraw/pull/6618)
|
||||
|
||||
- Library dropdown visibility issue for mobile [#6613](https://github.com/excalidraw/excalidraw/pull/6613)
|
||||
|
||||
- `withInternalFallback` leaking state in multi-instance scenarios [#6602](https://github.com/excalidraw/excalidraw/pull/6602)
|
||||
|
||||
- Language list containing duplicate `en` lang [#6583](https://github.com/excalidraw/excalidraw/pull/6583)
|
||||
|
||||
- Garbled text displayed on avatars [#6575](https://github.com/excalidraw/excalidraw/pull/6575)
|
||||
|
||||
- Assign the original text to text editor only during init [#6580](https://github.com/excalidraw/excalidraw/pull/6580)
|
||||
|
||||
- I18n: Apply Trans component to publish library dialogue [#6564](https://github.com/excalidraw/excalidraw/pull/6564)
|
||||
|
||||
- Fix brave error i18n string and remove unused [#6561](https://github.com/excalidraw/excalidraw/pull/6561)
|
||||
|
||||
- Revert add version tags to Docker build [#6540](https://github.com/excalidraw/excalidraw/pull/6540)
|
||||
|
||||
- Don't refresh dimensions for text containers on font load [#6523](https://github.com/excalidraw/excalidraw/pull/6523)
|
||||
|
||||
- Cleanup getMaxContainerHeight and getMaxContainerWidth [#6519](https://github.com/excalidraw/excalidraw/pull/6519)
|
||||
|
||||
- Cleanup redrawTextBoundingBox [#6518](https://github.com/excalidraw/excalidraw/pull/6518)
|
||||
|
||||
- Text jumps when editing on Android Chrome [#6503](https://github.com/excalidraw/excalidraw/pull/6503)
|
||||
|
||||
### Styles
|
||||
|
||||
- Removes extra spaces [#6558](https://github.com/excalidraw/excalidraw/pull/6558)
|
||||
|
||||
- Fix font family inconsistencies [#6501](https://github.com/excalidraw/excalidraw/pull/6501)
|
||||
|
||||
### Refactor
|
||||
|
||||
- Factor out shape generation from `renderElement.ts` pt 2 [#6878](https://github.com/excalidraw/excalidraw/pull/6878)
|
||||
|
||||
- Add typeScript support to enforce valid translation keys [#6776](https://github.com/excalidraw/excalidraw/pull/6776)
|
||||
|
||||
- Simplify `ImageExportDialog` [#6578](https://github.com/excalidraw/excalidraw/pull/6578)
|
||||
|
||||
### Performance
|
||||
|
||||
- Limiting the suggested binding to fix performance issue [#6877](https://github.com/excalidraw/excalidraw/pull/6877)
|
||||
|
||||
- Memoize rendering of library [#6622](https://github.com/excalidraw/excalidraw/pull/6622)
|
||||
|
||||
- Improve rendering performance for Library [#6587](https://github.com/excalidraw/excalidraw/pull/6587)
|
||||
|
||||
- Use `UIAppState` where possible to reduce UI rerenders [#6560](https://github.com/excalidraw/excalidraw/pull/6560)
|
||||
|
||||
### Build
|
||||
|
||||
- Increase limit for bundle by 1kb [#6880](https://github.com/excalidraw/excalidraw/pull/6880)
|
||||
|
||||
- Update to node 18 in docker [#6822](https://github.com/excalidraw/excalidraw/pull/6822)
|
||||
|
||||
- Migrate to Vite 🚀 [#6818](https://github.com/excalidraw/excalidraw/pull/6818)
|
||||
|
||||
- Migrate to Vite 🚀 [#6713](https://github.com/excalidraw/excalidraw/pull/6713)
|
||||
|
||||
- Increase limit to 290 kB for prod bundle [#6809](https://github.com/excalidraw/excalidraw/pull/6809)
|
||||
|
||||
- Add version tags to Docker build [#6508](https://github.com/excalidraw/excalidraw/pull/6508)
|
||||
|
||||
---
|
||||
|
||||
## 0.15.2 (2023-04-20)
|
||||
|
||||
### Docs
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.15.2",
|
||||
"version": "0.16.0",
|
||||
"main": "main.js",
|
||||
"types": "types/packages/excalidraw/index.d.ts",
|
||||
"files": [
|
||||
|
@ -52,7 +52,7 @@
|
|||
"@babel/preset-env": "7.18.6",
|
||||
"@babel/preset-react": "7.18.6",
|
||||
"@babel/preset-typescript": "7.18.6",
|
||||
"@size-limit/preset-big-lib": "8.2.6",
|
||||
"@size-limit/preset-big-lib": "9.0.0",
|
||||
"autoprefixer": "10.4.7",
|
||||
"babel-loader": "8.2.5",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
|
@ -63,7 +63,7 @@
|
|||
"mini-css-extract-plugin": "2.6.1",
|
||||
"postcss-loader": "7.0.1",
|
||||
"sass-loader": "13.0.2",
|
||||
"size-limit": "8.2.4",
|
||||
"size-limit": "9.0.0",
|
||||
"style-loader": "3.3.3",
|
||||
"terser-webpack-plugin": "5.3.3",
|
||||
"ts-loader": "9.3.1",
|
||||
|
|
|
@ -1112,38 +1112,37 @@
|
|||
dependencies:
|
||||
debug "^4.1.1"
|
||||
|
||||
"@size-limit/file@8.2.6":
|
||||
version "8.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-8.2.6.tgz#0e17045a0fa8009fc787c85e3c09f611316f908c"
|
||||
integrity sha512-B7ayjxiJsbtXdIIWazJkB5gezi5WBMecdHTFPMDhI3NwEML1RVvUjAkrb1mPAAkIpt2LVHPnhdCUHjqDdjugwg==
|
||||
"@size-limit/file@9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-9.0.0.tgz#eed5415f5bcc8407979e47ffa49ffaf12d2d2378"
|
||||
integrity sha512-oM2UaH2FRq4q22k+R+P6xCpzET10T94LFdSjb9svVu/vOD7NaB9LGcG6se8TW1BExXiyXO4GEhLsBt3uMKM3qA==
|
||||
dependencies:
|
||||
semver "7.5.3"
|
||||
semver "7.5.4"
|
||||
|
||||
"@size-limit/preset-big-lib@8.2.6":
|
||||
version "8.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/preset-big-lib/-/preset-big-lib-8.2.6.tgz#fbff51e7a03fc36b6b3d9103cbe5b3909e35a83e"
|
||||
integrity sha512-63a+yos0QNMVCfx1OWnxBrdQVTlBVGzW5fDXwpWq/hKfP3B89XXHYGeL2Z2f8IXSVeGkAHXnDcTZyIPRaXffVg==
|
||||
"@size-limit/preset-big-lib@9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/preset-big-lib/-/preset-big-lib-9.0.0.tgz#ddcf30e7646b66ecc0f8a1a6498a5eda6d82876d"
|
||||
integrity sha512-wc+VNLXjn0z11s1IWevo8+utP7uZGPVDNNe5cNyMFYHv7/pwJtgsd8w2onEkbK1h8x1oJfWlcqFNKAnvD1Bylw==
|
||||
dependencies:
|
||||
"@size-limit/file" "8.2.6"
|
||||
"@size-limit/time" "8.2.6"
|
||||
"@size-limit/webpack" "8.2.6"
|
||||
size-limit "8.2.6"
|
||||
"@size-limit/file" "9.0.0"
|
||||
"@size-limit/time" "9.0.0"
|
||||
"@size-limit/webpack" "9.0.0"
|
||||
size-limit "9.0.0"
|
||||
|
||||
"@size-limit/time@8.2.6":
|
||||
version "8.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/time/-/time-8.2.6.tgz#5d1912bcfc6437f6f59804737ad0538b25c207ed"
|
||||
integrity sha512-fUEPvz7Uq6+oUQxSYbNlJt3tTgQBl1VY21USi/B7ebdnVKLnUx1JyPI9v7imN6XEkB2VpJtnYgjFeLgNrirzMA==
|
||||
"@size-limit/time@9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/time/-/time-9.0.0.tgz#44ba75b3cba30736b133dbb3fd740f894a642c87"
|
||||
integrity sha512-//Yba5fRkYqpBZ6MFtjDTSjCpQonDMqkwofpe0G1hMd/5l/3PZXVLDCAU2BW3nQFqTkpeyytFG6Y3jxUqSddiw==
|
||||
dependencies:
|
||||
estimo "^2.3.6"
|
||||
react "^17.0.2"
|
||||
|
||||
"@size-limit/webpack@8.2.6":
|
||||
version "8.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/webpack/-/webpack-8.2.6.tgz#3a3c98293b80f7c5fb6e8499199ae6f94f05b463"
|
||||
integrity sha512-y2sB66m5sJxIjZ8SEAzpWbiw3/+bnQHDHfk9cSbV5ChKklq02AlYg8BS5KxGWmMpdyUo4TzpjSCP9oEudY+hxQ==
|
||||
"@size-limit/webpack@9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@size-limit/webpack/-/webpack-9.0.0.tgz#4514851d3607490e228bf22bc95286643f64a490"
|
||||
integrity sha512-0YwdvmBj9rS4bXE/PY9vSdc5lCiQXmT0794EsG7yvlDMWyrWa/dsgcRok/w0MoZstfuLaS6lv03VI5UJRFU/lg==
|
||||
dependencies:
|
||||
nanoid "^3.3.6"
|
||||
webpack "^5.88.0"
|
||||
webpack "^5.88.2"
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.2"
|
||||
|
@ -1694,9 +1693,9 @@ ansi-styles@^4.1.0:
|
|||
color-convert "^2.0.1"
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
@ -2553,9 +2552,9 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
|||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-glob@^3.2.9:
|
||||
version "3.2.12"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
||||
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
|
||||
integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
|
@ -2672,9 +2671,9 @@ fs.realpath@^1.0.0:
|
|||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
|
@ -2993,7 +2992,7 @@ is-docker@^2.0.0, is-docker@^2.1.1:
|
|||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||
|
||||
is-glob@^4.0.1, is-glob@~4.0.1:
|
||||
version "4.0.3"
|
||||
|
@ -3105,7 +3104,7 @@ klona@^2.0.4, klona@^2.0.5:
|
|||
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
|
||||
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
|
||||
|
||||
lilconfig@^2.0.6, lilconfig@^2.1.0:
|
||||
lilconfig@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
||||
|
@ -3146,7 +3145,7 @@ lodash@^4.17.20, lodash@^4.17.4:
|
|||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0:
|
||||
loose-envify@^1.0.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
|
@ -3360,11 +3359,6 @@ npm-run-path@^4.0.1:
|
|||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-inspect@^1.9.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
|
||||
|
@ -3519,16 +3513,16 @@ picocolors@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
picomatch@^2.3.1:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
picomatch@^2.2.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
pkg-dir@4.2.0, pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||
|
@ -3685,14 +3679,6 @@ raw-body@2.5.1:
|
|||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
react@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
readable-stream@^2.0.1:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
|
@ -3927,10 +3913,10 @@ semver@7.0.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
|
||||
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
|
||||
|
||||
semver@7.5.3:
|
||||
version "7.5.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e"
|
||||
integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==
|
||||
semver@7.5.4, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
|
@ -3939,13 +3925,6 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
send@0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
||||
|
@ -4054,22 +4033,10 @@ sirv@^1.0.7:
|
|||
mime "^2.3.1"
|
||||
totalist "^1.0.0"
|
||||
|
||||
size-limit@8.2.4:
|
||||
version "8.2.4"
|
||||
resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-8.2.4.tgz#0ab0df7cbc89007d544a50b451f5fb4d110694ca"
|
||||
integrity sha512-Un16nSreD1v2CYwSorattiJcHuAWqXvg4TsGgzpjnoByqQwsSfCIEQHuaD14HNStzredR8cdsO9oGH91ibypTA==
|
||||
dependencies:
|
||||
bytes-iec "^3.1.1"
|
||||
chokidar "^3.5.3"
|
||||
globby "^11.1.0"
|
||||
lilconfig "^2.0.6"
|
||||
nanospinner "^1.1.0"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
size-limit@8.2.6:
|
||||
version "8.2.6"
|
||||
resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-8.2.6.tgz#e41dbc74a4d7fc13be72551b6ef31ea50007d18d"
|
||||
integrity sha512-zpznim/tX/NegjoQuRKgWTF4XiB0cn2qt90uJzxYNTFAqexk4b94DOAkBD3TwhC6c3kw2r0KcnA5upziVMZqDg==
|
||||
size-limit@9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-9.0.0.tgz#203c47303462a8351976eb26175acea5f4e80447"
|
||||
integrity sha512-DrA7o2DeRN3s+vwCA9nn7Ck9Y4pn9t0GNUwQRpKqBtBmNkl6LA2s/NlNCdtKHrEkRTeYA1ZQ65mnYveo9rUqgA==
|
||||
dependencies:
|
||||
bytes-iec "^3.1.1"
|
||||
chokidar "^3.5.3"
|
||||
|
@ -4556,7 +4523,7 @@ webpack@5.76.0:
|
|||
watchpack "^2.4.0"
|
||||
webpack-sources "^3.2.3"
|
||||
|
||||
webpack@^5.88.0:
|
||||
webpack@^5.88.2:
|
||||
version "5.88.2"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e"
|
||||
integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==
|
||||
|
|
|
@ -940,10 +940,8 @@ const _renderStaticScene = ({
|
|||
strokeGrid(
|
||||
context,
|
||||
appState.gridSize,
|
||||
-Math.ceil(appState.zoom.value / appState.gridSize) * appState.gridSize +
|
||||
(appState.scrollX % appState.gridSize),
|
||||
-Math.ceil(appState.zoom.value / appState.gridSize) * appState.gridSize +
|
||||
(appState.scrollY % appState.gridSize),
|
||||
appState.scrollX,
|
||||
appState.scrollY,
|
||||
appState.zoom,
|
||||
normalizedWidth / appState.zoom.value,
|
||||
normalizedHeight / appState.zoom.value,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1737,7 +1737,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||
|
||||
exports[`regression tests > Cmd/Ctrl-click exclusively select element under pointer > [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests > Cmd/Ctrl-click exclusively select element under pointer > [end of test] number of renders 1`] = `32`;
|
||||
exports[`regression tests > Cmd/Ctrl-click exclusively select element under pointer > [end of test] number of renders 1`] = `30`;
|
||||
|
||||
exports[`regression tests > Drags selected element when hitting only bounding box and keeps element selected > [end of test] appState 1`] = `
|
||||
{
|
||||
|
@ -4622,7 +4622,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history
|
|||
|
||||
exports[`regression tests > click-drag to select a group > [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests > click-drag to select a group > [end of test] number of renders 1`] = `19`;
|
||||
exports[`regression tests > click-drag to select a group > [end of test] number of renders 1`] = `18`;
|
||||
|
||||
exports[`regression tests > deleting last but one element in editing group should unselect the group > [end of test] appState 1`] = `
|
||||
{
|
||||
|
@ -15351,7 +15351,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||
|
||||
exports[`regression tests > single-clicking on a subgroup of a selected group should not alter selection > [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests > single-clicking on a subgroup of a selected group should not alter selection > [end of test] number of renders 1`] = `31`;
|
||||
exports[`regression tests > single-clicking on a subgroup of a selected group should not alter selection > [end of test] number of renders 1`] = `30`;
|
||||
|
||||
exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] appState 1`] = `
|
||||
{
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
createPasteEvent,
|
||||
fireEvent,
|
||||
GlobalTestState,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from "./test-utils";
|
||||
import { UI, Pointer } from "./helpers/ui";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { API } from "./helpers/api";
|
||||
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||
import { getElementAbsoluteCoords } from "../element";
|
||||
|
@ -13,6 +15,7 @@ import {
|
|||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FileId,
|
||||
} from "../element/types";
|
||||
import { newLinearElement } from "../element";
|
||||
|
@ -22,6 +25,8 @@ import { NormalizedZoomValue } from "../types";
|
|||
import { ROUNDNESS } from "../constants";
|
||||
import { vi } from "vitest";
|
||||
import * as blob from "../data/blob";
|
||||
import { KEYS } from "../keys";
|
||||
import { getBoundTextElementPosition } from "../element/textElement";
|
||||
|
||||
const { h } = window;
|
||||
const mouse = new Pointer("mouse");
|
||||
|
@ -812,3 +817,69 @@ describe("image", () => {
|
|||
expect(h.elements[0].angle).toBeCloseTo(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mutliple elements", () => {
|
||||
it("with bound text flip correctly", async () => {
|
||||
UI.clickTool("arrow");
|
||||
fireEvent.click(screen.getByTitle("Architect"));
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 180,
|
||||
height: 80,
|
||||
});
|
||||
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
let editor = document.querySelector<HTMLTextAreaElement>(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
)!;
|
||||
fireEvent.input(editor, { target: { value: "arrow" } });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
Keyboard.keyPress(KEYS.ESCAPE);
|
||||
|
||||
const rectangle = UI.createElement("rectangle", {
|
||||
x: 0,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
editor = document.querySelector<HTMLTextAreaElement>(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
)!;
|
||||
fireEvent.input(editor, { target: { value: "rect\ntext" } });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
Keyboard.keyPress(KEYS.ESCAPE);
|
||||
|
||||
mouse.select([arrow, rectangle]);
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
const arrowText = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
const arrowTextPos = getBoundTextElementPosition(arrow.get(), arrowText)!;
|
||||
const rectText = h.elements[3] as ExcalidrawTextElementWithContainer;
|
||||
|
||||
expect(arrow.x).toBeCloseTo(180);
|
||||
expect(arrow.y).toBeCloseTo(200);
|
||||
expect(arrow.points[1][0]).toBeCloseTo(-180);
|
||||
expect(arrow.points[1][1]).toBeCloseTo(-80);
|
||||
|
||||
expect(arrowTextPos.x - (arrow.x - arrow.width)).toBeCloseTo(
|
||||
arrow.x - (arrowTextPos.x + arrowText.width),
|
||||
);
|
||||
expect(arrowTextPos.y - (arrow.y - arrow.height)).toBeCloseTo(
|
||||
arrow.y - (arrowTextPos.y + arrowText.height),
|
||||
);
|
||||
|
||||
expect(rectangle.x).toBeCloseTo(80);
|
||||
expect(rectangle.y).toBeCloseTo(0);
|
||||
|
||||
expect(rectText.x - rectangle.x).toBeCloseTo(
|
||||
rectangle.x + rectangle.width - (rectText.x + rectText.width),
|
||||
);
|
||||
expect(rectText.y - rectangle.y).toBeCloseTo(
|
||||
rectangle.y + rectangle.height - (rectText.y + rectText.height),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ import path from "path";
|
|||
import { getMimeType } from "../../data/blob";
|
||||
import {
|
||||
newEmbeddableElement,
|
||||
newFrameElement,
|
||||
newFreeDrawElement,
|
||||
newImageElement,
|
||||
} from "../../element/newElement";
|
||||
|
@ -24,6 +25,7 @@ import { Point } from "../../types";
|
|||
import { getSelectedElements } from "../../scene/selection";
|
||||
import { isLinearElementType } from "../../element/typeChecks";
|
||||
import { Mutable } from "../../utility-types";
|
||||
import { assertNever } from "../../utils";
|
||||
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
|
||||
|
@ -244,6 +246,15 @@ export class API {
|
|||
scale: rest.scale || [1, 1],
|
||||
});
|
||||
break;
|
||||
case "frame":
|
||||
element = newFrameElement({ ...base, width, height });
|
||||
break;
|
||||
default:
|
||||
assertNever(
|
||||
type,
|
||||
`API.createElement: unimplemented element type ${type}}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (element.type === "arrow") {
|
||||
element.startBinding = rest.startBinding ?? null;
|
||||
|
|
|
@ -275,7 +275,7 @@ describe("Test Linear Elements", () => {
|
|||
// drag line from midpoint
|
||||
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(14);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(6);
|
||||
|
||||
expect(line.points.length).toEqual(3);
|
||||
expect(line.points).toMatchInlineSnapshot(`
|
||||
|
@ -418,7 +418,7 @@ describe("Test Linear Elements", () => {
|
|||
]);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(11);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
|
||||
expect(line.points.length).toEqual(5);
|
||||
|
||||
|
@ -521,7 +521,7 @@ describe("Test Linear Elements", () => {
|
|||
deletePoint(points[2]);
|
||||
expect(line.points.length).toEqual(3);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(12);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
|
@ -568,7 +568,7 @@ describe("Test Linear Elements", () => {
|
|||
lastSegmentMidpoint[1] + delta,
|
||||
]);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(11);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(line.points.length).toEqual(5);
|
||||
|
||||
expect((h.elements[0] as ExcalidrawLinearElement).points)
|
||||
|
|
|
@ -308,7 +308,7 @@ describe("select single element on the scene", () => {
|
|||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(8);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
@ -338,7 +338,7 @@ describe("select single element on the scene", () => {
|
|||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(8);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
@ -368,7 +368,7 @@ describe("select single element on the scene", () => {
|
|||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(8);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
@ -411,7 +411,7 @@ describe("select single element on the scene", () => {
|
|||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(8);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
@ -453,7 +453,7 @@ describe("select single element on the scene", () => {
|
|||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(8);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
@ -477,3 +477,46 @@ describe("tool locking & selection", () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectedElementIds stability", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
});
|
||||
|
||||
it("box-selection should be stable when not changing selection", () => {
|
||||
const rectangle = API.createElement({
|
||||
type: "rectangle",
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
});
|
||||
|
||||
h.elements = [rectangle];
|
||||
|
||||
const selectedElementIds_1 = h.state.selectedElementIds;
|
||||
|
||||
mouse.downAt(-100, -100);
|
||||
mouse.moveTo(-50, -50);
|
||||
mouse.up();
|
||||
|
||||
expect(h.state.selectedElementIds).toBe(selectedElementIds_1);
|
||||
|
||||
mouse.downAt(-50, -50);
|
||||
mouse.moveTo(50, 50);
|
||||
|
||||
const selectedElementIds_2 = h.state.selectedElementIds;
|
||||
|
||||
expect(selectedElementIds_2).toEqual({ [rectangle.id]: true });
|
||||
|
||||
mouse.moveTo(60, 60);
|
||||
|
||||
// box-selecting further without changing selection should keep
|
||||
// selectedElementIds stable (the same object)
|
||||
expect(h.state.selectedElementIds).toBe(selectedElementIds_2);
|
||||
|
||||
mouse.up();
|
||||
|
||||
expect(h.state.selectedElementIds).toBe(selectedElementIds_2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -512,8 +512,9 @@ export type AppProps = Merge<
|
|||
* in the app, eg Manager. Factored out into a separate type to keep DRY. */
|
||||
export type AppClassProperties = {
|
||||
props: AppProps;
|
||||
canvas: HTMLCanvasElement;
|
||||
interactiveCanvas: HTMLCanvasElement | null;
|
||||
/** static canvas */
|
||||
canvas: HTMLCanvasElement;
|
||||
focusContainer(): void;
|
||||
library: Library;
|
||||
imageCache: Map<
|
||||
|
|
3
src/vite-env.d.ts
vendored
3
src/vite-env.d.ts
vendored
|
@ -47,6 +47,9 @@ interface ImportMetaEnv {
|
|||
VITE_PKG_VERSION: string;
|
||||
VITE_IS_EXCALIDRAW_NPM_PACKAGE: string;
|
||||
|
||||
VITE_APP_PLUS_LP: string;
|
||||
VITE_APP_PLUS_APP: string;
|
||||
|
||||
VITE_WORKER_ID: string;
|
||||
MODE: string;
|
||||
DEV: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue