mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax-maint-stage
This commit is contained in:
commit
62f5475c4a
91 changed files with 1575 additions and 604 deletions
|
@ -1,46 +0,0 @@
|
|||
import ReactDOM from "react-dom";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { reseed } from "../random";
|
||||
import { render, queryByTestId } from "../tests/test-utils";
|
||||
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { vi } from "vitest";
|
||||
|
||||
const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene");
|
||||
|
||||
describe("Test <App/>", () => {
|
||||
beforeEach(async () => {
|
||||
// Unmount ReactDOM from root
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||
localStorage.clear();
|
||||
renderStaticScene.mockClear();
|
||||
reseed(7);
|
||||
});
|
||||
|
||||
it("should show error modal when using brave and measureText API is not working", async () => {
|
||||
(global.navigator as any).brave = {
|
||||
isBrave: {
|
||||
name: "isBrave",
|
||||
},
|
||||
};
|
||||
|
||||
const originalContext = global.HTMLCanvasElement.prototype.getContext("2d");
|
||||
//@ts-ignore
|
||||
global.HTMLCanvasElement.prototype.getContext = (contextId) => {
|
||||
return {
|
||||
...originalContext,
|
||||
measureText: () => ({
|
||||
width: 0,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
await render(<ExcalidrawApp />);
|
||||
expect(
|
||||
queryByTestId(
|
||||
document.querySelector(".excalidraw-modal-container")!,
|
||||
"brave-measure-text-error",
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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";
|
||||
|
@ -886,7 +887,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 && (
|
||||
|
@ -1110,9 +1113,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)}
|
||||
|
@ -1154,6 +1157,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}
|
||||
|
@ -1346,7 +1359,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||
private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => {
|
||||
jotaiStore.set(activeEyeDropperAtom, {
|
||||
swapPreviewOnAlt: true,
|
||||
previewType: type === "stroke" ? "strokeColor" : "backgroundColor",
|
||||
colorPickerType:
|
||||
type === "stroke" ? "elementStroke" : "elementBackground",
|
||||
onSelect: (color, event) => {
|
||||
const shouldUpdateStrokeColor =
|
||||
(type === "background" && event.altKey) ||
|
||||
|
@ -1357,12 +1371,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.state.activeTool.type !== "selection"
|
||||
) {
|
||||
if (shouldUpdateStrokeColor) {
|
||||
this.setState({
|
||||
currentItemStrokeColor: color,
|
||||
this.syncActionResult({
|
||||
appState: { ...this.state, currentItemStrokeColor: color },
|
||||
commitToHistory: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
currentItemBackgroundColor: color,
|
||||
this.syncActionResult({
|
||||
appState: { ...this.state, currentItemBackgroundColor: color },
|
||||
commitToHistory: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -3975,7 +3991,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] =
|
||||
|
@ -4792,7 +4808,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,
|
||||
|
@ -5316,7 +5336,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);
|
||||
|
||||
|
@ -5362,7 +5386,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,
|
||||
|
@ -5446,7 +5474,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({
|
||||
|
@ -5540,7 +5568,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({
|
||||
|
@ -5599,7 +5627,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({
|
||||
|
@ -5682,7 +5710,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
|
||||
|
@ -5728,6 +5756,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.state.selectedLinearElement,
|
||||
pointerCoords,
|
||||
this.state,
|
||||
!event[KEYS.CTRL_OR_CMD],
|
||||
);
|
||||
if (!ret) {
|
||||
return;
|
||||
|
@ -5853,7 +5882,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] = [
|
||||
|
@ -5920,7 +5949,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),
|
||||
|
@ -7713,7 +7742,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 =
|
||||
|
@ -7782,7 +7811,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<
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getColor } from "./ColorPicker";
|
||||
import { useAtom } from "jotai";
|
||||
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
||||
import {
|
||||
ColorPickerType,
|
||||
activeColorPickerSectionAtom,
|
||||
} from "./colorPickerUtils";
|
||||
import { eyeDropperIcon } from "../icons";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { KEYS } from "../../keys";
|
||||
|
@ -15,14 +18,14 @@ interface ColorInputProps {
|
|||
color: string;
|
||||
onChange: (color: string) => void;
|
||||
label: string;
|
||||
eyeDropperType: "strokeColor" | "backgroundColor";
|
||||
colorPickerType: ColorPickerType;
|
||||
}
|
||||
|
||||
export const ColorInput = ({
|
||||
color,
|
||||
onChange,
|
||||
label,
|
||||
eyeDropperType,
|
||||
colorPickerType,
|
||||
}: ColorInputProps) => {
|
||||
const device = useDevice();
|
||||
const [innerValue, setInnerValue] = useState(color);
|
||||
|
@ -116,7 +119,7 @@ export const ColorInput = ({
|
|||
: {
|
||||
keepOpenOnAlt: false,
|
||||
onSelect: (color) => onChange(color),
|
||||
previewType: eyeDropperType,
|
||||
colorPickerType,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -82,14 +82,7 @@ const ColorPickerPopupContent = ({
|
|||
const { container } = useExcalidrawContainer();
|
||||
const { isMobile, isLandscape } = useDevice();
|
||||
|
||||
const eyeDropperType =
|
||||
type === "canvasBackground"
|
||||
? undefined
|
||||
: type === "elementBackground"
|
||||
? "backgroundColor"
|
||||
: "strokeColor";
|
||||
|
||||
const colorInputJSX = eyeDropperType && (
|
||||
const colorInputJSX = (
|
||||
<div>
|
||||
<PickerHeading>{t("colorPicker.hexCode")}</PickerHeading>
|
||||
<ColorInput
|
||||
|
@ -98,7 +91,7 @@ const ColorPickerPopupContent = ({
|
|||
onChange={(color) => {
|
||||
onChange(color);
|
||||
}}
|
||||
eyeDropperType={eyeDropperType}
|
||||
colorPickerType={type}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -160,7 +153,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 && eyeDropperType ? (
|
||||
{palette ? (
|
||||
<Picker
|
||||
palette={palette}
|
||||
color={color}
|
||||
|
@ -173,7 +166,7 @@ const ColorPickerPopupContent = ({
|
|||
state = state || {
|
||||
keepOpenOnAlt: true,
|
||||
onSelect: onChange,
|
||||
previewType: eyeDropperType,
|
||||
colorPickerType: type,
|
||||
};
|
||||
state.keepOpenOnAlt = true;
|
||||
return state;
|
||||
|
@ -184,7 +177,7 @@ const ColorPickerPopupContent = ({
|
|||
: {
|
||||
keepOpenOnAlt: false,
|
||||
onSelect: onChange,
|
||||
previewType: eyeDropperType,
|
||||
colorPickerType: type,
|
||||
};
|
||||
});
|
||||
}}
|
||||
|
|
|
@ -1,35 +1,47 @@
|
|||
import { atom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { rgbToHex } from "../colors";
|
||||
import { EVENT } from "../constants";
|
||||
import { useUIAppState } from "../context/ui-appState";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { useCreatePortalContainer } from "../hooks/useCreatePortalContainer";
|
||||
import { useOutsideClick } from "../hooks/useOutsideClick";
|
||||
import { KEYS } from "../keys";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import Scene from "../scene/Scene";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App";
|
||||
import { useStable } from "../hooks/useStable";
|
||||
|
||||
import "./EyeDropper.scss";
|
||||
import { ColorPickerType } from "./ColorPicker/colorPickerUtils";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
|
||||
type EyeDropperProperties = {
|
||||
export type EyeDropperProperties = {
|
||||
keepOpenOnAlt: boolean;
|
||||
swapPreviewOnAlt?: boolean;
|
||||
/** called when user picks color (on pointerup) */
|
||||
onSelect: (color: string, event: PointerEvent) => void;
|
||||
previewType: "strokeColor" | "backgroundColor";
|
||||
/**
|
||||
* property of selected elements to update live when alt-dragging.
|
||||
* Supply `null` if not applicable (e.g. updating the canvas bg instead of
|
||||
* elements)
|
||||
**/
|
||||
colorPickerType: ColorPickerType;
|
||||
};
|
||||
|
||||
export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null);
|
||||
|
||||
export const EyeDropper: React.FC<{
|
||||
onCancel: () => void;
|
||||
onSelect: Required<EyeDropperProperties>["onSelect"];
|
||||
swapPreviewOnAlt?: EyeDropperProperties["swapPreviewOnAlt"];
|
||||
previewType: EyeDropperProperties["previewType"];
|
||||
}> = ({ onCancel, onSelect, swapPreviewOnAlt, previewType }) => {
|
||||
onSelect: EyeDropperProperties["onSelect"];
|
||||
/** called when color changes, on pointerdown for preview */
|
||||
onChange: (
|
||||
type: ColorPickerType,
|
||||
color: string,
|
||||
selectedElements: ExcalidrawElement[],
|
||||
event: { altKey: boolean },
|
||||
) => void;
|
||||
colorPickerType: EyeDropperProperties["colorPickerType"];
|
||||
}> = ({ onCancel, onChange, onSelect, colorPickerType }) => {
|
||||
const eyeDropperContainer = useCreatePortalContainer({
|
||||
className: "excalidraw-eye-dropper-backdrop",
|
||||
parentSelector: ".excalidraw-eye-dropper-container",
|
||||
|
@ -40,9 +52,13 @@ export const EyeDropper: React.FC<{
|
|||
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
|
||||
const metaStuffRef = useRef({ selectedElements, app });
|
||||
metaStuffRef.current.selectedElements = selectedElements;
|
||||
metaStuffRef.current.app = app;
|
||||
const stableProps = useStable({
|
||||
app,
|
||||
onCancel,
|
||||
onChange,
|
||||
onSelect,
|
||||
selectedElements,
|
||||
});
|
||||
|
||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||
|
||||
|
@ -90,28 +106,28 @@ export const EyeDropper: React.FC<{
|
|||
const currentColor = getCurrentColor({ clientX, clientY });
|
||||
|
||||
if (isHoldingPointerDown) {
|
||||
for (const element of metaStuffRef.current.selectedElements) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
[altKey && swapPreviewOnAlt
|
||||
? previewType === "strokeColor"
|
||||
? "backgroundColor"
|
||||
: "strokeColor"
|
||||
: previewType]: currentColor,
|
||||
},
|
||||
false,
|
||||
);
|
||||
ShapeCache.delete(element);
|
||||
}
|
||||
Scene.getScene(
|
||||
metaStuffRef.current.selectedElements[0],
|
||||
)?.informMutation();
|
||||
stableProps.onChange(
|
||||
colorPickerType,
|
||||
currentColor,
|
||||
stableProps.selectedElements,
|
||||
{ altKey },
|
||||
);
|
||||
}
|
||||
|
||||
colorPreviewDiv.style.background = currentColor;
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
stableProps.onCancel();
|
||||
};
|
||||
|
||||
const onSelect: Required<EyeDropperProperties>["onSelect"] = (
|
||||
color,
|
||||
event,
|
||||
) => {
|
||||
stableProps.onSelect(color, event);
|
||||
};
|
||||
|
||||
const pointerDownListener = (event: PointerEvent) => {
|
||||
isHoldingPointerDown = true;
|
||||
// NOTE we can't event.preventDefault() as that would stop
|
||||
|
@ -148,8 +164,8 @@ export const EyeDropper: React.FC<{
|
|||
|
||||
// init color preview else it would show only after the first mouse move
|
||||
mouseMoveListener({
|
||||
clientX: metaStuffRef.current.app.lastViewportPosition.x,
|
||||
clientY: metaStuffRef.current.app.lastViewportPosition.y,
|
||||
clientX: stableProps.app.lastViewportPosition.x,
|
||||
clientY: stableProps.app.lastViewportPosition.y,
|
||||
altKey: false,
|
||||
});
|
||||
|
||||
|
@ -179,12 +195,10 @@ export const EyeDropper: React.FC<{
|
|||
window.removeEventListener(EVENT.BLUR, onCancel);
|
||||
};
|
||||
}, [
|
||||
stableProps,
|
||||
app.canvas,
|
||||
eyeDropperContainer,
|
||||
onCancel,
|
||||
onSelect,
|
||||
swapPreviewOnAlt,
|
||||
previewType,
|
||||
colorPickerType,
|
||||
excalidrawContainer,
|
||||
appState.offsetLeft,
|
||||
appState.offsetTop,
|
||||
|
|
|
@ -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";
|
||||
|
@ -52,6 +52,9 @@ import { EyeDropper, activeEyeDropperAtom } from "./EyeDropper";
|
|||
|
||||
import "./LayerUI.scss";
|
||||
import "./Toolbar.scss";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import Scene from "../scene/Scene";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
|
@ -368,11 +371,44 @@ const LayerUI = ({
|
|||
)}
|
||||
{eyeDropperState && !device.isMobile && (
|
||||
<EyeDropper
|
||||
swapPreviewOnAlt={eyeDropperState.swapPreviewOnAlt}
|
||||
previewType={eyeDropperState.previewType}
|
||||
colorPickerType={eyeDropperState.colorPickerType}
|
||||
onCancel={() => {
|
||||
setEyeDropperState(null);
|
||||
}}
|
||||
onChange={(colorPickerType, color, selectedElements, { altKey }) => {
|
||||
if (
|
||||
colorPickerType !== "elementBackground" &&
|
||||
colorPickerType !== "elementStroke"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedElements.length) {
|
||||
for (const element of selectedElements) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
[altKey && eyeDropperState.swapPreviewOnAlt
|
||||
? colorPickerType === "elementBackground"
|
||||
? "strokeColor"
|
||||
: "backgroundColor"
|
||||
: colorPickerType === "elementBackground"
|
||||
? "backgroundColor"
|
||||
: "strokeColor"]: color,
|
||||
},
|
||||
false,
|
||||
);
|
||||
ShapeCache.delete(element);
|
||||
}
|
||||
Scene.getScene(selectedElements[0])?.informMutation();
|
||||
} else if (colorPickerType === "elementBackground") {
|
||||
setAppState({
|
||||
currentItemBackgroundColor: color,
|
||||
});
|
||||
} else {
|
||||
setAppState({ currentItemStrokeColor: color });
|
||||
}
|
||||
}}
|
||||
onSelect={(color, event) => {
|
||||
setEyeDropperState((state) => {
|
||||
return state?.keepOpenOnAlt && event.altKey ? state : null;
|
||||
|
@ -427,13 +463,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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -161,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;
|
||||
|
@ -202,7 +202,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:not(:active) .welcome-screen-menu-item:hover {
|
||||
.welcome-screen-menu-item:hover {
|
||||
text-decoration: none;
|
||||
background: var(--color-gray-10);
|
||||
|
||||
|
@ -246,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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue