mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-21 13:14:25 +01:00
Compare commits
1 Commits
aakansha-f
...
image_back
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c83a322b6 |
18
package.json
18
package.json
@@ -21,12 +21,12 @@
|
||||
"dependencies": {
|
||||
"@sentry/browser": "6.2.5",
|
||||
"@sentry/integrations": "6.2.5",
|
||||
"@testing-library/jest-dom": "5.16.1",
|
||||
"@testing-library/jest-dom": "5.15.1",
|
||||
"@testing-library/react": "12.1.2",
|
||||
"@tldraw/vec": "1.4.0",
|
||||
"@tldraw/vec": "1.1.5",
|
||||
"@types/jest": "27.0.3",
|
||||
"@types/pica": "5.1.3",
|
||||
"@types/react": "17.0.38",
|
||||
"@types/react": "17.0.37",
|
||||
"@types/react-dom": "17.0.11",
|
||||
"@types/socket.io-client": "1.4.36",
|
||||
"browser-fs-access": "0.23.0",
|
||||
@@ -50,16 +50,16 @@
|
||||
"react-dom": "17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"roughjs": "4.5.2",
|
||||
"sass": "1.45.2",
|
||||
"sass": "1.43.5",
|
||||
"socket.io-client": "2.3.1",
|
||||
"typescript": "4.5.4"
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@excalidraw/eslint-config": "1.0.0",
|
||||
"@excalidraw/prettier-config": "1.0.2",
|
||||
"@types/chai": "4.3.0",
|
||||
"@types/chai": "4.2.22",
|
||||
"@types/lodash.throttle": "4.1.6",
|
||||
"@types/pako": "1.0.3",
|
||||
"@types/pako": "1.0.2",
|
||||
"@types/resize-observer-browser": "0.1.6",
|
||||
"chai": "4.3.4",
|
||||
"dotenv": "10.0.0",
|
||||
@@ -68,9 +68,9 @@
|
||||
"firebase-tools": "9.23.0",
|
||||
"husky": "7.0.4",
|
||||
"jest-canvas-mock": "2.3.1",
|
||||
"lint-staged": "12.1.4",
|
||||
"lint-staged": "12.1.2",
|
||||
"pepjs": "0.5.3",
|
||||
"prettier": "2.5.1",
|
||||
"prettier": "2.5.0",
|
||||
"rewire": "5.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -14,10 +14,60 @@ import {
|
||||
bindOrUnbindLinearElement,
|
||||
} from "../element/binding";
|
||||
import { isBindingElement } from "../element/typeChecks";
|
||||
import { ExcalidrawImageElement } from "../element/types";
|
||||
import { imageFromImageData } from "../element/image";
|
||||
|
||||
export const actionFinalize = register({
|
||||
name: "finalize",
|
||||
perform: (elements, appState, _, { canvas, focusContainer }) => {
|
||||
perform: (
|
||||
elements,
|
||||
appState,
|
||||
_,
|
||||
{ canvas, focusContainer, imageCache, addFiles },
|
||||
) => {
|
||||
if (appState.editingImageElement) {
|
||||
const { elementId, imageData } = appState.editingImageElement;
|
||||
const editingImageElement = elements.find((el) => el.id === elementId) as
|
||||
| ExcalidrawImageElement
|
||||
| undefined;
|
||||
if (editingImageElement?.fileId) {
|
||||
const cachedImageData = imageCache.get(editingImageElement.fileId);
|
||||
if (cachedImageData) {
|
||||
const { image, dataURL } = imageFromImageData(imageData);
|
||||
|
||||
imageCache.set(editingImageElement.fileId, {
|
||||
...cachedImageData,
|
||||
image,
|
||||
});
|
||||
|
||||
addFiles([
|
||||
{
|
||||
id: editingImageElement.fileId,
|
||||
dataURL,
|
||||
mimeType: cachedImageData.mimeType,
|
||||
created: Date.now(),
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (appState.editingLinearElement) {
|
||||
const { elementId, startBindingElement, endBindingElement } =
|
||||
appState.editingLinearElement;
|
||||
@@ -162,6 +212,7 @@ export const actionFinalize = register({
|
||||
keyTest: (event, appState) =>
|
||||
(event.key === KEYS.ESCAPE &&
|
||||
(appState.editingLinearElement !== null ||
|
||||
appState.editingImageElement !== null ||
|
||||
(!appState.draggingElement && appState.multiElement === null))) ||
|
||||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||
appState.multiElement !== null),
|
||||
|
||||
@@ -17,9 +17,8 @@ import {
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { randomId } from "../random";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
|
||||
const allElementsInSameGroup = (elements: readonly ExcalidrawElement[]) => {
|
||||
if (elements.length >= 2) {
|
||||
@@ -152,12 +151,7 @@ export const actionUngroup = register({
|
||||
if (groupIds.length === 0) {
|
||||
return { appState, elements, commitToHistory: false };
|
||||
}
|
||||
|
||||
const boundTextElementIds: ExcalidrawTextElement["id"][] = [];
|
||||
const nextElements = elements.map((element) => {
|
||||
if (isBoundToContainer(element)) {
|
||||
boundTextElementIds.push(element.id);
|
||||
}
|
||||
const nextGroupIds = removeFromSelectedGroups(
|
||||
element.groupIds,
|
||||
appState.selectedGroupIds,
|
||||
@@ -169,19 +163,11 @@ export const actionUngroup = register({
|
||||
groupIds: nextGroupIds,
|
||||
});
|
||||
});
|
||||
|
||||
const updateAppState = selectGroupsForSelectedElements(
|
||||
{ ...appState, selectedGroupIds: {} },
|
||||
getNonDeletedElements(nextElements),
|
||||
);
|
||||
|
||||
// remove binded text elements from selection
|
||||
boundTextElementIds.forEach(
|
||||
(id) => (updateAppState.selectedElementIds[id] = false),
|
||||
);
|
||||
return {
|
||||
appState: updateAppState,
|
||||
|
||||
appState: selectGroupsForSelectedElements(
|
||||
{ ...appState, selectedGroupIds: {} },
|
||||
getNonDeletedElements(nextElements),
|
||||
),
|
||||
elements: nextElements,
|
||||
commitToHistory: true,
|
||||
};
|
||||
|
||||
75
src/actions/actionImageEditing.tsx
Normal file
75
src/actions/actionImageEditing.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { backgroundIcon } from "../components/icons";
|
||||
import { register } from "./register";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { isInitializedImageElement } from "../element/typeChecks";
|
||||
import Scene from "../scene/Scene";
|
||||
|
||||
export const actionEditImageAlpha = register({
|
||||
name: "editImageAlpha",
|
||||
perform: async (elements, appState, _, app) => {
|
||||
if (appState.editingImageElement) {
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const selectedElement = selectedElements[0];
|
||||
if (
|
||||
selectedElements.length === 1 &&
|
||||
isInitializedImageElement(selectedElement)
|
||||
) {
|
||||
const imgData = app.imageCache.get(selectedElement.fileId);
|
||||
if (!imgData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const image = await imgData.image;
|
||||
const { width, height } = image;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.height = height;
|
||||
canvas.width = width;
|
||||
const context = canvas.getContext("2d")!;
|
||||
|
||||
context.drawImage(image, 0, 0, width, height);
|
||||
|
||||
const imageData = context.getImageData(0, 0, width, height);
|
||||
|
||||
Scene.mapElementToScene(selectedElement.id, app.scene);
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
editingImageElement: {
|
||||
editorType: "alpha",
|
||||
elementId: selectedElement.id,
|
||||
origImageData: imageData,
|
||||
imageData,
|
||||
pointerDownState: { screenX: 0, screenY: 0, sampledPixel: null },
|
||||
},
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={backgroundIcon}
|
||||
label="Edit Image Alpha"
|
||||
className={appState.editingImageElement ? "active" : ""}
|
||||
title={"Edit image alpha"}
|
||||
aria-label={"Edit image alpha"}
|
||||
onClick={() => updateData(null)}
|
||||
visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
|
||||
/>
|
||||
),
|
||||
});
|
||||
@@ -42,7 +42,6 @@ import {
|
||||
redrawTextBoundingBox,
|
||||
} from "../element";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { getBoundTextElement } from "../element/textElement";
|
||||
import { isLinearElement, isLinearElementType } from "../element/typeChecks";
|
||||
import {
|
||||
Arrowhead,
|
||||
@@ -58,27 +57,20 @@ import {
|
||||
canChangeSharpness,
|
||||
canHaveArrowheads,
|
||||
getCommonAttributeOfSelectedElements,
|
||||
getSelectedElements,
|
||||
getTargetElements,
|
||||
isSomeElementSelected,
|
||||
} from "../scene";
|
||||
import { hasStrokeColor } from "../scene/comparisons";
|
||||
import Scene from "../scene/Scene";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const changeProperty = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
callback: (element: ExcalidrawElement) => ExcalidrawElement,
|
||||
includeBoundText = false,
|
||||
) => {
|
||||
const selectedElementIds = arrayToMap(
|
||||
getSelectedElements(elements, appState, includeBoundText),
|
||||
);
|
||||
return elements.map((element) => {
|
||||
if (
|
||||
selectedElementIds.get(element.id) ||
|
||||
appState.selectedElementIds[element.id] ||
|
||||
element.id === appState.editingElement?.id
|
||||
) {
|
||||
return callback(element);
|
||||
@@ -434,26 +426,17 @@ export const actionChangeFontSize = register({
|
||||
name: "changeFontSize",
|
||||
perform: (elements, appState, value) => {
|
||||
return {
|
||||
elements: changeProperty(
|
||||
elements,
|
||||
appState,
|
||||
(el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
fontSize: value,
|
||||
});
|
||||
let container = null;
|
||||
if (el.containerId) {
|
||||
container = Scene.getScene(el)!.getElement(el.containerId);
|
||||
}
|
||||
redrawTextBoundingBox(element, container, appState);
|
||||
return element;
|
||||
}
|
||||
elements: changeProperty(elements, appState, (el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
fontSize: value,
|
||||
});
|
||||
redrawTextBoundingBox(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
return el;
|
||||
},
|
||||
true,
|
||||
),
|
||||
return el;
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
currentItemFontSize: value,
|
||||
@@ -491,16 +474,7 @@ export const actionChangeFontSize = register({
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => {
|
||||
if (isTextElement(element)) {
|
||||
return element.fontSize;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.fontSize;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) => isTextElement(element) && element.fontSize,
|
||||
appState.currentItemFontSize || DEFAULT_FONT_SIZE,
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
@@ -513,26 +487,17 @@ export const actionChangeFontFamily = register({
|
||||
name: "changeFontFamily",
|
||||
perform: (elements, appState, value) => {
|
||||
return {
|
||||
elements: changeProperty(
|
||||
elements,
|
||||
appState,
|
||||
(el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
fontFamily: value,
|
||||
});
|
||||
let container = null;
|
||||
if (el.containerId) {
|
||||
container = Scene.getScene(el)!.getElement(el.containerId);
|
||||
}
|
||||
redrawTextBoundingBox(element, container, appState);
|
||||
return element;
|
||||
}
|
||||
elements: changeProperty(elements, appState, (el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
fontFamily: value,
|
||||
});
|
||||
redrawTextBoundingBox(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
return el;
|
||||
},
|
||||
true,
|
||||
),
|
||||
return el;
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
currentItemFontFamily: value,
|
||||
@@ -572,16 +537,7 @@ export const actionChangeFontFamily = register({
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => {
|
||||
if (isTextElement(element)) {
|
||||
return element.fontFamily;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.fontFamily;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) => isTextElement(element) && element.fontFamily,
|
||||
appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
@@ -595,26 +551,17 @@ export const actionChangeTextAlign = register({
|
||||
name: "changeTextAlign",
|
||||
perform: (elements, appState, value) => {
|
||||
return {
|
||||
elements: changeProperty(
|
||||
elements,
|
||||
appState,
|
||||
(el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
textAlign: value,
|
||||
});
|
||||
let container = null;
|
||||
if (el.containerId) {
|
||||
container = Scene.getScene(el)!.getElement(el.containerId);
|
||||
}
|
||||
redrawTextBoundingBox(element, container, appState);
|
||||
return element;
|
||||
}
|
||||
elements: changeProperty(elements, appState, (el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
textAlign: value,
|
||||
});
|
||||
redrawTextBoundingBox(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
return el;
|
||||
},
|
||||
true,
|
||||
),
|
||||
return el;
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
currentItemTextAlign: value,
|
||||
@@ -647,16 +594,7 @@ export const actionChangeTextAlign = register({
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => {
|
||||
if (isTextElement(element)) {
|
||||
return element.textAlign;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.textAlign;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) => isTextElement(element) && element.textAlign,
|
||||
appState.currentItemTextAlign,
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
|
||||
@@ -12,9 +12,6 @@ import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
} from "../constants";
|
||||
import Scene from "../scene/Scene";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import { ExcalidrawTextElement } from "../element/types";
|
||||
|
||||
// `copiedStyles` is exported only for tests.
|
||||
export let copiedStyles: string = "{}";
|
||||
@@ -64,18 +61,7 @@ export const actionPasteStyles = register({
|
||||
fontFamily: pastedElement?.fontFamily || DEFAULT_FONT_FAMILY,
|
||||
textAlign: pastedElement?.textAlign || DEFAULT_TEXT_ALIGN,
|
||||
});
|
||||
let container = null;
|
||||
|
||||
if (isBoundToContainer(element)) {
|
||||
container = Scene.getScene(element)!.getElement(
|
||||
element.containerId,
|
||||
);
|
||||
}
|
||||
redrawTextBoundingBox(
|
||||
element as ExcalidrawTextElement,
|
||||
container,
|
||||
appState,
|
||||
);
|
||||
redrawTextBoundingBox(newElement);
|
||||
}
|
||||
return newElement;
|
||||
}
|
||||
|
||||
@@ -80,3 +80,4 @@ export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||
|
||||
export { actionToggleStats } from "./actionToggleStats";
|
||||
export { actionEditImageAlpha } from "./actionImageEditing";
|
||||
|
||||
@@ -101,7 +101,8 @@ export type ActionName =
|
||||
| "flipVertical"
|
||||
| "viewMode"
|
||||
| "exportWithDarkMode"
|
||||
| "toggleTheme";
|
||||
| "toggleTheme"
|
||||
| "editImageAlpha";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
||||
23
src/align.ts
23
src/align.ts
@@ -1,7 +1,6 @@
|
||||
import { ExcalidrawElement } from "./element/types";
|
||||
import { newElementWith } from "./element/mutateElement";
|
||||
import { Box, getCommonBoundingBox } from "./element/bounds";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
|
||||
export interface Alignment {
|
||||
position: "start" | "center" | "end";
|
||||
@@ -31,6 +30,28 @@ export const alignElements = (
|
||||
});
|
||||
};
|
||||
|
||||
export const getMaximumGroups = (
|
||||
elements: ExcalidrawElement[],
|
||||
): ExcalidrawElement[][] => {
|
||||
const groups: Map<String, ExcalidrawElement[]> = new Map<
|
||||
String,
|
||||
ExcalidrawElement[]
|
||||
>();
|
||||
|
||||
elements.forEach((element: ExcalidrawElement) => {
|
||||
const groupId =
|
||||
element.groupIds.length === 0
|
||||
? element.id
|
||||
: element.groupIds[element.groupIds.length - 1];
|
||||
|
||||
const currentGroupMembers = groups.get(groupId) || [];
|
||||
|
||||
groups.set(groupId, [...currentGroupMembers, element]);
|
||||
});
|
||||
|
||||
return Array.from(groups.values());
|
||||
};
|
||||
|
||||
const calculateTranslation = (
|
||||
group: ExcalidrawElement[],
|
||||
selectionBoundingBox: Box,
|
||||
|
||||
@@ -41,6 +41,7 @@ export const getDefaultAppState = (): Omit<
|
||||
editingElement: null,
|
||||
editingGroupId: null,
|
||||
editingLinearElement: null,
|
||||
editingImageElement: null,
|
||||
elementLocked: false,
|
||||
elementType: "selection",
|
||||
errorMessage: null,
|
||||
@@ -125,6 +126,7 @@ const APP_STATE_STORAGE_CONF = (<
|
||||
editingElement: { browser: false, export: false, server: false },
|
||||
editingGroupId: { browser: true, export: false, server: false },
|
||||
editingLinearElement: { browser: false, export: false, server: false },
|
||||
editingImageElement: { browser: false, export: false, server: false },
|
||||
elementLocked: { browser: true, export: false, server: false },
|
||||
elementType: { browser: true, export: false, server: false },
|
||||
errorMessage: { browser: false, export: false, server: false },
|
||||
|
||||
@@ -19,6 +19,7 @@ import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
|
||||
import Stack from "./Stack";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { hasStrokeColor } from "../scene/comparisons";
|
||||
import { isImageElement } from "../element/typeChecks";
|
||||
|
||||
export const SelectedShapeActions = ({
|
||||
appState,
|
||||
@@ -105,6 +106,13 @@ export const SelectedShapeActions = ({
|
||||
<>{renderAction("changeArrowhead")}</>
|
||||
)}
|
||||
|
||||
<fieldset>
|
||||
<div className="buttonList">
|
||||
{targetElements.some((element) => isImageElement(element)) &&
|
||||
renderAction("editImageAlpha")}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{renderAction("changeOpacity")}
|
||||
|
||||
<fieldset>
|
||||
|
||||
@@ -123,7 +123,6 @@ import {
|
||||
hasBoundTextElement,
|
||||
isBindingElement,
|
||||
isBindingElementType,
|
||||
isBoundToContainer,
|
||||
isImageElement,
|
||||
isInitializedImageElement,
|
||||
isLinearElement,
|
||||
@@ -238,6 +237,7 @@ import {
|
||||
getBoundTextElementId,
|
||||
} from "../element/textElement";
|
||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||
import { ImageEditor } from "../element/imageEditor";
|
||||
|
||||
const IsMobileContext = React.createContext(false);
|
||||
export const useIsMobile = () => useContext(IsMobileContext);
|
||||
@@ -282,7 +282,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
UIOptions: DEFAULT_UI_OPTIONS,
|
||||
};
|
||||
|
||||
private scene: Scene;
|
||||
public scene: Scene;
|
||||
private resizeObserver: ResizeObserver | undefined;
|
||||
private nearestScrollableContainer: HTMLElement | Document | undefined;
|
||||
public library: AppClassProperties["library"];
|
||||
@@ -918,16 +918,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
window.removeEventListener(EVENT.RESIZE, this.onResize, false);
|
||||
window.removeEventListener(EVENT.UNLOAD, this.onUnload, false);
|
||||
window.removeEventListener(EVENT.BLUR, this.onBlur, false);
|
||||
this.excalidrawContainerRef.current?.removeEventListener(
|
||||
EVENT.DRAG_OVER,
|
||||
this.disableEvent,
|
||||
false,
|
||||
);
|
||||
this.excalidrawContainerRef.current?.removeEventListener(
|
||||
EVENT.DROP,
|
||||
this.disableEvent,
|
||||
false,
|
||||
);
|
||||
window.removeEventListener(EVENT.DRAG_OVER, this.disableEvent, false);
|
||||
window.removeEventListener(EVENT.DROP, this.disableEvent, false);
|
||||
|
||||
document.removeEventListener(
|
||||
EVENT.GESTURE_START,
|
||||
@@ -996,16 +988,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
||||
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
||||
window.addEventListener(EVENT.BLUR, this.onBlur, false);
|
||||
this.excalidrawContainerRef.current?.addEventListener(
|
||||
EVENT.DRAG_OVER,
|
||||
this.disableEvent,
|
||||
false,
|
||||
);
|
||||
this.excalidrawContainerRef.current?.addEventListener(
|
||||
EVENT.DROP,
|
||||
this.disableEvent,
|
||||
false,
|
||||
);
|
||||
window.addEventListener(EVENT.DRAG_OVER, this.disableEvent, false);
|
||||
window.addEventListener(EVENT.DROP, this.disableEvent, false);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||
@@ -1048,8 +1032,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
|
||||
if (
|
||||
this.state.editingLinearElement &&
|
||||
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]
|
||||
(this.state.editingLinearElement &&
|
||||
!this.state.selectedElementIds[
|
||||
this.state.editingLinearElement.elementId
|
||||
]) ||
|
||||
(this.state.editingImageElement &&
|
||||
!this.state.selectedElementIds[
|
||||
this.state.editingImageElement.elementId
|
||||
])
|
||||
) {
|
||||
// defer so that the commitToHistory flag isn't reset via current update
|
||||
setTimeout(() => {
|
||||
@@ -1152,6 +1142,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
imageCache: this.imageCache,
|
||||
isExporting: false,
|
||||
renderScrollbars: !this.isMobile,
|
||||
editingImageElement: this.state.editingImageElement,
|
||||
},
|
||||
);
|
||||
if (scrollBars) {
|
||||
@@ -1420,7 +1411,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
...this.state,
|
||||
isLibraryOpen: false,
|
||||
selectedElementIds: newElements.reduce((map, element) => {
|
||||
if (!isBoundToContainer(element)) {
|
||||
if (isTextElement(element) && !element.containerId) {
|
||||
map[element.id] = true;
|
||||
}
|
||||
return map;
|
||||
@@ -1680,11 +1671,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||
: ELEMENT_TRANSLATE_AMOUNT);
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getElements(),
|
||||
this.state,
|
||||
true,
|
||||
);
|
||||
const selectedElements = this.scene
|
||||
.getElements()
|
||||
.filter((element) => this.state.selectedElementIds[element.id]);
|
||||
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
@@ -1765,7 +1754,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
|
||||
isHoldingSpace = true;
|
||||
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.key === KEYS.G || event.key === KEYS.S) {
|
||||
@@ -2086,7 +2074,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
/** whether to attempt to insert at element center if applicable */
|
||||
insertAtParentCenter?: boolean;
|
||||
}) => {
|
||||
let parentCenterPosition =
|
||||
const parentCenterPosition =
|
||||
insertAtParentCenter &&
|
||||
this.getTextWysiwygSnappedToCenterPosition(
|
||||
sceneX,
|
||||
@@ -2101,9 +2089,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const container =
|
||||
shouldBind || parentCenterPosition
|
||||
? getElementContainingPosition(
|
||||
this.scene.getElements().filter((ele) => !isTextElement(ele)),
|
||||
this.scene.getElements(),
|
||||
sceneX,
|
||||
sceneY,
|
||||
"text",
|
||||
)
|
||||
: null;
|
||||
|
||||
@@ -2130,15 +2119,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
mutateElement(container, { height: newHeight, width: newWidth });
|
||||
sceneX = container.x + newWidth / 2;
|
||||
sceneY = container.y + newHeight / 2;
|
||||
if (parentCenterPosition) {
|
||||
parentCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
||||
sceneX,
|
||||
sceneY,
|
||||
this.state,
|
||||
this.canvas,
|
||||
window.devicePixelRatio,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const element = existingTextElement
|
||||
@@ -2168,7 +2148,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
? "middle"
|
||||
: DEFAULT_VERTICAL_ALIGN,
|
||||
containerId: container?.id ?? undefined,
|
||||
groupIds: container?.groupIds ?? [],
|
||||
});
|
||||
|
||||
this.setState({ editingElement: element });
|
||||
@@ -2359,6 +2338,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const scenePointer = viewportCoordsToSceneCoords(event, this.state);
|
||||
const { x: scenePointerX, y: scenePointerY } = scenePointer;
|
||||
|
||||
if (this.state.editingImageElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.state.editingLinearElement &&
|
||||
!this.state.editingLinearElement.isDragging
|
||||
@@ -2732,7 +2715,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
return false;
|
||||
}
|
||||
isPanning = true;
|
||||
event.preventDefault();
|
||||
|
||||
let nextPastePrevented = false;
|
||||
const isLinux = /Linux/.test(window.navigator.platform);
|
||||
@@ -2950,6 +2932,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState: PointerDownState,
|
||||
): boolean => {
|
||||
if (this.state.elementType === "selection") {
|
||||
if (this.state.editingImageElement) {
|
||||
ImageEditor.handlePointerDown(
|
||||
this.state.editingImageElement,
|
||||
pointerDownState.origin,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const elements = this.scene.getElements();
|
||||
const selectedElements = getSelectedElements(elements, this.state);
|
||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||
@@ -3510,6 +3500,22 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.editingImageElement) {
|
||||
const newImageData = ImageEditor.handlePointerMove(
|
||||
this.state.editingImageElement,
|
||||
pointerCoords,
|
||||
);
|
||||
if (newImageData) {
|
||||
this.setState({
|
||||
editingImageElement: {
|
||||
...this.state.editingImageElement,
|
||||
imageData: newImageData,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.editingLinearElement) {
|
||||
const didDrag = LinearElementEditor.handlePointDragging(
|
||||
this.state,
|
||||
@@ -3579,7 +3585,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
lockDirection,
|
||||
dragDistanceX,
|
||||
dragDistanceY,
|
||||
this.state,
|
||||
);
|
||||
this.maybeSuggestBindingForAll(selectedElements);
|
||||
|
||||
@@ -3833,6 +3838,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
||||
|
||||
if (this.state.editingImageElement) {
|
||||
ImageEditor.handlePointerUp(this.state.editingImageElement);
|
||||
}
|
||||
|
||||
// Handle end of dragging a point of a linear element, might close a loop
|
||||
// and sets binding element
|
||||
if (this.state.editingLinearElement) {
|
||||
|
||||
@@ -260,18 +260,6 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
label={t("labels.multiSelect")}
|
||||
shortcuts={[getShortcutKey(`Shift+${t("helpDialog.click")}`)]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.deepSelect")}
|
||||
shortcuts={[
|
||||
getShortcutKey(`CtrlOrCmd+${t("helpDialog.click")}`),
|
||||
]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.deepBoxSelect")}
|
||||
shortcuts={[
|
||||
getShortcutKey(`CtrlOrCmd+${t("helpDialog.drag")}`),
|
||||
]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("labels.moveCanvas")}
|
||||
shortcuts={[
|
||||
|
||||
@@ -61,27 +61,6 @@ const getHints = ({ appState, elements, isMobile }: HintViewerProps) => {
|
||||
return t("hints.rotate");
|
||||
}
|
||||
|
||||
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
||||
return t("hints.text_selected");
|
||||
}
|
||||
|
||||
if (appState.editingElement && isTextElement(appState.editingElement)) {
|
||||
return t("hints.text_editing");
|
||||
}
|
||||
|
||||
if (elementType === "selection") {
|
||||
if (
|
||||
appState.draggingElement?.type === "selection" &&
|
||||
!appState.editingElement &&
|
||||
!appState.editingLinearElement
|
||||
) {
|
||||
return t("hints.deepBoxSelect");
|
||||
}
|
||||
if (!selectedElements.length && !isMobile) {
|
||||
return t("hints.canvasPanning");
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedElements.length === 1) {
|
||||
if (isLinearElement(selectedElements[0])) {
|
||||
if (appState.editingLinearElement) {
|
||||
@@ -96,6 +75,18 @@ const getHints = ({ appState, elements, isMobile }: HintViewerProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
||||
return t("hints.text_selected");
|
||||
}
|
||||
|
||||
if (appState.editingElement && isTextElement(appState.editingElement)) {
|
||||
return t("hints.text_editing");
|
||||
}
|
||||
|
||||
if (elementType === "selection" && !selectedElements.length && !isMobile) {
|
||||
return t("hints.canvasPanning");
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
|
||||
.library-unit__dragger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,14 @@ export const trash = createIcon(
|
||||
|
||||
{ width: 448, height: 512 },
|
||||
);
|
||||
export const backgroundIcon = createIcon(
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M512 320s-64 92.65-64 128c0 35.35 28.66 64 64 64s64-28.65 64-64-64-128-64-128zm-9.37-102.94L294.94 9.37C288.69 3.12 280.5 0 272.31 0s-16.38 3.12-22.62 9.37l-81.58 81.58L81.93 4.76c-6.25-6.25-16.38-6.25-22.62 0L36.69 27.38c-6.24 6.25-6.24 16.38 0 22.62l86.19 86.18-94.76 94.76c-37.49 37.48-37.49 98.26 0 135.75l117.19 117.19c18.74 18.74 43.31 28.12 67.87 28.12 24.57 0 49.13-9.37 67.87-28.12l221.57-221.57c12.5-12.5 12.5-32.75.01-45.25zm-116.22 70.97H65.93c1.36-3.84 3.57-7.98 7.43-11.83l13.15-13.15 81.61-81.61 58.6 58.6c12.49 12.49 32.75 12.49 45.24 0s12.49-32.75 0-45.24l-58.6-58.6 58.95-58.95 162.44 162.44-48.34 48.34z"
|
||||
></path>,
|
||||
|
||||
{ width: 576, height: 512 },
|
||||
);
|
||||
|
||||
export const palette = createIcon(
|
||||
"M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z",
|
||||
|
||||
@@ -182,4 +182,4 @@ export const VERSIONS = {
|
||||
excalidrawLibrary: 2,
|
||||
} as const;
|
||||
|
||||
export const BOUND_TEXT_PADDING = 5;
|
||||
export const PADDING = 30;
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { ExcalidrawElement } from "./element/types";
|
||||
import { newElementWith } from "./element/mutateElement";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
import { getCommonBoundingBox } from "./element/bounds";
|
||||
import { getCommonBounds } from "./element";
|
||||
|
||||
interface Box {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Distribution {
|
||||
space: "between";
|
||||
@@ -88,3 +98,39 @@ export const distributeElements = (
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const getMaximumGroups = (
|
||||
elements: ExcalidrawElement[],
|
||||
): ExcalidrawElement[][] => {
|
||||
const groups: Map<String, ExcalidrawElement[]> = new Map<
|
||||
String,
|
||||
ExcalidrawElement[]
|
||||
>();
|
||||
|
||||
elements.forEach((element: ExcalidrawElement) => {
|
||||
const groupId =
|
||||
element.groupIds.length === 0
|
||||
? element.id
|
||||
: element.groupIds[element.groupIds.length - 1];
|
||||
|
||||
const currentGroupMembers = groups.get(groupId) || [];
|
||||
|
||||
groups.set(groupId, [...currentGroupMembers, element]);
|
||||
});
|
||||
|
||||
return Array.from(groups.values());
|
||||
};
|
||||
|
||||
const getCommonBoundingBox = (elements: ExcalidrawElement[]): Box => {
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
return {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
midX: (minX + maxX) / 2,
|
||||
midY: (minY + maxY) / 2,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -520,24 +520,11 @@ export interface Box {
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const getCommonBoundingBox = (
|
||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
||||
): Box => {
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
return {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
midX: (minX + maxX) / 2,
|
||||
midY: (minY + maxY) / 2,
|
||||
};
|
||||
return { minX, minY, maxX, maxY };
|
||||
};
|
||||
|
||||
@@ -46,7 +46,8 @@ const isElementDraggableFromInside = (
|
||||
return true;
|
||||
}
|
||||
const isDraggableFromInside =
|
||||
!isTransparent(element.backgroundColor) || hasBoundTextElement(element);
|
||||
!isTransparent(element.backgroundColor) ||
|
||||
(isTransparent(element.backgroundColor) && hasBoundTextElement(element));
|
||||
if (element.type === "line") {
|
||||
return isDraggableFromInside && isPathALoop(element.points);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ import { mutateElement } from "./mutateElement";
|
||||
import { getPerfectElementSize } from "./sizeHelpers";
|
||||
import Scene from "../scene/Scene";
|
||||
import { NonDeletedExcalidrawElement } from "./types";
|
||||
import { AppState, PointerDownState } from "../types";
|
||||
import { PointerDownState } from "../types";
|
||||
import { getBoundTextElementId } from "./textElement";
|
||||
import { isSelectedViaGroup } from "../groups";
|
||||
|
||||
export const dragSelectedElements = (
|
||||
pointerDownState: PointerDownState,
|
||||
@@ -17,7 +16,6 @@ export const dragSelectedElements = (
|
||||
lockDirection: boolean = false,
|
||||
distanceX: number = 0,
|
||||
distanceY: number = 0,
|
||||
appState: AppState,
|
||||
) => {
|
||||
const [x1, y1] = getCommonBounds(selectedElements);
|
||||
const offset = { x: pointerX - x1, y: pointerY - y1 };
|
||||
@@ -30,15 +28,7 @@ export const dragSelectedElements = (
|
||||
element,
|
||||
offset,
|
||||
);
|
||||
// update coords of bound text only if we're dragging the container directly
|
||||
// (we don't drag the group that it's part of)
|
||||
if (
|
||||
// container isn't part of any group
|
||||
// (perf optim so we don't check `isSelectedViaGroup()` in every case)
|
||||
!element.groupIds.length ||
|
||||
// container is part of a group, but we're dragging the container directly
|
||||
(appState.editingGroupId && !isSelectedViaGroup(appState, element))
|
||||
) {
|
||||
if (!element.groupIds.length) {
|
||||
const boundTextElementId = getBoundTextElementId(element);
|
||||
if (boundTextElementId) {
|
||||
const textElement =
|
||||
|
||||
@@ -109,3 +109,16 @@ export const normalizeSVG = async (SVGString: string) => {
|
||||
return svg.outerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
export const imageFromImageData = (imagedata: ImageData) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
canvas.width = imagedata.width;
|
||||
canvas.height = imagedata.height;
|
||||
ctx.putImageData(imagedata, 0, 0);
|
||||
|
||||
const image = new Image();
|
||||
const dataURL = canvas.toDataURL() as DataURL;
|
||||
image.src = dataURL;
|
||||
return { image, dataURL };
|
||||
};
|
||||
|
||||
112
src/element/imageEditor.ts
Normal file
112
src/element/imageEditor.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { distance2d } from "../math";
|
||||
import Scene from "../scene/Scene";
|
||||
import {
|
||||
ExcalidrawImageElement,
|
||||
InitializedExcalidrawImageElement,
|
||||
} from "./types";
|
||||
|
||||
export type EditingImageElement = {
|
||||
editorType: "alpha";
|
||||
elementId: ExcalidrawImageElement["id"];
|
||||
origImageData: Readonly<ImageData>;
|
||||
imageData: ImageData;
|
||||
pointerDownState: {
|
||||
screenX: number;
|
||||
screenY: number;
|
||||
sampledPixel: readonly [number, number, number, number] | null;
|
||||
};
|
||||
};
|
||||
|
||||
const getElement = (id: EditingImageElement["elementId"]) => {
|
||||
const element = Scene.getScene(id)?.getNonDeletedElement(id);
|
||||
if (element) {
|
||||
return element as InitializedExcalidrawImageElement;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export class ImageEditor {
|
||||
static handlePointerDown(
|
||||
editingElement: EditingImageElement,
|
||||
scenePointer: { x: number; y: number },
|
||||
) {
|
||||
const imageElement = getElement(editingElement.elementId);
|
||||
|
||||
if (imageElement) {
|
||||
if (
|
||||
scenePointer.x >= imageElement.x &&
|
||||
scenePointer.x <= imageElement.x + imageElement.width &&
|
||||
scenePointer.y >= imageElement.y &&
|
||||
scenePointer.y <= imageElement.y + imageElement.height
|
||||
) {
|
||||
editingElement.pointerDownState.screenX = scenePointer.x;
|
||||
editingElement.pointerDownState.screenY = scenePointer.y;
|
||||
|
||||
const { width, height, data } = editingElement.origImageData;
|
||||
|
||||
const imageOffsetX = Math.round(
|
||||
(scenePointer.x - imageElement.x) * (width / imageElement.width),
|
||||
);
|
||||
const imageOffsetY = Math.round(
|
||||
(scenePointer.y - imageElement.y) * (height / imageElement.height),
|
||||
);
|
||||
|
||||
const sampledPixel = [
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 0],
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 1],
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 2],
|
||||
data[(imageOffsetY * width + imageOffsetX) * 4 + 3],
|
||||
] as const;
|
||||
|
||||
editingElement.pointerDownState.sampledPixel = sampledPixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static handlePointerMove(
|
||||
editingElement: EditingImageElement,
|
||||
scenePointer: { x: number; y: number },
|
||||
) {
|
||||
const { sampledPixel } = editingElement.pointerDownState;
|
||||
if (sampledPixel) {
|
||||
const { screenX, screenY } = editingElement.pointerDownState;
|
||||
const distance = distance2d(
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
screenX,
|
||||
screenY,
|
||||
);
|
||||
|
||||
const { width, height, data } = editingElement.origImageData;
|
||||
const newImageData = new ImageData(width, height);
|
||||
|
||||
for (let x = 0; x < width; ++x) {
|
||||
for (let y = 0; y < height; ++y) {
|
||||
if (
|
||||
Math.abs(sampledPixel[0] - data[(y * width + x) * 4 + 0]) +
|
||||
Math.abs(sampledPixel[1] - data[(y * width + x) * 4 + 1]) +
|
||||
Math.abs(sampledPixel[2] - data[(y * width + x) * 4 + 2]) <
|
||||
distance
|
||||
) {
|
||||
newImageData.data[(y * width + x) * 4 + 0] = 0;
|
||||
newImageData.data[(y * width + x) * 4 + 1] = 255;
|
||||
newImageData.data[(y * width + x) * 4 + 2] = 0;
|
||||
newImageData.data[(y * width + x) * 4 + 3] = 0;
|
||||
} else {
|
||||
for (let p = 0; p < 4; ++p) {
|
||||
newImageData.data[(y * width + x) * 4 + p] =
|
||||
data[(y * width + x) * 4 + p];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newImageData;
|
||||
}
|
||||
}
|
||||
|
||||
static handlePointerUp(editingElement: EditingImageElement) {
|
||||
editingElement.pointerDownState.sampledPixel = null;
|
||||
editingElement.origImageData = editingElement.imageData;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
FontFamilyValues,
|
||||
ExcalidrawRectangleElement,
|
||||
} from "../element/types";
|
||||
import { getFontString, getUpdatedTimestamp, isTestEnv } from "../utils";
|
||||
import { getFontString, getUpdatedTimestamp } from "../utils";
|
||||
import { randomInteger, randomId } from "../random";
|
||||
import { mutateElement, newElementWith } from "./mutateElement";
|
||||
import { getNewGroupIdsForDuplication } from "../groups";
|
||||
@@ -24,7 +24,7 @@ import { getResizedElementAbsoluteCoords } from "./bounds";
|
||||
import { measureText } from "./textElement";
|
||||
import { isBoundToContainer } from "./typeChecks";
|
||||
import Scene from "../scene/Scene";
|
||||
import { BOUND_TEXT_PADDING } from "../constants";
|
||||
import { PADDING } from "../constants";
|
||||
|
||||
type ElementConstructorOpts = MarkOptional<
|
||||
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
||||
@@ -219,11 +219,11 @@ const getAdjustedDimensions = (
|
||||
const container = Scene.getScene(element)!.getElement(element.containerId)!;
|
||||
let height = container.height;
|
||||
let width = container.width;
|
||||
if (nextHeight > height - BOUND_TEXT_PADDING * 2) {
|
||||
height = nextHeight + BOUND_TEXT_PADDING * 2;
|
||||
if (nextHeight > height - PADDING * 2) {
|
||||
height = nextHeight + PADDING * 2;
|
||||
}
|
||||
if (nextWidth > width - BOUND_TEXT_PADDING * 2) {
|
||||
width = nextWidth + BOUND_TEXT_PADDING * 2;
|
||||
if (nextWidth > width - PADDING * 2) {
|
||||
width = nextWidth + PADDING * 2;
|
||||
}
|
||||
if (height !== container.height || width !== container.width) {
|
||||
mutateElement(container, { height, width });
|
||||
@@ -369,7 +369,7 @@ export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
overrides?: Partial<TElement>,
|
||||
): TElement => {
|
||||
let copy: TElement = deepCopyElement(element);
|
||||
if (isTestEnv()) {
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
copy.id = `${copy.id}_copy`;
|
||||
// `window.h` may not be defined in some unit tests
|
||||
if (
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import { wrapText } from "./textElement";
|
||||
import { FontString } from "./types";
|
||||
|
||||
describe("Test wrapText", () => {
|
||||
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
|
||||
|
||||
describe("When text doesn't contain new lines", () => {
|
||||
const text = "Hello whats up";
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
width: 90,
|
||||
res: `Hello
|
||||
whats
|
||||
up`,
|
||||
},
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
|
||||
width: 150,
|
||||
res: `Hello whats
|
||||
up`,
|
||||
},
|
||||
{
|
||||
desc: "fit the container",
|
||||
|
||||
width: 250,
|
||||
res: "Hello whats up",
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When text contain new lines", () => {
|
||||
const text = `Hello
|
||||
whats up`;
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
width: 90,
|
||||
res: `Hello
|
||||
whats
|
||||
up`,
|
||||
},
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
|
||||
width: 150,
|
||||
res: `Hello
|
||||
whats up`,
|
||||
},
|
||||
{
|
||||
desc: "fit the container",
|
||||
|
||||
width: 250,
|
||||
res: `Hello
|
||||
whats up`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should respect new lines and ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When text is long", () => {
|
||||
const text = `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg break it now`;
|
||||
[
|
||||
{
|
||||
desc: "fit characters of long string as per container width",
|
||||
width: 170,
|
||||
res: `hellolongtextth
|
||||
isiswhatsupwith
|
||||
youIamtypingggg
|
||||
gandtypinggg
|
||||
break it now`,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "fit characters of long string as per container width and break words as per the width",
|
||||
|
||||
width: 130,
|
||||
res: `hellolongte
|
||||
xtthisiswha
|
||||
tsupwithyou
|
||||
Iamtypinggg
|
||||
ggandtyping
|
||||
gg break it
|
||||
now`,
|
||||
},
|
||||
{
|
||||
desc: "fit the long text when container width is greater than text length and move the rest to next line",
|
||||
|
||||
width: 600,
|
||||
res: `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg
|
||||
break it now`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,34 +1,20 @@
|
||||
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
||||
import { getFontString, arrayToMap } from "../utils";
|
||||
import {
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FontString,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./types";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { BOUND_TEXT_PADDING } from "../constants";
|
||||
import { PADDING } from "../constants";
|
||||
import { MaybeTransformHandleType } from "./transformHandles";
|
||||
import Scene from "../scene/Scene";
|
||||
import { AppState } from "../types";
|
||||
|
||||
export const redrawTextBoundingBox = (
|
||||
element: ExcalidrawTextElement,
|
||||
container: ExcalidrawElement | null,
|
||||
appState: AppState,
|
||||
) => {
|
||||
const maxWidth = container
|
||||
? container.width - BOUND_TEXT_PADDING * 2
|
||||
: undefined;
|
||||
let text = element.text;
|
||||
|
||||
if (container) {
|
||||
text = wrapText(
|
||||
element.originalText,
|
||||
getFontString(element),
|
||||
container.width,
|
||||
);
|
||||
export const redrawTextBoundingBox = (element: ExcalidrawTextElement) => {
|
||||
let maxWidth;
|
||||
if (element.containerId) {
|
||||
maxWidth = element.width;
|
||||
}
|
||||
const metrics = measureText(
|
||||
element.originalText,
|
||||
@@ -36,24 +22,10 @@ export const redrawTextBoundingBox = (
|
||||
maxWidth,
|
||||
);
|
||||
|
||||
let coordY = element.y;
|
||||
// Resize container and vertically center align the text
|
||||
if (container) {
|
||||
coordY = container.y + container.height / 2 - metrics.height / 2;
|
||||
let nextHeight = container.height;
|
||||
if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) {
|
||||
nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
|
||||
coordY = container.y + nextHeight / 2 - metrics.height / 2;
|
||||
}
|
||||
mutateElement(container, { height: nextHeight });
|
||||
}
|
||||
|
||||
mutateElement(element, {
|
||||
width: metrics.width,
|
||||
height: metrics.height,
|
||||
baseline: metrics.baseline,
|
||||
y: coordY,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -110,12 +82,20 @@ export const handleBindTextResize = (
|
||||
let containerHeight = element.height;
|
||||
let nextBaseLine = textElement.baseline;
|
||||
if (transformHandleType !== "n" && transformHandleType !== "s") {
|
||||
let minCharWidthTillNow = 0;
|
||||
if (text) {
|
||||
text = wrapText(
|
||||
textElement.originalText,
|
||||
getFontString(textElement),
|
||||
element.width,
|
||||
minCharWidthTillNow = getMinCharWidth(getFontString(textElement));
|
||||
// check if the diff has exceeded min char width needed
|
||||
const diff = Math.abs(
|
||||
element.width - textElement.width + PADDING * 2,
|
||||
);
|
||||
if (diff >= minCharWidthTillNow) {
|
||||
text = wrapText(
|
||||
textElement.originalText,
|
||||
getFontString(textElement),
|
||||
element.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const dimensions = measureText(
|
||||
@@ -127,8 +107,8 @@ export const handleBindTextResize = (
|
||||
nextBaseLine = dimensions.baseline;
|
||||
}
|
||||
// increase height in case text element height exceeds
|
||||
if (nextHeight > element.height - BOUND_TEXT_PADDING * 2) {
|
||||
containerHeight = nextHeight + BOUND_TEXT_PADDING * 2;
|
||||
if (nextHeight > element.height - PADDING * 2) {
|
||||
containerHeight = nextHeight + PADDING * 2;
|
||||
const diff = containerHeight - element.height;
|
||||
// fix the y coord when resizing from ne/nw/n
|
||||
const updatedY =
|
||||
@@ -147,9 +127,9 @@ export const handleBindTextResize = (
|
||||
mutateElement(textElement, {
|
||||
text,
|
||||
// preserve padding and set width correctly
|
||||
width: element.width - BOUND_TEXT_PADDING * 2,
|
||||
width: element.width - PADDING * 2,
|
||||
height: nextHeight,
|
||||
x: element.x + BOUND_TEXT_PADDING,
|
||||
x: element.x + PADDING,
|
||||
y: updatedY,
|
||||
baseline: nextBaseLine,
|
||||
});
|
||||
@@ -218,12 +198,6 @@ const getTextWidth = (text: string, font: FontString) => {
|
||||
canvas2dContext.font = font;
|
||||
|
||||
const metrics = canvas2dContext.measureText(text);
|
||||
// since in test env the canvas measureText algo
|
||||
// doesn't measure text and instead just returns number of
|
||||
// characters hence we assume that each letteris 10px
|
||||
if (isTestEnv()) {
|
||||
return metrics.width * 10;
|
||||
}
|
||||
|
||||
return metrics.width;
|
||||
};
|
||||
@@ -233,7 +207,7 @@ export const wrapText = (
|
||||
font: FontString,
|
||||
containerWidth: number,
|
||||
) => {
|
||||
const maxWidth = containerWidth - BOUND_TEXT_PADDING * 2;
|
||||
const maxWidth = containerWidth - PADDING * 2;
|
||||
|
||||
const lines: Array<string> = [];
|
||||
const originalLines = text.split("\n");
|
||||
@@ -252,7 +226,7 @@ export const wrapText = (
|
||||
const currentWordWidth = getTextWidth(words[index], font);
|
||||
|
||||
// Start breaking longer words exceeding max width
|
||||
if (currentWordWidth >= maxWidth) {
|
||||
if (currentWordWidth > maxWidth) {
|
||||
// push current line since the current word exceeds the max width
|
||||
// so will be appended in next line
|
||||
if (currentLine) {
|
||||
@@ -283,7 +257,7 @@ export const wrapText = (
|
||||
}
|
||||
}
|
||||
// push current line if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
if (currentLineWidthTillNow + spaceWidth > maxWidth) {
|
||||
lines.push(currentLine);
|
||||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
@@ -311,15 +285,8 @@ export const wrapText = (
|
||||
}
|
||||
index++;
|
||||
currentLine += `${word} `;
|
||||
|
||||
// Push the word if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
lines.push(currentLine.slice(0, -1));
|
||||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLineWidthTillNow === maxWidth) {
|
||||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
@@ -353,23 +320,34 @@ export const charWidth = (() => {
|
||||
return cachedCharWidth[font][ascii];
|
||||
};
|
||||
|
||||
const updateCache = (char: string, font: FontString) => {
|
||||
const ascii = char.charCodeAt(0);
|
||||
|
||||
if (!cachedCharWidth[font][ascii]) {
|
||||
cachedCharWidth[font][ascii] = calculate(char, font);
|
||||
}
|
||||
};
|
||||
|
||||
const clearCacheforFont = (font: FontString) => {
|
||||
cachedCharWidth[font] = [];
|
||||
};
|
||||
|
||||
const getCache = (font: FontString) => {
|
||||
return cachedCharWidth[font];
|
||||
};
|
||||
return {
|
||||
calculate,
|
||||
updateCache,
|
||||
clearCacheforFont,
|
||||
getCache,
|
||||
};
|
||||
})();
|
||||
export const getApproxMinLineWidth = (font: FontString) => {
|
||||
return (
|
||||
measureText(DUMMY_TEXT.split("").join("\n"), font).width +
|
||||
BOUND_TEXT_PADDING * 2
|
||||
);
|
||||
return measureText(DUMMY_TEXT.split("").join("\n"), font).width + PADDING * 2;
|
||||
};
|
||||
|
||||
export const getApproxMinLineHeight = (font: FontString) => {
|
||||
return getApproxLineHeight(font) + BOUND_TEXT_PADDING * 2;
|
||||
return getApproxLineHeight(font) + PADDING * 2;
|
||||
};
|
||||
|
||||
export const getMinCharWidth = (font: FontString) => {
|
||||
@@ -409,16 +387,3 @@ export const getApproxCharsToFitInWidth = (font: FontString, width: number) => {
|
||||
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
|
||||
return container?.boundElements?.filter((ele) => ele.type === "text")[0]?.id;
|
||||
};
|
||||
|
||||
export const getBoundTextElement = (element: ExcalidrawElement | null) => {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
const boundTextElementId = getBoundTextElementId(element);
|
||||
if (boundTextElementId) {
|
||||
return Scene.getScene(element)!.getElement(
|
||||
boundTextElementId,
|
||||
) as ExcalidrawTextElementWithContainer;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -2,11 +2,12 @@ import { CODES, KEYS } from "../keys";
|
||||
import {
|
||||
isWritableElement,
|
||||
getFontString,
|
||||
viewportCoordsToSceneCoords,
|
||||
getFontFamilyString,
|
||||
} from "../utils";
|
||||
import Scene from "../scene/Scene";
|
||||
import { isBoundToContainer, isTextElement } from "./typeChecks";
|
||||
import { CLASSES, BOUND_TEXT_PADDING } from "../constants";
|
||||
import { CLASSES, PADDING } from "../constants";
|
||||
import {
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawElement,
|
||||
@@ -36,20 +37,16 @@ const getTransform = (
|
||||
angle: number,
|
||||
appState: AppState,
|
||||
maxWidth: number,
|
||||
maxHeight: number,
|
||||
) => {
|
||||
const { zoom, offsetTop, offsetLeft } = appState;
|
||||
const degree = (180 * angle) / Math.PI;
|
||||
// offsets must be multiplied by 2 to account for the division by 2 of
|
||||
// the whole expression afterwards
|
||||
let translateX = ((width - offsetLeft * 2) * (zoom.value - 1)) / 2;
|
||||
let translateY = ((height - offsetTop * 2) * (zoom.value - 1)) / 2;
|
||||
const translateY = ((height - offsetTop * 2) * (zoom.value - 1)) / 2;
|
||||
if (width > maxWidth && zoom.value !== 1) {
|
||||
translateX = (maxWidth / 2) * (zoom.value - 1);
|
||||
}
|
||||
if (height > maxHeight && zoom.value !== 1) {
|
||||
translateY = ((maxHeight - offsetTop * 2) * (zoom.value - 1)) / 2;
|
||||
}
|
||||
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`;
|
||||
};
|
||||
|
||||
@@ -102,68 +99,83 @@ export const textWysiwyg = ({
|
||||
if (updatedElement && isTextElement(updatedElement)) {
|
||||
let coordX = updatedElement.x;
|
||||
let coordY = updatedElement.y;
|
||||
const container = updatedElement?.containerId
|
||||
let container = updatedElement?.containerId
|
||||
? Scene.getScene(updatedElement)!.getElement(updatedElement.containerId)
|
||||
: null;
|
||||
let maxWidth = updatedElement.width;
|
||||
|
||||
let maxHeight = updatedElement.height;
|
||||
let width = updatedElement.width;
|
||||
// Set to element height by default since thats
|
||||
// what is going to be used for unbounded text
|
||||
let height = updatedElement.height;
|
||||
if (container && updatedElement.containerId) {
|
||||
const propertiesUpdated = textPropertiesUpdated(
|
||||
updatedElement,
|
||||
editable,
|
||||
);
|
||||
// using editor.style.height to get the accurate height of text editor
|
||||
const editorHeight = Number(editable.style.height.slice(0, -2));
|
||||
if (editorHeight > 0) {
|
||||
height = editorHeight;
|
||||
}
|
||||
if (propertiesUpdated) {
|
||||
const currentContainer = Scene.getScene(updatedElement)?.getElement(
|
||||
updatedElement.containerId,
|
||||
) as ExcalidrawBindableElement;
|
||||
approxLineHeight = isTextElement(updatedElement)
|
||||
? getApproxLineHeight(getFontString(updatedElement))
|
||||
: 0;
|
||||
|
||||
originalContainerHeight = container.height;
|
||||
|
||||
// update height of the editor after properties updated
|
||||
height = updatedElement.height;
|
||||
if (updatedElement.height > currentContainer.height - PADDING * 2) {
|
||||
const nextHeight = updatedElement.height + PADDING * 2;
|
||||
originalContainerHeight = nextHeight;
|
||||
mutateElement(container, { height: nextHeight });
|
||||
container = { ...container, height: nextHeight };
|
||||
}
|
||||
editable.style.height = `${updatedElement.height}px`;
|
||||
}
|
||||
if (!originalContainerHeight) {
|
||||
originalContainerHeight = container.height;
|
||||
}
|
||||
maxWidth = container.width - BOUND_TEXT_PADDING * 2;
|
||||
maxHeight = container.height - BOUND_TEXT_PADDING * 2;
|
||||
maxWidth = container.width - PADDING * 2;
|
||||
maxHeight = container.height - PADDING * 2;
|
||||
width = maxWidth;
|
||||
height = Math.min(height, maxHeight);
|
||||
// The coordinates of text box set a distance of
|
||||
// 30px to preserve padding
|
||||
coordX = container.x + BOUND_TEXT_PADDING;
|
||||
coordX = container.x + PADDING;
|
||||
|
||||
// autogrow container height if text exceeds
|
||||
if (height > maxHeight) {
|
||||
const diff = Math.min(height - maxHeight, approxLineHeight);
|
||||
if (editable.clientHeight > maxHeight) {
|
||||
const diff = Math.min(
|
||||
editable.clientHeight - maxHeight,
|
||||
approxLineHeight,
|
||||
);
|
||||
mutateElement(container, { height: container.height + diff });
|
||||
return;
|
||||
} else if (
|
||||
// autoshrink container height until original container height
|
||||
// is reached when text is removed
|
||||
container.height > originalContainerHeight &&
|
||||
height < maxHeight
|
||||
editable.clientHeight < maxHeight
|
||||
) {
|
||||
const diff = Math.min(maxHeight - height, approxLineHeight);
|
||||
const diff = Math.min(
|
||||
maxHeight - editable.clientHeight,
|
||||
approxLineHeight,
|
||||
);
|
||||
mutateElement(container, { height: container.height - diff });
|
||||
}
|
||||
// Start pushing text upward until a diff of 30px (padding)
|
||||
// is reached
|
||||
else {
|
||||
// vertically center align the text
|
||||
coordY = container.y + container.height / 2 - height / 2;
|
||||
const lines = editable.clientHeight / approxLineHeight;
|
||||
// For some reason the scrollHeight gets set to twice the lineHeight
|
||||
// when you start typing for first time and thus line count is 2
|
||||
// hence this check
|
||||
if (lines > 2 || propertiesUpdated) {
|
||||
// vertically center align the text
|
||||
coordY =
|
||||
container.y + container.height / 2 - editable.clientHeight / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
|
||||
const { textAlign } = updatedElement;
|
||||
const { textAlign, angle } = updatedElement;
|
||||
|
||||
editable.value = updatedElement.originalText || updatedElement.text;
|
||||
const lines = updatedElement.originalText.split("\n");
|
||||
const lineHeight = updatedElement.containerId
|
||||
@@ -180,36 +192,19 @@ export const textWysiwyg = ({
|
||||
).marginRight.slice(0, -2),
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure text editor height doesn't go beyond viewport
|
||||
const editorMaxHeight =
|
||||
(appState.height -
|
||||
viewportY -
|
||||
// There is a ~14px difference which keeps on increasing
|
||||
// with every zoom step when offset present hence I am subtracting it here
|
||||
// However this is not the best fix and breaks in
|
||||
// few scenarios
|
||||
(appState.offsetTop
|
||||
? ((appState.zoom.value * 100 - 100) / 10) * 14
|
||||
: 0)) /
|
||||
(appState.offsetTop + appState.height - viewportY) /
|
||||
appState.zoom.value;
|
||||
const angle = container ? container.angle : updatedElement.angle;
|
||||
Object.assign(editable.style, {
|
||||
font: getFontString(updatedElement),
|
||||
// must be defined *after* font ¯\_(ツ)_/¯
|
||||
lineHeight: `${lineHeight}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
height: `${Math.max(editable.clientHeight, updatedElement.height)}px`,
|
||||
left: `${viewportX}px`,
|
||||
top: `${viewportY}px`,
|
||||
transform: getTransform(
|
||||
width,
|
||||
height,
|
||||
angle,
|
||||
appState,
|
||||
maxWidth,
|
||||
editorMaxHeight,
|
||||
),
|
||||
transform: getTransform(width, height, angle, appState, maxWidth),
|
||||
textAlign,
|
||||
color: updatedElement.strokeColor,
|
||||
opacity: updatedElement.opacity / 100,
|
||||
@@ -259,37 +254,8 @@ export const textWysiwyg = ({
|
||||
|
||||
if (onChange) {
|
||||
editable.oninput = () => {
|
||||
// using scrollHeight here since we need to calculate
|
||||
// number of lines so cannot use editable.style.height
|
||||
// as that gets updated below
|
||||
const lines = editable.scrollHeight / approxLineHeight;
|
||||
// auto increase height only when lines > 1 so its
|
||||
// measured correctly and vertically alignes for
|
||||
// first line as well as setting height to "auto"
|
||||
// doubles the height as soon as user starts typing
|
||||
if (isBoundToContainer(element) && lines > 1) {
|
||||
let height = "auto";
|
||||
|
||||
if (lines === 2) {
|
||||
const container = Scene.getScene(element)!.getElement(
|
||||
element.containerId,
|
||||
);
|
||||
const actualLineCount = wrapText(
|
||||
editable.value,
|
||||
getFontString(element),
|
||||
container!.width,
|
||||
).split("\n").length;
|
||||
|
||||
// This is browser behaviour when setting height to "auto"
|
||||
// It sets the height needed for 2 lines even if actual
|
||||
// line count is 1 as mentioned above as well
|
||||
// hence reducing the height by half if actual line count is 1
|
||||
// so single line aligns vertically when deleting
|
||||
if (actualLineCount === 1) {
|
||||
height = `${editable.scrollHeight / 2}px`;
|
||||
}
|
||||
}
|
||||
editable.style.height = height;
|
||||
if (isBoundToContainer(element)) {
|
||||
editable.style.height = "auto";
|
||||
editable.style.height = `${editable.scrollHeight}px`;
|
||||
}
|
||||
onChange(normalizeText(editable.value));
|
||||
@@ -450,17 +416,20 @@ export const textWysiwyg = ({
|
||||
getFontString(updateElement),
|
||||
container.width,
|
||||
);
|
||||
const { x, y } = viewportCoordsToSceneCoords(
|
||||
{
|
||||
clientX: Number(editable.style.left.slice(0, -2)),
|
||||
clientY: Number(editable.style.top.slice(0, -2)),
|
||||
},
|
||||
appState,
|
||||
);
|
||||
if (isTextElement(updateElement) && updateElement.containerId) {
|
||||
const editorHeight = Number(editable.style.height.slice(0, -2));
|
||||
if (editable.value) {
|
||||
mutateElement(updateElement, {
|
||||
// vertically center align
|
||||
y: container.y + container.height / 2 - editorHeight / 2,
|
||||
height: editorHeight,
|
||||
y: y + appState.offsetTop,
|
||||
height: Number(editable.style.height.slice(0, -2)),
|
||||
width: Number(editable.style.width.slice(0, -2)),
|
||||
// preserve padding
|
||||
x: container.x + BOUND_TEXT_PADDING,
|
||||
angle: container.angle,
|
||||
x: x + appState.offsetLeft,
|
||||
});
|
||||
const boundTextElementId = getBoundTextElementId(container);
|
||||
if (!boundTextElementId || boundTextElementId !== element.id) {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
GroupId,
|
||||
ExcalidrawElement,
|
||||
NonDeleted,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
} from "./element/types";
|
||||
import { GroupId, ExcalidrawElement, NonDeleted } from "./element/types";
|
||||
import { AppState } from "./types";
|
||||
import { getSelectedElements } from "./scene";
|
||||
import { getBoundTextElementId } from "./element/textElement";
|
||||
import Scene from "./scene/Scene";
|
||||
|
||||
export const selectGroup = (
|
||||
groupId: GroupId,
|
||||
@@ -165,33 +158,3 @@ export const removeFromSelectedGroups = (
|
||||
groupIds: ExcalidrawElement["groupIds"],
|
||||
selectedGroupIds: { [groupId: string]: boolean },
|
||||
) => groupIds.filter((groupId) => !selectedGroupIds[groupId]);
|
||||
|
||||
export const getMaximumGroups = (
|
||||
elements: ExcalidrawElement[],
|
||||
): ExcalidrawElement[][] => {
|
||||
const groups: Map<String, ExcalidrawElement[]> = new Map<
|
||||
String,
|
||||
ExcalidrawElement[]
|
||||
>();
|
||||
|
||||
elements.forEach((element: ExcalidrawElement) => {
|
||||
const groupId =
|
||||
element.groupIds.length === 0
|
||||
? element.id
|
||||
: element.groupIds[element.groupIds.length - 1];
|
||||
|
||||
const currentGroupMembers = groups.get(groupId) || [];
|
||||
|
||||
// Include bounded text if present when grouping
|
||||
const boundTextElementId = getBoundTextElementId(element);
|
||||
if (boundTextElementId) {
|
||||
const textElement = Scene.getScene(element)!.getElement(
|
||||
boundTextElementId,
|
||||
) as ExcalidrawTextElementWithContainer;
|
||||
currentGroupMembers.push(textElement);
|
||||
}
|
||||
groups.set(groupId, [...currentGroupMembers, element]);
|
||||
});
|
||||
|
||||
return Array.from(groups.values());
|
||||
};
|
||||
|
||||
@@ -208,8 +208,7 @@
|
||||
"lineEditor_nothingSelected": "Select a point to edit (hold SHIFT to select multiple),\nor hold Alt and click to add new points",
|
||||
"placeImage": "Click to place the image, or click and drag to set its size manually",
|
||||
"publishLibrary": "Publish your own library",
|
||||
"bindTextToElement": "Press enter to add text",
|
||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging"
|
||||
"bindTextToElement": "Press enter to add text"
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Cannot show preview",
|
||||
@@ -256,8 +255,6 @@
|
||||
"helpDialog": {
|
||||
"blog": "Read our blog",
|
||||
"click": "click",
|
||||
"deepSelect": "Deep select",
|
||||
"deepBoxSelect": "Deep select within box, and prevent dragging",
|
||||
"curvedArrow": "Curved arrow",
|
||||
"curvedLine": "Curved line",
|
||||
"documentation": "Documentation",
|
||||
|
||||
@@ -64,15 +64,8 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
The `Appearance` type is now removed and renamed to `Theme` so `Theme` type needs to be used.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Panning the canvas using `mousewheel-drag` and `space-drag` now prevents the browser from scrolling the container/page [#4489](https://github.com/excalidraw/excalidraw/pull/4489).
|
||||
- Scope drag and drop events to Excalidraw container to prevent overriding host application drag and drop events.
|
||||
|
||||
### Build
|
||||
|
||||
- Added an example to test and develop the package [locally](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#Development) using `yarn start`
|
||||
|
||||
- Remove `file-loader` so font assets are not duplicated by webpack and use webpack asset modules for font generation [#4380](https://github.com/excalidraw/excalidraw/pull/4380)
|
||||
|
||||
- We're now compiling to `es2017` target. Notably, `async/await` is not compiled down to generators. [#4341](https://github.com/excalidraw/excalidraw/pull/4341)
|
||||
|
||||
@@ -1008,21 +1008,3 @@ Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
|
||||
## Need help?
|
||||
|
||||
Check out the existing [Q&A](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw). If you have any queries or need help, ask us [here](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw).
|
||||
|
||||
### Development
|
||||
|
||||
#### Install the dependencies
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
#### Start the server
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
[http://localhost:3001](http://localhost:3001) will open in your default browser.
|
||||
|
||||
The example is same as the [codesandbox example](https://ehlz3.csb.app/)
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
import InitialData from "./initialData";
|
||||
import Sidebar from "./sidebar/Sidebar";
|
||||
|
||||
import "./App.scss";
|
||||
import initialData from "./initialData";
|
||||
|
||||
// This is so that we use the bundled excalidraw.developement.js file instead
|
||||
// of the actual source code
|
||||
const { exportToCanvas, exportToSvg, exportToBlob } = window.Excalidraw;
|
||||
const Excalidraw = window.Excalidraw.default;
|
||||
|
||||
const renderTopRightUI = () => {
|
||||
return (
|
||||
<button onClick={() => alert("This is dummy top right UI")}>
|
||||
{" "}
|
||||
Click me{" "}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<button onClick={() => alert("This is dummy footer")}>
|
||||
{" "}
|
||||
custom footer{" "}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const excalidrawRef = useRef(null);
|
||||
|
||||
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
||||
const [zenModeEnabled, setZenModeEnabled] = useState(false);
|
||||
const [gridModeEnabled, setGridModeEnabled] = useState(false);
|
||||
const [blobUrl, setBlobUrl] = useState(null);
|
||||
const [canvasUrl, setCanvasUrl] = useState(null);
|
||||
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
||||
const [shouldAddWatermark, setShouldAddWatermark] = useState(false);
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
useEffect(() => {
|
||||
const onHashChange = () => {
|
||||
const hash = new URLSearchParams(window.location.hash.slice(1));
|
||||
const libraryUrl = hash.get("addLibrary");
|
||||
if (libraryUrl) {
|
||||
excalidrawRef.current.importLibrary(libraryUrl, hash.get("token"));
|
||||
}
|
||||
};
|
||||
window.addEventListener("hashchange", onHashChange, false);
|
||||
return () => {
|
||||
window.removeEventListener("hashchange", onHashChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateScene = () => {
|
||||
const sceneData = {
|
||||
elements: [
|
||||
{
|
||||
type: "rectangle",
|
||||
version: 141,
|
||||
versionNonce: 361174001,
|
||||
isDeleted: false,
|
||||
id: "oDVXy8D6rom3H1-LLH2-f",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
angle: 0,
|
||||
x: 100.50390625,
|
||||
y: 93.67578125,
|
||||
strokeColor: "#c92a2a",
|
||||
backgroundColor: "transparent",
|
||||
width: 186.47265625,
|
||||
height: 141.9765625,
|
||||
seed: 1968410350,
|
||||
groupIds: [],
|
||||
},
|
||||
],
|
||||
appState: {
|
||||
viewBackgroundColor: "#edf2ff",
|
||||
},
|
||||
};
|
||||
excalidrawRef.current.updateScene(sceneData);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h1> Excalidraw Example</h1>
|
||||
<Sidebar>
|
||||
<div className="button-wrapper">
|
||||
<button className="update-scene" onClick={updateScene}>
|
||||
Update Scene
|
||||
</button>
|
||||
<button
|
||||
className="reset-scene"
|
||||
onClick={() => {
|
||||
excalidrawRef.current.resetScene();
|
||||
}}
|
||||
>
|
||||
Reset Scene
|
||||
</button>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={viewModeEnabled}
|
||||
onChange={() => setViewModeEnabled(!viewModeEnabled)}
|
||||
/>
|
||||
View mode
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={zenModeEnabled}
|
||||
onChange={() => setZenModeEnabled(!zenModeEnabled)}
|
||||
/>
|
||||
Zen mode
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={gridModeEnabled}
|
||||
onChange={() => setGridModeEnabled(!gridModeEnabled)}
|
||||
/>
|
||||
Grid mode
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={theme === "dark"}
|
||||
onChange={() => {
|
||||
let newTheme = "light";
|
||||
if (theme === "light") {
|
||||
newTheme = "dark";
|
||||
}
|
||||
setTheme(newTheme);
|
||||
}}
|
||||
/>
|
||||
Switch to Dark Theme
|
||||
</label>
|
||||
</div>
|
||||
<div className="excalidraw-wrapper">
|
||||
<Excalidraw
|
||||
ref={excalidrawRef}
|
||||
initialData={InitialData}
|
||||
onChange={(elements, state) =>
|
||||
console.info("Elements :", elements, "State : ", state)
|
||||
}
|
||||
onPointerUpdate={(payload) => console.info(payload)}
|
||||
onCollabButtonClick={() =>
|
||||
window.alert("You clicked on collab button")
|
||||
}
|
||||
viewModeEnabled={viewModeEnabled}
|
||||
zenModeEnabled={zenModeEnabled}
|
||||
gridModeEnabled={gridModeEnabled}
|
||||
theme={theme}
|
||||
name="Custom name of drawing"
|
||||
UIOptions={{ canvasActions: { loadScene: false } }}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderFooter={renderFooter}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="export-wrapper button-wrapper">
|
||||
<label className="export-wrapper__checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={exportWithDarkMode}
|
||||
onChange={() => setExportWithDarkMode(!exportWithDarkMode)}
|
||||
/>
|
||||
Export with dark mode
|
||||
</label>
|
||||
<label className="export-wrapper__checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={shouldAddWatermark}
|
||||
onChange={() => setShouldAddWatermark(!shouldAddWatermark)}
|
||||
/>
|
||||
Add Watermark
|
||||
</label>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const svg = await exportToSvg({
|
||||
elements: excalidrawRef.current.getSceneElements(),
|
||||
appState: {
|
||||
...initialData.appState,
|
||||
exportWithDarkMode,
|
||||
shouldAddWatermark,
|
||||
width: 300,
|
||||
height: 100,
|
||||
},
|
||||
embedScene: true,
|
||||
});
|
||||
document.querySelector(".export-svg").innerHTML = svg.outerHTML;
|
||||
}}
|
||||
>
|
||||
Export to SVG
|
||||
</button>
|
||||
<div className="export export-svg"></div>
|
||||
|
||||
<button
|
||||
onClick={async () => {
|
||||
const blob = await exportToBlob({
|
||||
elements: excalidrawRef.current.getSceneElements(),
|
||||
mimeType: "image/png",
|
||||
appState: {
|
||||
...initialData.appState,
|
||||
exportWithDarkMode,
|
||||
shouldAddWatermark,
|
||||
},
|
||||
});
|
||||
setBlobUrl(window.URL.createObjectURL(blob));
|
||||
}}
|
||||
>
|
||||
Export to Blob
|
||||
</button>
|
||||
<div className="export export-blob">
|
||||
<img src={blobUrl} alt="" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
const canvas = exportToCanvas({
|
||||
elements: excalidrawRef.current.getSceneElements(),
|
||||
appState: {
|
||||
...initialData.appState,
|
||||
exportWithDarkMode,
|
||||
shouldAddWatermark,
|
||||
},
|
||||
});
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.font = "30px Virgil";
|
||||
ctx.strokeText("My custom text", 50, 60);
|
||||
setCanvasUrl(canvas.toDataURL());
|
||||
}}
|
||||
>
|
||||
Export to Canvas
|
||||
</button>
|
||||
<div className="export export-canvas">
|
||||
<img src={canvasUrl} alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-wrapper button {
|
||||
z-index: 1;
|
||||
height: 40px;
|
||||
max-width: 200px;
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.excalidraw .App-menu_top .buttonList {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.excalidraw-wrapper {
|
||||
height: 800px;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
:root[dir="ltr"]
|
||||
.excalidraw
|
||||
.layer-ui__wrapper
|
||||
.zen-mode-transition.App-menu_bottom--transition-left {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.excalidraw .panelColumn {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.export-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 50px;
|
||||
|
||||
&__checkbox {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>React App</title>
|
||||
<script>
|
||||
window.name = "codesandbox";
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="root"></div>
|
||||
<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
|
||||
|
||||
<!-- This is so that we use the bundled excalidraw.developement.js file instead
|
||||
of the actual source code -->
|
||||
<script src="./excalidraw.development.js"></script>
|
||||
|
||||
<script src="./bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
rootElement,
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import "./Sidebar.scss";
|
||||
export default function Sidebar(props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="mySidebar" className={`sidebar ${open ? "open" : ""}`}>
|
||||
<button className="closebtn" onClick={() => setOpen(false)}>
|
||||
x
|
||||
</button>
|
||||
<div className="sidebar-links">
|
||||
<button>Dummy Home</button>
|
||||
<button>Dummy About</button>{" "}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${open ? "sidebar-open" : ""}`}>
|
||||
<button
|
||||
className="openbtn"
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
Open Sidebar
|
||||
</button>
|
||||
{props.children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
.sidebar {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #111;
|
||||
overflow-x: hidden;
|
||||
transition: 0.5s;
|
||||
padding-top: 60px;
|
||||
|
||||
&.open {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
&-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
background: #faa2c1;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
padding: 8px 8px 8px 32px;
|
||||
text-decoration: none;
|
||||
font-size: 25px;
|
||||
color: #818181;
|
||||
display: block;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.sidebar .closebtn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 36px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.openbtn {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
background-color: #111;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
display: flex;
|
||||
margin-left: 50px;
|
||||
}
|
||||
.sidebar-open {
|
||||
margin-left: 300px;
|
||||
}
|
||||
@@ -45,13 +45,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.16.0",
|
||||
"@babel/plugin-transform-arrow-functions": "7.16.7",
|
||||
"@babel/plugin-transform-arrow-functions": "7.16.0",
|
||||
"@babel/plugin-transform-async-to-generator": "7.16.0",
|
||||
"@babel/plugin-transform-runtime": "7.16.4",
|
||||
"@babel/plugin-transform-typescript": "7.16.1",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/preset-typescript": "7.16.7",
|
||||
"@babel/preset-env": "7.16.4",
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@babel/preset-typescript": "7.16.0",
|
||||
"autoprefixer": "10.4.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
@@ -60,14 +60,12 @@
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"postcss-loader": "6.2.1",
|
||||
"sass-loader": "12.4.0",
|
||||
"terser-webpack-plugin": "5.3.0",
|
||||
"terser-webpack-plugin": "5.2.5",
|
||||
"ts-loader": "9.2.6",
|
||||
"typescript": "4.5.3",
|
||||
"webpack": "5.65.0",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
"webpack-cli": "4.9.1",
|
||||
"webpack-dev-server": "4.7.1",
|
||||
"webpack-merge": "5.8.0"
|
||||
"webpack-cli": "4.9.1"
|
||||
},
|
||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||
"homepage": "https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw",
|
||||
@@ -75,8 +73,7 @@
|
||||
"gen:types": "tsc --project ../../../tsconfig-types.json",
|
||||
"build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && yarn gen:types",
|
||||
"build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js",
|
||||
"pack": "yarn build:umd && yarn pack",
|
||||
"start": "webpack serve --config webpack.dev-server.config.js "
|
||||
"pack": "yarn build:umd && yarn pack"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "10.0.0"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
const path = require("path");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const devConfig = require("./webpack.dev.config");
|
||||
|
||||
const devServerConfig = {
|
||||
entry: {
|
||||
bundle: "./example/index.js",
|
||||
},
|
||||
// Server Configuration options
|
||||
devServer: {
|
||||
port: 3001,
|
||||
host: "localhost",
|
||||
hot: true,
|
||||
compress: true,
|
||||
static: {
|
||||
directory: path.join(__dirname, "example"),
|
||||
},
|
||||
client: {
|
||||
progress: true,
|
||||
logging: "info",
|
||||
overlay: true, //Shows a full-screen overlay in the browser when there are compiler errors or warnings.
|
||||
},
|
||||
open: ["./"],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = merge(devServerConfig, devConfig);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,13 +34,13 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.16.5",
|
||||
"@babel/core": "7.16.0",
|
||||
"@babel/plugin-transform-arrow-functions": "7.16.0",
|
||||
"@babel/plugin-transform-async-to-generator": "7.16.5",
|
||||
"@babel/plugin-transform-async-to-generator": "7.16.0",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/plugin-transform-typescript": "7.16.1",
|
||||
"@babel/preset-env": "7.16.4",
|
||||
"@babel/preset-typescript": "7.16.5",
|
||||
"@babel/preset-typescript": "7.16.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
"cross-env": "7.0.3",
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
|
||||
integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==
|
||||
|
||||
"@babel/core@7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c"
|
||||
integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==
|
||||
"@babel/core@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4"
|
||||
integrity sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.16.0"
|
||||
"@babel/generator" "^7.16.5"
|
||||
"@babel/helper-compilation-targets" "^7.16.3"
|
||||
"@babel/helper-module-transforms" "^7.16.5"
|
||||
"@babel/helpers" "^7.16.5"
|
||||
"@babel/parser" "^7.16.5"
|
||||
"@babel/generator" "^7.16.0"
|
||||
"@babel/helper-compilation-targets" "^7.16.0"
|
||||
"@babel/helper-module-transforms" "^7.16.0"
|
||||
"@babel/helpers" "^7.16.0"
|
||||
"@babel/parser" "^7.16.0"
|
||||
"@babel/template" "^7.16.0"
|
||||
"@babel/traverse" "^7.16.5"
|
||||
"@babel/traverse" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
convert-source-map "^1.7.0"
|
||||
debug "^4.1.0"
|
||||
@@ -35,10 +35,10 @@
|
||||
semver "^6.3.0"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf"
|
||||
integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==
|
||||
"@babel/generator@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2"
|
||||
integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.0"
|
||||
jsesc "^2.5.1"
|
||||
@@ -103,13 +103,6 @@
|
||||
resolve "^1.14.2"
|
||||
semver "^6.1.2"
|
||||
|
||||
"@babel/helper-environment-visitor@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8"
|
||||
integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-explode-assignable-expression@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778"
|
||||
@@ -147,25 +140,25 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
|
||||
integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
|
||||
"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3"
|
||||
integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-module-transforms@^7.16.0", "@babel/helper-module-transforms@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29"
|
||||
integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==
|
||||
"@babel/helper-module-transforms@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5"
|
||||
integrity sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==
|
||||
dependencies:
|
||||
"@babel/helper-environment-visitor" "^7.16.5"
|
||||
"@babel/helper-module-imports" "^7.16.0"
|
||||
"@babel/helper-replace-supers" "^7.16.0"
|
||||
"@babel/helper-simple-access" "^7.16.0"
|
||||
"@babel/helper-split-export-declaration" "^7.16.0"
|
||||
"@babel/helper-validator-identifier" "^7.15.7"
|
||||
"@babel/template" "^7.16.0"
|
||||
"@babel/traverse" "^7.16.5"
|
||||
"@babel/traverse" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-optimise-call-expression@^7.16.0":
|
||||
@@ -175,18 +168,27 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
|
||||
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
|
||||
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
|
||||
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
|
||||
|
||||
"@babel/helper-remap-async-to-generator@^7.16.4", "@babel/helper-remap-async-to-generator@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3"
|
||||
integrity sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw==
|
||||
"@babel/helper-remap-async-to-generator@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.0.tgz#d5aa3b086e13a5fe05238ff40c3a5a0c2dab3ead"
|
||||
integrity sha512-MLM1IOMe9aQBqMWxcRw8dcb9jlM86NIw7KA0Wri91Xkfied+dE0QuBFSBjMNvqzmS0OSIDsMNC24dBEkPUi7ew==
|
||||
dependencies:
|
||||
"@babel/helper-annotate-as-pure" "^7.16.0"
|
||||
"@babel/helper-wrap-function" "^7.16.5"
|
||||
"@babel/helper-wrap-function" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-remap-async-to-generator@^7.16.4":
|
||||
version "7.16.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.4.tgz#5d7902f61349ff6b963e07f06a389ce139fbfe6e"
|
||||
integrity sha512-vGERmmhR+s7eH5Y/cp8PCVzj4XEjerq8jooMfxFdA5xVtAk9Sh4AQsrWgiErUEBjtGrBtOFKDUcWQFW4/dFwMA==
|
||||
dependencies:
|
||||
"@babel/helper-annotate-as-pure" "^7.16.0"
|
||||
"@babel/helper-wrap-function" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helper-replace-supers@^7.16.0":
|
||||
@@ -225,33 +227,28 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
|
||||
integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
|
||||
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
|
||||
|
||||
"@babel/helper-validator-option@^7.14.5":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
|
||||
integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
|
||||
|
||||
"@babel/helper-wrap-function@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f"
|
||||
integrity sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA==
|
||||
"@babel/helper-wrap-function@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.0.tgz#b3cf318afce774dfe75b86767cd6d68f3482e57c"
|
||||
integrity sha512-VVMGzYY3vkWgCJML+qVLvGIam902mJW0FvT7Avj1zEe0Gn7D93aWdLblYARTxEw+6DhZmtzhBM2zv0ekE5zg1g==
|
||||
dependencies:
|
||||
"@babel/helper-function-name" "^7.16.0"
|
||||
"@babel/template" "^7.16.0"
|
||||
"@babel/traverse" "^7.16.5"
|
||||
"@babel/traverse" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/helpers@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd"
|
||||
integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==
|
||||
"@babel/helpers@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.0.tgz#875519c979c232f41adfbd43a3b0398c2e388183"
|
||||
integrity sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ==
|
||||
dependencies:
|
||||
"@babel/template" "^7.16.0"
|
||||
"@babel/traverse" "^7.16.5"
|
||||
"@babel/traverse" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/highlight@^7.16.0":
|
||||
@@ -263,10 +260,10 @@
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.16.0", "@babel/parser@^7.16.5":
|
||||
version "7.16.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
|
||||
integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==
|
||||
"@babel/parser@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.0.tgz#cf147d7ada0a3655e79bf4b08ee846f00a00a295"
|
||||
integrity sha512-TEHWXf0xxpi9wKVyBCmRcSSDjbJ/cl6LUdlbYUHEaNQUJGhreJbZrXT6sR4+fZLxVUJqNRB4KyOvjuy/D9009A==
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2":
|
||||
version "7.16.2"
|
||||
@@ -524,14 +521,14 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
|
||||
"@babel/plugin-transform-async-to-generator@7.16.5", "@babel/plugin-transform-async-to-generator@^7.16.0":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz#89c9b501e65bb14c4579a6ce9563f859de9b34e4"
|
||||
integrity sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w==
|
||||
"@babel/plugin-transform-async-to-generator@7.16.0", "@babel/plugin-transform-async-to-generator@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.0.tgz#df12637f9630ddfa0ef9d7a11bc414d629d38604"
|
||||
integrity sha512-PbIr7G9kR8tdH6g8Wouir5uVjklETk91GMVSUq+VaOgiinbCkBP6Q7NN/suM/QutZkMJMvcyAriogcYAdhg8Gw==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.16.0"
|
||||
"@babel/helper-plugin-utils" "^7.16.5"
|
||||
"@babel/helper-remap-async-to-generator" "^7.16.5"
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
"@babel/helper-remap-async-to-generator" "^7.16.0"
|
||||
|
||||
"@babel/plugin-transform-block-scoped-functions@^7.16.0":
|
||||
version "7.16.0"
|
||||
@@ -715,12 +712,12 @@
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
|
||||
"@babel/plugin-transform-runtime@^7.14.5":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.7.tgz#1da184cb83a2287a01956c10c60e66dd503c18aa"
|
||||
integrity sha512-2FoHiSAWkdq4L06uaDN3rS43i6x28desUVxq+zAFuE6kbWYQeiLPJI5IC7Sg9xKYVcrBKSQkVUfH6aeQYbl9QA==
|
||||
version "7.16.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.4.tgz#f9ba3c7034d429c581e1bd41b4952f3db3c2c7e8"
|
||||
integrity sha512-pru6+yHANMTukMtEZGC4fs7XPwg35v8sj5CIEmE+gEkFljFiVJxEWxx/7ZDkTK+iZRYo1bFXBtfIN95+K3cJ5A==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.16.7"
|
||||
"@babel/helper-plugin-utils" "^7.16.7"
|
||||
"@babel/helper-module-imports" "^7.16.0"
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
babel-plugin-polyfill-corejs2 "^0.3.0"
|
||||
babel-plugin-polyfill-corejs3 "^0.4.0"
|
||||
babel-plugin-polyfill-regenerator "^0.3.0"
|
||||
@@ -762,7 +759,7 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
|
||||
"@babel/plugin-transform-typescript@7.16.1", "@babel/plugin-transform-typescript@^7.16.1":
|
||||
"@babel/plugin-transform-typescript@7.16.1", "@babel/plugin-transform-typescript@^7.16.0":
|
||||
version "7.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz#cc0670b2822b0338355bc1b3d2246a42b8166409"
|
||||
integrity sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==
|
||||
@@ -877,14 +874,14 @@
|
||||
"@babel/types" "^7.4.4"
|
||||
esutils "^2.0.2"
|
||||
|
||||
"@babel/preset-typescript@7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz#b86a5b0ae739ba741347d2f58c52f52e63cf1ba1"
|
||||
integrity sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q==
|
||||
"@babel/preset-typescript@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.0.tgz#b0b4f105b855fb3d631ec036cdc9d1ffd1fa5eac"
|
||||
integrity sha512-txegdrZYgO9DlPbv+9QOVpMnKbOtezsLHWsnsRF4AjbSIsVaujrq1qg8HK0mxQpWv0jnejt0yEoW1uWpvbrDTg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.16.5"
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
"@babel/helper-validator-option" "^7.14.5"
|
||||
"@babel/plugin-transform-typescript" "^7.16.1"
|
||||
"@babel/plugin-transform-typescript" "^7.16.0"
|
||||
|
||||
"@babel/runtime@^7.8.4":
|
||||
version "7.12.13"
|
||||
@@ -902,18 +899,17 @@
|
||||
"@babel/parser" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
|
||||
"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.5":
|
||||
version "7.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3"
|
||||
integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==
|
||||
"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.0.tgz#965df6c6bfc0a958c1e739284d3c9fa4a6e3c45b"
|
||||
integrity sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.16.0"
|
||||
"@babel/generator" "^7.16.5"
|
||||
"@babel/helper-environment-visitor" "^7.16.5"
|
||||
"@babel/generator" "^7.16.0"
|
||||
"@babel/helper-function-name" "^7.16.0"
|
||||
"@babel/helper-hoist-variables" "^7.16.0"
|
||||
"@babel/helper-split-export-declaration" "^7.16.0"
|
||||
"@babel/parser" "^7.16.5"
|
||||
"@babel/parser" "^7.16.0"
|
||||
"@babel/types" "^7.16.0"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
@@ -926,14 +922,6 @@
|
||||
"@babel/helper-validator-identifier" "^7.15.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.7.tgz#4ed19d51f840ed4bd5645be6ce40775fecf03159"
|
||||
integrity sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.16.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@discoveryjs/json-ext@^0.5.0":
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { nanoid } from "nanoid";
|
||||
import { isTestEnv } from "./utils";
|
||||
|
||||
let random = new Random(Date.now());
|
||||
let testIdBase = 0;
|
||||
@@ -12,4 +11,5 @@ export const reseed = (seed: number) => {
|
||||
testIdBase = 0;
|
||||
};
|
||||
|
||||
export const randomId = () => (isTestEnv() ? `id${testIdBase++}` : nanoid());
|
||||
export const randomId = () =>
|
||||
process.env.NODE_ENV === "test" ? `id${testIdBase++}` : nanoid();
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isLinearElement,
|
||||
isFreeDrawElement,
|
||||
isInitializedImageElement,
|
||||
isImageElement,
|
||||
} from "../element/typeChecks";
|
||||
import {
|
||||
getDiamondPoints,
|
||||
@@ -221,19 +222,31 @@ const drawElementOnCanvas = (
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
const img = isInitializedImageElement(element)
|
||||
? renderConfig.imageCache.get(element.fileId)?.image
|
||||
: undefined;
|
||||
if (img != null && !(img instanceof Promise)) {
|
||||
context.drawImage(
|
||||
img,
|
||||
0 /* hardcoded for the selection box*/,
|
||||
0,
|
||||
element.width,
|
||||
element.height,
|
||||
);
|
||||
if (renderConfig.editingImageElement) {
|
||||
const { imageData } = renderConfig.editingImageElement;
|
||||
|
||||
const imgCanvas = document.createElement("canvas");
|
||||
imgCanvas.width = imageData.width;
|
||||
imgCanvas.height = imageData.height;
|
||||
const imgContext = imgCanvas.getContext("2d")!;
|
||||
imgContext.putImageData(imageData, 0, 0);
|
||||
|
||||
context.drawImage(imgCanvas, 0, 0, element.width, element.height);
|
||||
} else {
|
||||
drawImagePlaceholder(element, context, renderConfig.zoom.value);
|
||||
const img = isInitializedImageElement(element)
|
||||
? renderConfig.imageCache.get(element.fileId)?.image
|
||||
: undefined;
|
||||
if (img != null && !(img instanceof Promise)) {
|
||||
context.drawImage(
|
||||
img,
|
||||
0 /* hardcoded for the selection box*/,
|
||||
0,
|
||||
element.width,
|
||||
element.height,
|
||||
);
|
||||
} else {
|
||||
drawImagePlaceholder(element, context, renderConfig.zoom.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -410,23 +423,23 @@ const generateElementShape = (
|
||||
topY + (rightY - topY) * 0.25
|
||||
} L ${rightX - (rightX - topX) * 0.25} ${
|
||||
rightY - (rightY - topY) * 0.25
|
||||
}
|
||||
}
|
||||
C ${rightX} ${rightY}, ${rightX} ${rightY}, ${
|
||||
rightX - (rightX - bottomX) * 0.25
|
||||
} ${rightY + (bottomY - rightY) * 0.25}
|
||||
} ${rightY + (bottomY - rightY) * 0.25}
|
||||
L ${bottomX + (rightX - bottomX) * 0.25} ${
|
||||
bottomY - (bottomY - rightY) * 0.25
|
||||
}
|
||||
}
|
||||
C ${bottomX} ${bottomY}, ${bottomX} ${bottomY}, ${
|
||||
bottomX - (bottomX - leftX) * 0.25
|
||||
} ${bottomY - (bottomY - leftY) * 0.25}
|
||||
} ${bottomY - (bottomY - leftY) * 0.25}
|
||||
L ${leftX + (bottomX - leftX) * 0.25} ${
|
||||
leftY + (bottomY - leftY) * 0.25
|
||||
}
|
||||
}
|
||||
C ${leftX} ${leftY}, ${leftX} ${leftY}, ${
|
||||
leftX + (topX - leftX) * 0.25
|
||||
} ${leftY - (leftY - topY) * 0.25}
|
||||
L ${topX - (topX - leftX) * 0.25} ${topY + (leftY - topY) * 0.25}
|
||||
} ${leftY - (leftY - topY) * 0.25}
|
||||
L ${topX - (topX - leftX) * 0.25} ${topY + (leftY - topY) * 0.25}
|
||||
C ${topX} ${topY}, ${topX} ${topY}, ${
|
||||
topX + (rightX - topX) * 0.25
|
||||
} ${topY + (rightY - topY) * 0.25}`,
|
||||
@@ -608,7 +621,10 @@ const generateElementWithCanvas = (
|
||||
if (
|
||||
!prevElementWithCanvas ||
|
||||
shouldRegenerateBecauseZoom ||
|
||||
prevElementWithCanvas.theme !== renderConfig.theme
|
||||
prevElementWithCanvas.theme !== renderConfig.theme ||
|
||||
(renderConfig.editingImageElement &&
|
||||
isImageElement(element) &&
|
||||
element.id === renderConfig.editingImageElement.elementId)
|
||||
) {
|
||||
const elementWithCanvas = generateElementCanvas(
|
||||
element,
|
||||
|
||||
@@ -4,15 +4,13 @@ import {
|
||||
NonDeleted,
|
||||
} from "../element/types";
|
||||
import { getNonDeletedElements, isNonDeletedElement } from "../element";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
|
||||
type ElementKey = ExcalidrawElement | ElementIdKey;
|
||||
type ElementKey = ExcalidrawElement | ExcalidrawElement["id"];
|
||||
|
||||
type SceneStateCallback = () => void;
|
||||
type SceneStateCallbackRemover = () => void;
|
||||
|
||||
const isIdKey = (elementKey: ElementKey): elementKey is ElementIdKey => {
|
||||
const isIdKey = (elementKey: ElementKey): elementKey is string => {
|
||||
if (typeof elementKey === "string") {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ export const getElementContainingPosition = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
x: number,
|
||||
y: number,
|
||||
excludedType?: ExcalidrawElement["type"],
|
||||
) => {
|
||||
let hitElement = null;
|
||||
// We need to to hit testing from front (end of the array) to back (beginning of the array)
|
||||
@@ -83,7 +84,13 @@ export const getElementContainingPosition = (
|
||||
continue;
|
||||
}
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index]);
|
||||
if (x1 < x && x < x2 && y1 < y && y < y2) {
|
||||
if (
|
||||
x1 < x &&
|
||||
x < x2 &&
|
||||
y1 < y &&
|
||||
y < y2 &&
|
||||
elements[index].type !== excludedType
|
||||
) {
|
||||
hitElement = elements[index];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ export const exportToCanvas = async (
|
||||
renderSelection: false,
|
||||
renderGrid: false,
|
||||
isExporting: true,
|
||||
editingImageElement: null,
|
||||
});
|
||||
|
||||
return canvas;
|
||||
|
||||
@@ -77,4 +77,4 @@ export const getTargetElements = (
|
||||
) =>
|
||||
appState.editingElement
|
||||
? [appState.editingElement]
|
||||
: getSelectedElements(elements, appState, true);
|
||||
: getSelectedElements(elements, appState);
|
||||
|
||||
@@ -11,6 +11,7 @@ export type RenderConfig = {
|
||||
zoom: AppState["zoom"];
|
||||
shouldCacheIgnoreZoom: AppState["shouldCacheIgnoreZoom"];
|
||||
theme: AppState["theme"];
|
||||
editingImageElement: AppState["editingImageElement"];
|
||||
// collab-related state
|
||||
// ---------------------------------------------------------------------------
|
||||
remotePointerViewportCoords: { [id: string]: { x: number; y: number } };
|
||||
|
||||
@@ -29,6 +29,8 @@ import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||
import Library from "./data/library";
|
||||
import type { FileSystemHandle } from "./data/filesystem";
|
||||
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||
import { EditingImageElement } from "./element/imageEditor";
|
||||
import Scene from "./scene/Scene";
|
||||
|
||||
export type Point = Readonly<RoughPoint>;
|
||||
|
||||
@@ -77,6 +79,7 @@ export type AppState = {
|
||||
// (e.g. text element when typing into the input)
|
||||
editingElement: NonDeletedExcalidrawElement | null;
|
||||
editingLinearElement: LinearElementEditor | null;
|
||||
editingImageElement: EditingImageElement | null;
|
||||
elementType: typeof SHAPES[number]["value"];
|
||||
elementLocked: boolean;
|
||||
exportBackground: boolean;
|
||||
@@ -316,6 +319,8 @@ export type AppClassProperties = {
|
||||
}
|
||||
>;
|
||||
files: BinaryFiles;
|
||||
scene: Scene;
|
||||
addFiles: App["addFiles"];
|
||||
};
|
||||
|
||||
export type PointerDownState = Readonly<{
|
||||
|
||||
@@ -443,7 +443,8 @@ export const bytesToHexString = (bytes: Uint8Array) => {
|
||||
.join("");
|
||||
};
|
||||
|
||||
export const getUpdatedTimestamp = () => (isTestEnv() ? 1 : Date.now());
|
||||
export const getUpdatedTimestamp = () =>
|
||||
process.env.NODE_ENV === "test" ? 1 : Date.now();
|
||||
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
@@ -457,6 +458,3 @@ export const arrayToMap = <T extends { id: string } | string>(
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
|
||||
export const isTestEnv = () =>
|
||||
typeof process !== "undefined" && process.env?.NODE_ENV === "test";
|
||||
|
||||
141
yarn.lock
141
yarn.lock
@@ -2136,14 +2136,14 @@
|
||||
lz-string "^1.4.4"
|
||||
pretty-format "^27.0.2"
|
||||
|
||||
"@testing-library/jest-dom@5.16.1":
|
||||
version "5.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz#3db7df5ae97596264a7da9696fe14695ba02e51f"
|
||||
integrity sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw==
|
||||
"@testing-library/jest-dom@5.15.1":
|
||||
version "5.15.1"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.15.1.tgz#4c49ba4d244f235aec53f0a83498daeb4ee06c33"
|
||||
integrity sha512-kmj8opVDRE1E4GXyLlESsQthCXK7An28dFWxhiMwD7ZUI7ZxA6sjdJRxLerD9Jd8cHX4BDc1jzXaaZKqzlUkvg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
"@types/testing-library__jest-dom" "^5.9.1"
|
||||
aria-query "^5.0.0"
|
||||
aria-query "^4.2.2"
|
||||
chalk "^3.0.0"
|
||||
css "^3.0.0"
|
||||
css.escape "^1.5.1"
|
||||
@@ -2159,10 +2159,10 @@
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@testing-library/dom" "^8.0.0"
|
||||
|
||||
"@tldraw/vec@1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@tldraw/vec/-/vec-1.4.0.tgz#91a6d7c852c680a3288fe8bf79623252ef917281"
|
||||
integrity sha512-YWkGe/IXdFB/Kts982fmtis2nJHB8KWVbkf0PlaZtQ1CL0TZKH0xdpIA/QJMk/WCj3bN4AOYuZKwEAegmWAnOA==
|
||||
"@tldraw/vec@1.1.5":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@tldraw/vec/-/vec-1.1.5.tgz#a0ec75742c20da43e3328f824ef7fca0f982b1fc"
|
||||
integrity sha512-reEos3gJ9OiNvAYFtJzHbYWNjPTvWPmpjAY70HWHMjfhyNk6lHwwzDjwSgej4/KxVBbxB3ppNfLD9mEMq9yCOQ==
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
@@ -2219,10 +2219,10 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/chai@4.3.0":
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc"
|
||||
integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==
|
||||
"@types/chai@4.2.22":
|
||||
version "4.2.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.22.tgz#47020d7e4cf19194d43b5202f35f75bd2ad35ce7"
|
||||
integrity sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==
|
||||
|
||||
"@types/duplexify@^3.6.0":
|
||||
version "3.6.0"
|
||||
@@ -2338,10 +2338,10 @@
|
||||
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz"
|
||||
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
|
||||
|
||||
"@types/pako@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.3.tgz#2e61c2b02020b5f44e2e5e946dfac74f4ec33c58"
|
||||
integrity sha512-EDxOsHAD5dqjbjEUM1xwa7rpKPFb8ECBE5irONTQU7/OsO3thI5YrNEWSPNMvYmvFM0l/OLQJ6Mgw7PEdXSjhg==
|
||||
"@types/pako@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.2.tgz#17c9b136877f33d9ecc8e73cd26944f1f6dd39a1"
|
||||
integrity sha512-8UJl2MjkqqS6ncpLZqRZ5LmGiFBkbYxocD4e4jmBqGvfRG1RS23gKsBQbdtV9O9GvRyjFTiRHRByjSlKCLlmZw==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
@@ -2375,10 +2375,10 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@17.0.38":
|
||||
version "17.0.38"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd"
|
||||
integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==
|
||||
"@types/react@*", "@types/react@17.0.37":
|
||||
version "17.0.37"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959"
|
||||
integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
@@ -4379,6 +4379,11 @@ clone@^1.0.2:
|
||||
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz"
|
||||
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
|
||||
|
||||
clone@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
||||
|
||||
clsx@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz"
|
||||
@@ -5158,10 +5163,10 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
@@ -5718,7 +5723,7 @@ enhanced-resolve@^4.3.0:
|
||||
memory-fs "^0.5.0"
|
||||
tapable "^1.0.0"
|
||||
|
||||
enquirer@^2.3.5:
|
||||
enquirer@^2.3.5, enquirer@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz"
|
||||
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||
@@ -7750,11 +7755,6 @@ immer@8.0.1:
|
||||
resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz"
|
||||
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
|
||||
|
||||
immutable@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||
|
||||
import-cwd@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz"
|
||||
@@ -9276,23 +9276,24 @@ lines-and-columns@^1.1.6:
|
||||
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz"
|
||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||
|
||||
lint-staged@12.1.4:
|
||||
version "12.1.4"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.1.4.tgz#a92ec8509f13018caaafade61d515c2d5873316e"
|
||||
integrity sha512-RgDz9nsFsE0/5eL9Vat0AvCuk0+j5mEuzBIVfrRH5FRtt5wibYe8zTjZs2nuqLFrLAGQGYnj8+HJxolcj08i/A==
|
||||
lint-staged@12.1.2:
|
||||
version "12.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.1.2.tgz#90c571927e1371fc133e720671dd7989eab53f74"
|
||||
integrity sha512-bSMcQVqMW98HLLLR2c2tZ+vnDCnx4fd+0QJBQgN/4XkdspGRPc8DGp7UuOEBe1ApCfJ+wXXumYnJmU+wDo7j9A==
|
||||
dependencies:
|
||||
cli-truncate "^3.1.0"
|
||||
colorette "^2.0.16"
|
||||
commander "^8.3.0"
|
||||
debug "^4.3.3"
|
||||
debug "^4.3.2"
|
||||
enquirer "^2.3.6"
|
||||
execa "^5.1.1"
|
||||
lilconfig "2.0.4"
|
||||
listr2 "^3.13.5"
|
||||
listr2 "^3.13.3"
|
||||
micromatch "^4.0.4"
|
||||
normalize-path "^3.0.0"
|
||||
object-inspect "^1.11.1"
|
||||
object-inspect "^1.11.0"
|
||||
string-argv "^0.3.1"
|
||||
supports-color "^9.2.1"
|
||||
supports-color "^9.0.2"
|
||||
yaml "^1.10.2"
|
||||
|
||||
listenercount@~1.0.1:
|
||||
@@ -9300,16 +9301,16 @@ listenercount@~1.0.1:
|
||||
resolved "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz"
|
||||
integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=
|
||||
|
||||
listr2@^3.13.5:
|
||||
version "3.13.5"
|
||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.5.tgz#105a813f2eb2329c4aae27373a281d610ee4985f"
|
||||
integrity sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==
|
||||
listr2@^3.13.3:
|
||||
version "3.13.3"
|
||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.3.tgz#d8f6095c9371b382c9b1c2bc33c5941d8e177f11"
|
||||
integrity sha512-VqAgN+XVfyaEjSaFewGPcDs5/3hBbWVaX1VgWv2f52MF7US45JuARlArULctiB44IIcEk3JF7GtoFCLqEdeuPA==
|
||||
dependencies:
|
||||
cli-truncate "^2.1.0"
|
||||
clone "^2.1.2"
|
||||
colorette "^2.0.16"
|
||||
log-update "^4.0.0"
|
||||
p-map "^4.0.0"
|
||||
rfdc "^1.3.0"
|
||||
rxjs "^7.4.0"
|
||||
through "^2.3.8"
|
||||
wrap-ansi "^7.0.0"
|
||||
@@ -10403,10 +10404,10 @@ object-copy@^0.1.0:
|
||||
define-property "^0.2.5"
|
||||
kind-of "^3.0.3"
|
||||
|
||||
object-inspect@^1.11.1, object-inspect@^1.9.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
|
||||
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
|
||||
object-inspect@^1.11.0, object-inspect@^1.9.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
|
||||
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
|
||||
|
||||
object-is@^1.0.1:
|
||||
version "1.1.5"
|
||||
@@ -11787,10 +11788,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
|
||||
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
|
||||
prettier@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.0.tgz#a6370e2d4594e093270419d9cc47f7670488f893"
|
||||
integrity sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==
|
||||
|
||||
pretty-bytes@^5.3.0:
|
||||
version "5.6.0"
|
||||
@@ -12718,11 +12719,6 @@ rework@1.0.1:
|
||||
convert-source-map "^0.3.3"
|
||||
css "^2.0.0"
|
||||
|
||||
rfdc@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
|
||||
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
|
||||
|
||||
rgb-regex@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz"
|
||||
@@ -12915,14 +12911,12 @@ sass-loader@^10.0.5:
|
||||
schema-utils "^3.0.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
sass@1.45.2:
|
||||
version "1.45.2"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.2.tgz#130b428c1692201cfa181139835d6fc378a33323"
|
||||
integrity sha512-cKfs+F9AMPAFlbbTXNsbGvg3y58nV0mXA3E94jqaySKcC8Kq3/8983zVKQ0TLMUrHw7hF9Tnd3Bz9z5Xgtrl9g==
|
||||
sass@1.43.5:
|
||||
version "1.43.5"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.5.tgz#25a9d91dd098793ef7229d7b04dd3daae2fc4a65"
|
||||
integrity sha512-WuNm+eAryMgQluL7Mbq9M4EruyGGMyal7Lu58FfnRMVWxgUzIvI7aSn60iNt3kn5yZBMR7G84fAGDcwqOF5JOg==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
sax@~1.2.4:
|
||||
version "1.2.4"
|
||||
@@ -13354,11 +13348,6 @@ source-list-map@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
|
||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
|
||||
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
|
||||
|
||||
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz"
|
||||
@@ -13875,10 +13864,10 @@ supports-color@^7.0.0, supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^9.2.1:
|
||||
version "9.2.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891"
|
||||
integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==
|
||||
supports-color@^9.0.2:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.1.0.tgz#558963681dafeff41ed68220488cbf438d29f351"
|
||||
integrity sha512-lOCGOTmBSN54zKAoPWhHkjoqVQ0MqgzPE5iirtoSixhr0ZieR/6l7WZ32V53cvy9+1qghFnIk7k52p991lKd6g==
|
||||
|
||||
supports-hyperlinks@^1.0.1:
|
||||
version "1.0.1"
|
||||
@@ -14392,10 +14381,10 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
|
||||
typescript@4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
|
||||
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
|
||||
|
||||
typeson-registry@^1.0.0-alpha.20:
|
||||
version "1.0.0-alpha.39"
|
||||
|
||||
Reference in New Issue
Block a user