Merge branch 'master' of github.com:excalidraw/excalidraw into arnost/scroll-in-read-only-links

This commit is contained in:
Arnošt Pleskot
2023-09-20 15:38:30 +02:00
91 changed files with 2428 additions and 369 deletions
+1
View File
@@ -60,6 +60,7 @@ const flipSelectedElements = (
getNonDeletedElements(elements),
appState,
{
includeBoundTextElement: true,
includeElementsInFrames: true,
},
);
+2 -2
View File
@@ -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,
+43 -17
View File
@@ -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";
@@ -871,7 +872,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 && (
@@ -1095,9 +1098,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)}
@@ -1139,6 +1142,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}
@@ -4066,7 +4079,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] =
@@ -4883,7 +4896,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,
@@ -5407,7 +5424,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);
@@ -5453,7 +5474,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,
@@ -5536,7 +5561,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({
@@ -5629,7 +5654,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({
@@ -5687,7 +5712,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({
@@ -5770,7 +5795,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
@@ -5816,6 +5841,7 @@ class App extends React.Component<AppProps, AppState> {
this.state.selectedLinearElement,
pointerCoords,
this.state,
!event[KEYS.CTRL_OR_CMD],
);
if (!ret) {
return;
@@ -5941,7 +5967,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] = [
@@ -6008,7 +6034,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),
@@ -6623,7 +6649,7 @@ class App extends React.Component<AppProps, AppState> {
}
nextElements = updateFrameMembershipOfSelectedElements(
this.scene.getElementsIncludingDeleted(),
nextElements,
this.state,
this,
);
@@ -7801,7 +7827,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 =
@@ -7870,7 +7896,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<
+8 -1
View File
@@ -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,
},
)
}
+13 -3
View File
@@ -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
View 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);
}
}
}
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -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);
+24 -20
View File
@@ -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) => {
+1 -1
View File
@@ -7,7 +7,7 @@
}
.FixedSideContainer > * {
pointer-events: all;
pointer-events: var(--ui-pointerEvents);
}
.FixedSideContainer_side_top {
+20 -11
View File
@@ -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");
}
}
+28 -10
View File
@@ -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 -8
View File
@@ -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 &&
+2
View File
@@ -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;
+4 -4
View File
@@ -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;
}
}
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -26,7 +26,7 @@
}
.UserList > * {
pointer-events: all;
pointer-events: var(--ui-pointerEvents);
}
.UserList_mobile {
+1 -1
View File
@@ -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" }}>
+2 -1
View File
@@ -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;
}
+3 -5
View File
@@ -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 {
+8
View File
@@ -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",
+9 -18
View File
@@ -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 {
+5
View File
@@ -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;
}
}
+9 -8
View File
@@ -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!),
+1
View File
@@ -121,6 +121,7 @@ export const isBindableElement = (
element.type === "ellipse" ||
element.type === "image" ||
element.type === "embeddable" ||
element.type === "frame" ||
(element.type === "text" && !element.containerId))
);
};
+6 -2
View File
@@ -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
+3 -2
View File
@@ -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);
+368 -56
View File
@@ -1,9 +1,10 @@
import { ExcalidrawElement } from "./element/types";
import {
convertToExcalidrawElements,
Excalidraw,
} from "./packages/excalidraw/index";
import { API } from "./tests/helpers/api";
import { Pointer } from "./tests/helpers/ui";
import { Keyboard, Pointer } from "./tests/helpers/ui";
import { render } from "./tests/test-utils";
const { h } = window;
@@ -28,83 +29,301 @@ describe("adding elements to frames", () => {
}, []);
};
describe("resizing frame over elements", () => {
const testElements = async (
containerType: "arrow" | "rectangle",
initialOrder: ElementType[],
expectedOrder: ElementType[],
) => {
await render(<Excalidraw />);
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();
}
const frame = API.createElement({ type: "frame", x: 0, y: 0 });
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();
}
h.elements = reorderElements(
[
frame,
...convertToExcalidrawElements([
{
type: containerType,
x: 100,
y: 100,
height: 10,
label: { text: "xx" },
},
]),
],
initialOrder,
);
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,
];
assertOrder(h.elements, initialOrder);
expect(h.elements[1].frameId).toBe(null);
expect(h.elements[2].frameId).toBe(null);
const container = h.elements[1];
mouse.clickAt(0, 0);
mouse.downAt(frame.x + frame.width, frame.y + frame.height);
mouse.moveTo(
container.x + container.width + 100,
container.y + container.height + 100,
);
Keyboard.withModifierKeys({ alt: true }, () => {
mouse.downAt(x, y);
mouse.moveTo(moveTo[0], moveTo[1]);
mouse.up();
assertOrder(h.elements, expectedOrder);
});
}
expect(h.elements[0].frameId).toBe(frame.id);
expect(h.elements[1].frameId).toBe(frame.id);
};
function expectEqualIds(expected: ExcalidrawElement[]) {
expect(h.elements.map((x) => x.id)).toEqual(expected.map((x) => x.id));
}
it("resizing over text containers / labelled arrows", async () => {
await testElements(
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 testElements(
await resizingTest(
"rectangle",
["frame", "text", "rectangle"],
["rectangle", "text", "frame"],
);
await testElements(
await resizingTest(
"rectangle",
["rectangle", "text", "frame"],
["rectangle", "text", "frame"],
);
await testElements(
await resizingTest(
"rectangle",
["text", "rectangle", "frame"],
["text", "rectangle", "frame"],
["rectangle", "text", "frame"],
);
await testElements(
await resizingTest(
"arrow",
["frame", "arrow", "text"],
["arrow", "text", "frame"],
);
await testElements(
await resizingTest(
"arrow",
["text", "arrow", "frame"],
["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
@@ -118,11 +337,104 @@ describe("adding elements to frames", () => {
// ["arrow", "text", "frame"],
// ["arrow", "text", "frame"],
// );
// await testElements(
// "arrow",
// ["frame", "text", "arrow"],
// ["text", "arrow", "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]);
});
});
});
+55 -22
View File
@@ -468,14 +468,39 @@ export const addElementsToFrame = (
}
}
let nextElements = allElements.slice();
const allElementsIndex = allElements.reduce(
(acc: Record<string, number>, element, index) => {
acc[element.id] = index;
return acc;
},
{},
);
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[] = [];
const frameBoundary = findIndex(nextElements, (e) => e.frameId === frame.id);
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,
{
@@ -483,28 +508,35 @@ export const addElementsToFrame = (
},
false,
);
const frameIndex = findIndex(nextElements, (e) => e.id === frame.id);
const elementIndex = findIndex(nextElements, (e) => e.id === element.id);
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;
};
@@ -518,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);
@@ -566,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,
+2 -1
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
[
{
"path": "dist/excalidraw.production.min.js",
"limit": "291 kB"
"limit": "305 kB"
},
{
"path": "dist/excalidraw-assets/locales",
+232 -21
View File
@@ -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 -1
View File
@@ -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": [
File diff suppressed because one or more lines are too long
+72 -1
View File
@@ -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),
);
});
});
+2 -1
View File
@@ -509,8 +509,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
View File
@@ -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;