mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-28 16:44:22 +01:00
Compare commits
14 Commits
perf_debug
...
zsviczian-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e68e11ff9f | ||
|
|
b4750f4485 | ||
|
|
4426275184 | ||
|
|
2b4462c941 | ||
|
|
43b13d8e3a | ||
|
|
720f468f39 | ||
|
|
33300d19f6 | ||
|
|
5aed159991 | ||
|
|
de1d221d1c | ||
|
|
9a68dbffe2 | ||
|
|
32d82219b1 | ||
|
|
ba2c86fe1b | ||
|
|
f1ae37c84b | ||
|
|
ec350ba8b2 |
@@ -31,10 +31,8 @@
|
||||
"@types/socket.io-client": "1.4.36",
|
||||
"browser-fs-access": "0.29.1",
|
||||
"clsx": "1.1.1",
|
||||
"cross-env": "7.0.3",
|
||||
"fake-indexeddb": "3.1.7",
|
||||
"firebase": "8.3.3",
|
||||
"http-server": "14.1.1",
|
||||
"i18next-browser-languagedetector": "6.1.4",
|
||||
"idb-keyval": "6.0.3",
|
||||
"image-blob-reduce": "3.0.1",
|
||||
@@ -93,8 +91,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-node": "node ./scripts/build-node.js",
|
||||
"build:app:docker": "cross-env REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
||||
"build:app": "cross-env REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
||||
"build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
||||
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
||||
"build:version": "node ./scripts/build-version.js",
|
||||
"build:prebuild": "node ./scripts/prebuild.js",
|
||||
"build": "yarn build:prebuild && yarn build:app && yarn build:version",
|
||||
@@ -107,7 +105,6 @@
|
||||
"prepare": "husky install",
|
||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||
"start": "react-scripts start",
|
||||
"start:build": "npm run build && npx http-server build -a localhost -p 3001 -o",
|
||||
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watchAll=false",
|
||||
"test:app": "react-scripts test --passWithNoTests",
|
||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||
|
||||
@@ -18,6 +18,4 @@ const moveServiceWorkerScript = () => {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
if (process.env.CI) {
|
||||
moveServiceWorkerScript();
|
||||
}
|
||||
moveServiceWorkerScript();
|
||||
|
||||
@@ -244,7 +244,7 @@ export const actionLoadScene = register({
|
||||
}
|
||||
},
|
||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
|
||||
PanelComponent: ({ updateData, appState }) => (
|
||||
PanelComponent: ({ updateData }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={load}
|
||||
|
||||
@@ -147,6 +147,7 @@ export class ActionManager {
|
||||
) {
|
||||
const action = this.actions[name];
|
||||
const PanelComponent = action.PanelComponent!;
|
||||
PanelComponent.displayName = "PanelComponent";
|
||||
const elements = this.getElementsIncludingDeleted();
|
||||
const appState = this.getAppState();
|
||||
const updateData = (formState?: any) => {
|
||||
|
||||
@@ -26,17 +26,17 @@ import { ToolButton } from "./ToolButton";
|
||||
import { hasStrokeColor } from "../scene/comparisons";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { hasBoundTextElement, isBoundToContainer } from "../element/typeChecks";
|
||||
import clsx from "clsx";
|
||||
import { actionToggleZenMode } from "../actions";
|
||||
|
||||
export const SelectedShapeActions = ({
|
||||
appState,
|
||||
elements,
|
||||
renderAction,
|
||||
activeTool,
|
||||
}: {
|
||||
appState: AppState;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
renderAction: ActionManager["renderAction"];
|
||||
activeTool: AppState["activeTool"]["type"];
|
||||
}) => {
|
||||
const targetElements = getTargetElements(
|
||||
getNonDeletedElements(elements),
|
||||
@@ -56,13 +56,13 @@ export const SelectedShapeActions = ({
|
||||
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||
|
||||
const showFillIcons =
|
||||
hasBackground(activeTool) ||
|
||||
hasBackground(appState.activeTool.type) ||
|
||||
targetElements.some(
|
||||
(element) =>
|
||||
hasBackground(element.type) && !isTransparent(element.backgroundColor),
|
||||
);
|
||||
const showChangeBackgroundIcons =
|
||||
hasBackground(activeTool) ||
|
||||
hasBackground(appState.activeTool.type) ||
|
||||
targetElements.some((element) => hasBackground(element.type));
|
||||
|
||||
const showLinkIcon =
|
||||
@@ -79,23 +79,23 @@ export const SelectedShapeActions = ({
|
||||
|
||||
return (
|
||||
<div className="panelColumn">
|
||||
{((hasStrokeColor(activeTool) &&
|
||||
activeTool !== "image" &&
|
||||
{((hasStrokeColor(appState.activeTool.type) &&
|
||||
appState.activeTool.type !== "image" &&
|
||||
commonSelectedType !== "image") ||
|
||||
targetElements.some((element) => hasStrokeColor(element.type))) &&
|
||||
renderAction("changeStrokeColor")}
|
||||
{showChangeBackgroundIcons && renderAction("changeBackgroundColor")}
|
||||
{showFillIcons && renderAction("changeFillStyle")}
|
||||
|
||||
{(hasStrokeWidth(activeTool) ||
|
||||
{(hasStrokeWidth(appState.activeTool.type) ||
|
||||
targetElements.some((element) => hasStrokeWidth(element.type))) &&
|
||||
renderAction("changeStrokeWidth")}
|
||||
|
||||
{(activeTool === "freedraw" ||
|
||||
{(appState.activeTool.type === "freedraw" ||
|
||||
targetElements.some((element) => element.type === "freedraw")) &&
|
||||
renderAction("changeStrokeShape")}
|
||||
|
||||
{(hasStrokeStyle(activeTool) ||
|
||||
{(hasStrokeStyle(appState.activeTool.type) ||
|
||||
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
||||
<>
|
||||
{renderAction("changeStrokeStyle")}
|
||||
@@ -103,12 +103,12 @@ export const SelectedShapeActions = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{(canChangeSharpness(activeTool) ||
|
||||
{(canChangeSharpness(appState.activeTool.type) ||
|
||||
targetElements.some((element) => canChangeSharpness(element.type))) && (
|
||||
<>{renderAction("changeSharpness")}</>
|
||||
)}
|
||||
|
||||
{(hasText(activeTool) ||
|
||||
{(hasText(appState.activeTool.type) ||
|
||||
targetElements.some((element) => hasText(element.type))) && (
|
||||
<>
|
||||
{renderAction("changeFontSize")}
|
||||
@@ -123,7 +123,7 @@ export const SelectedShapeActions = ({
|
||||
(element) =>
|
||||
hasBoundTextElement(element) || isBoundToContainer(element),
|
||||
) && renderAction("changeVerticalAlign")}
|
||||
{(canHaveArrowheads(activeTool) ||
|
||||
{(canHaveArrowheads(appState.activeTool.type) ||
|
||||
targetElements.some((element) => canHaveArrowheads(element.type))) && (
|
||||
<>{renderAction("changeArrowhead")}</>
|
||||
)}
|
||||
@@ -271,3 +271,45 @@ export const ZoomActions = ({
|
||||
</Stack.Row>
|
||||
</Stack.Col>
|
||||
);
|
||||
|
||||
export const UndoRedoActions = ({
|
||||
renderAction,
|
||||
className,
|
||||
}: {
|
||||
renderAction: ActionManager["renderAction"];
|
||||
className?: string;
|
||||
}) => (
|
||||
<div className={`undo-redo-buttons ${className}`}>
|
||||
{renderAction("undo", { size: "small" })}
|
||||
{renderAction("redo", { size: "small" })}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ExitZenModeAction = ({
|
||||
executeAction,
|
||||
showExitZenModeBtn,
|
||||
}: {
|
||||
executeAction: ActionManager["executeAction"];
|
||||
showExitZenModeBtn: boolean;
|
||||
}) => (
|
||||
<button
|
||||
className={clsx("disable-zen-mode", {
|
||||
"disable-zen-mode--visible": showExitZenModeBtn,
|
||||
})}
|
||||
onClick={() => executeAction(actionToggleZenMode)}
|
||||
>
|
||||
{t("buttons.exitZenMode")}
|
||||
</button>
|
||||
);
|
||||
|
||||
export const FinalizeAction = ({
|
||||
renderAction,
|
||||
className,
|
||||
}: {
|
||||
renderAction: ActionManager["renderAction"];
|
||||
className?: string;
|
||||
}) => (
|
||||
<div className={`finalize-button ${className}`}>
|
||||
{renderAction("finalize", { size: "small" })}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -264,101 +264,6 @@ import {
|
||||
} from "../element/Hyperlink";
|
||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||
|
||||
let TIMES_AGGR: Record<string, { t: number; times: number[] }> = {};
|
||||
let TIMES_AVG: Record<
|
||||
string,
|
||||
{ t: number; times: number[]; avg: number | null }
|
||||
> = {};
|
||||
|
||||
window.DEBUG_LOG_TIMES = true;
|
||||
|
||||
let lastDebugLogCall = 0;
|
||||
let DEBUG_LOG_INTERVAL_ID: null | number = null;
|
||||
|
||||
const setupInterval = () => {
|
||||
if (DEBUG_LOG_INTERVAL_ID === null) {
|
||||
console.info("%c(starting perf recording)", "color: lime");
|
||||
DEBUG_LOG_INTERVAL_ID = window.setInterval(debugLogger, 1000);
|
||||
}
|
||||
lastDebugLogCall = Date.now();
|
||||
};
|
||||
|
||||
const lessPrecise = (num: number, precision = 5) =>
|
||||
parseFloat(num.toPrecision(precision));
|
||||
|
||||
const getAvgFrameTime = (times: number[]) =>
|
||||
lessPrecise(times.reduce((a, b) => a + b) / times.length);
|
||||
|
||||
const getFps = (frametime: number) => lessPrecise(1000 / frametime);
|
||||
|
||||
const debugLogger = () => {
|
||||
if (Date.now() - lastDebugLogCall > 600 && DEBUG_LOG_INTERVAL_ID !== null) {
|
||||
window.clearInterval(DEBUG_LOG_INTERVAL_ID);
|
||||
DEBUG_LOG_INTERVAL_ID = null;
|
||||
for (const [name, { avg }] of Object.entries(TIMES_AVG)) {
|
||||
if (avg != null) {
|
||||
console.info(
|
||||
`%c${name} run avg: ${avg}ms (${getFps(avg)} fps)`,
|
||||
"color: blue",
|
||||
);
|
||||
}
|
||||
}
|
||||
console.info("%c(stopping perf recording)", "color: red");
|
||||
TIMES_AGGR = {};
|
||||
TIMES_AVG = {};
|
||||
return;
|
||||
}
|
||||
if (window.DEBUG_LOG_TIMES) {
|
||||
for (const [name, { t, times }] of Object.entries(TIMES_AGGR)) {
|
||||
if (times.length) {
|
||||
console.info(
|
||||
name,
|
||||
lessPrecise(times.reduce((a, b) => a + b) / times.length),
|
||||
times.sort((a, b) => a - b).map((x) => lessPrecise(x)),
|
||||
);
|
||||
TIMES_AGGR[name] = { t, times: [] };
|
||||
}
|
||||
}
|
||||
for (const [name, { t, times, avg }] of Object.entries(TIMES_AVG)) {
|
||||
if (times.length) {
|
||||
const avgFrameTime = getAvgFrameTime(times);
|
||||
console.info(name, `${avgFrameTime}ms (${getFps(avgFrameTime)} fps)`);
|
||||
TIMES_AVG[name] = {
|
||||
t,
|
||||
times: [],
|
||||
avg:
|
||||
avg != null ? getAvgFrameTime([avg, avgFrameTime]) : avgFrameTime,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.logTime = (name: string, time?: number) => {
|
||||
setupInterval();
|
||||
const now = performance.now();
|
||||
const { t, times } = (TIMES_AGGR[name] = TIMES_AGGR[name] || {
|
||||
t: 0,
|
||||
times: [],
|
||||
});
|
||||
if (t) {
|
||||
times.push(time != null ? time : now - t);
|
||||
}
|
||||
TIMES_AGGR[name].t = now;
|
||||
};
|
||||
window.logTimeAverage = (name: string, time?: number) => {
|
||||
setupInterval();
|
||||
const now = performance.now();
|
||||
const { t, times } = (TIMES_AVG[name] = TIMES_AVG[name] || {
|
||||
t: 0,
|
||||
times: [],
|
||||
});
|
||||
if (t) {
|
||||
times.push(time != null ? time : now - t);
|
||||
}
|
||||
TIMES_AVG[name].t = now;
|
||||
};
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
isSmScreen: false,
|
||||
isMobile: false,
|
||||
@@ -367,6 +272,7 @@ const deviceContextInitialValue = {
|
||||
};
|
||||
const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
|
||||
export const useDevice = () => useContext<Device>(DeviceContext);
|
||||
|
||||
const ExcalidrawContainerContext = React.createContext<{
|
||||
container: HTMLDivElement | null;
|
||||
id: string | null;
|
||||
@@ -374,6 +280,22 @@ const ExcalidrawContainerContext = React.createContext<{
|
||||
export const useExcalidrawContainer = () =>
|
||||
useContext(ExcalidrawContainerContext);
|
||||
|
||||
const ExcalidrawElementsContext = React.createContext<
|
||||
readonly NonDeletedExcalidrawElement[]
|
||||
>([]);
|
||||
|
||||
const ExcalidrawAppStateContext = React.createContext<AppState>({
|
||||
...getDefaultAppState(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
offsetLeft: 0,
|
||||
offsetTop: 0,
|
||||
});
|
||||
export const useExcalidrawElements = () =>
|
||||
useContext(ExcalidrawElementsContext);
|
||||
export const useExcalidrawAppState = () =>
|
||||
useContext(ExcalidrawAppStateContext);
|
||||
|
||||
let didTapTwice: boolean = false;
|
||||
let tappedTwiceTimer = 0;
|
||||
let cursorX = 0;
|
||||
@@ -571,26 +493,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
}
|
||||
|
||||
private __renderUI = true;
|
||||
|
||||
private perfTest = (runs = 180, initial = true) => {
|
||||
if (initial) {
|
||||
console.time("perfTest");
|
||||
} else if (!runs) {
|
||||
console.timeEnd("perfTest");
|
||||
}
|
||||
if (runs) {
|
||||
requestAnimationFrame((id) => {
|
||||
for (const element of this.scene.getNonDeletedElements()) {
|
||||
mutateElement(element, {
|
||||
x: element.x + 1,
|
||||
});
|
||||
}
|
||||
this.perfTest(runs - 1, false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
const selectedElement = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
@@ -620,65 +522,69 @@ class App extends React.Component<AppProps, AppState> {
|
||||
value={this.excalidrawContainerValue}
|
||||
>
|
||||
<DeviceContext.Provider value={this.device}>
|
||||
{this.__renderUI && (
|
||||
<LayerUI
|
||||
canvas={this.canvas}
|
||||
appState={this.state}
|
||||
files={this.files}
|
||||
setAppState={this.setAppState}
|
||||
actionManager={this.actionManager}
|
||||
elements={this.scene.getNonDeletedElements()}
|
||||
onCollabButtonClick={onCollabButtonClick}
|
||||
onLockToggle={this.toggleLock}
|
||||
onPenModeToggle={this.togglePenMode}
|
||||
onInsertElements={(elements) =>
|
||||
this.addElementsFromPasteOrLibrary({
|
||||
elements,
|
||||
position: "center",
|
||||
files: null,
|
||||
})
|
||||
}
|
||||
langCode={getLanguage().code}
|
||||
isCollaborating={this.props.isCollaborating}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderCustomFooter={renderFooter}
|
||||
renderCustomStats={renderCustomStats}
|
||||
showExitZenModeBtn={
|
||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||
this.state.zenModeEnabled
|
||||
}
|
||||
showThemeBtn={
|
||||
typeof this.props?.theme === "undefined" &&
|
||||
this.props.UIOptions.canvasActions.theme
|
||||
}
|
||||
libraryReturnUrl={this.props.libraryReturnUrl}
|
||||
UIOptions={this.props.UIOptions}
|
||||
focusContainer={this.focusContainer}
|
||||
library={this.library}
|
||||
id={this.id}
|
||||
onImageAction={this.onImageAction}
|
||||
/>
|
||||
)}
|
||||
<div className="excalidraw-textEditorContainer" />
|
||||
<div className="excalidraw-contextMenuContainer" />
|
||||
{selectedElement.length === 1 && this.state.showHyperlinkPopup && (
|
||||
<Hyperlink
|
||||
key={selectedElement[0].id}
|
||||
element={selectedElement[0]}
|
||||
appState={this.state}
|
||||
setAppState={this.setAppState}
|
||||
onLinkOpen={this.props.onLinkOpen}
|
||||
/>
|
||||
)}
|
||||
{this.state.toast !== null && (
|
||||
<Toast
|
||||
message={this.state.toast.message}
|
||||
onClose={() => this.setToast(null)}
|
||||
duration={this.state.toast.duration}
|
||||
closable={this.state.toast.closable}
|
||||
/>
|
||||
)}
|
||||
<main>{this.renderCanvas()}</main>
|
||||
<ExcalidrawAppStateContext.Provider value={this.state}>
|
||||
<ExcalidrawElementsContext.Provider
|
||||
value={this.scene.getNonDeletedElements()}
|
||||
>
|
||||
<LayerUI
|
||||
canvas={this.canvas}
|
||||
appState={this.state}
|
||||
files={this.files}
|
||||
setAppState={this.setAppState}
|
||||
actionManager={this.actionManager}
|
||||
elements={this.scene.getNonDeletedElements()}
|
||||
onCollabButtonClick={onCollabButtonClick}
|
||||
onLockToggle={this.toggleLock}
|
||||
onPenModeToggle={this.togglePenMode}
|
||||
onInsertElements={(elements) =>
|
||||
this.addElementsFromPasteOrLibrary({
|
||||
elements,
|
||||
position: "center",
|
||||
files: null,
|
||||
})
|
||||
}
|
||||
langCode={getLanguage().code}
|
||||
isCollaborating={this.props.isCollaborating}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderCustomFooter={renderFooter}
|
||||
renderCustomStats={renderCustomStats}
|
||||
showExitZenModeBtn={
|
||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||
this.state.zenModeEnabled
|
||||
}
|
||||
showThemeBtn={
|
||||
typeof this.props?.theme === "undefined" &&
|
||||
this.props.UIOptions.canvasActions.theme
|
||||
}
|
||||
libraryReturnUrl={this.props.libraryReturnUrl}
|
||||
UIOptions={this.props.UIOptions}
|
||||
focusContainer={this.focusContainer}
|
||||
library={this.library}
|
||||
id={this.id}
|
||||
onImageAction={this.onImageAction}
|
||||
/>
|
||||
<div className="excalidraw-textEditorContainer" />
|
||||
<div className="excalidraw-contextMenuContainer" />
|
||||
{selectedElement.length === 1 &&
|
||||
this.state.showHyperlinkPopup && (
|
||||
<Hyperlink
|
||||
key={selectedElement[0].id}
|
||||
element={selectedElement[0]}
|
||||
setAppState={this.setAppState}
|
||||
onLinkOpen={this.props.onLinkOpen}
|
||||
/>
|
||||
)}
|
||||
{this.state.toast !== null && (
|
||||
<Toast
|
||||
message={this.state.toast.message}
|
||||
onClose={() => this.setToast(null)}
|
||||
duration={this.state.toast.duration}
|
||||
closable={this.state.toast.closable}
|
||||
/>
|
||||
)}
|
||||
<main>{this.renderCanvas()}</main>
|
||||
</ExcalidrawElementsContext.Provider>{" "}
|
||||
</ExcalidrawAppStateContext.Provider>
|
||||
</DeviceContext.Provider>
|
||||
</ExcalidrawContainerContext.Provider>
|
||||
</div>
|
||||
@@ -937,8 +843,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV === ENV.TEST ||
|
||||
process.env.NODE_ENV === ENV.DEVELOPMENT ||
|
||||
process.env.REACT_APP_VERCEL_ENV === "preview"
|
||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
||||
) {
|
||||
const setState = this.setState.bind(this);
|
||||
Object.defineProperties(window.h, {
|
||||
@@ -6261,6 +6166,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
width,
|
||||
@@ -6321,8 +6233,7 @@ declare global {
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV === ENV.TEST ||
|
||||
process.env.NODE_ENV === ENV.DEVELOPMENT ||
|
||||
process.env.REACT_APP_VERCEL_ENV === "preview"
|
||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
||||
) {
|
||||
window.h = window.h || ({} as Window["h"]);
|
||||
|
||||
|
||||
@@ -343,6 +343,8 @@ const ColorInput = React.forwardRef(
|
||||
},
|
||||
);
|
||||
|
||||
ColorInput.displayName = "ColorInput";
|
||||
|
||||
export const ColorPicker = ({
|
||||
type,
|
||||
color,
|
||||
|
||||
99
src/components/Footer.tsx
Normal file
99
src/components/Footer.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import clsx from "clsx";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
import {
|
||||
ExitZenModeAction,
|
||||
FinalizeAction,
|
||||
UndoRedoActions,
|
||||
ZoomActions,
|
||||
} from "./Actions";
|
||||
import { useDevice } from "./App";
|
||||
import { Island } from "./Island";
|
||||
import { Section } from "./Section";
|
||||
import Stack from "./Stack";
|
||||
|
||||
const Footer = ({
|
||||
appState,
|
||||
actionManager,
|
||||
renderCustomFooter,
|
||||
showExitZenModeBtn,
|
||||
}: {
|
||||
appState: AppState;
|
||||
actionManager: ActionManager;
|
||||
renderCustomFooter?: ExcalidrawProps["renderFooter"];
|
||||
showExitZenModeBtn: boolean;
|
||||
}) => {
|
||||
const device = useDevice();
|
||||
const showFinalize =
|
||||
!appState.viewModeEnabled && appState.multiElement && device.isTouchScreen;
|
||||
return (
|
||||
<footer
|
||||
role="contentinfo"
|
||||
className="layer-ui__wrapper__footer App-menu App-menu_bottom"
|
||||
>
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper__footer-left zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
<Stack.Col gap={2}>
|
||||
<Section heading="canvasActions">
|
||||
<Island padding={1}>
|
||||
<ZoomActions
|
||||
renderAction={actionManager.renderAction}
|
||||
zoom={appState.zoom}
|
||||
/>
|
||||
</Island>
|
||||
{!appState.viewModeEnabled && (
|
||||
<>
|
||||
<UndoRedoActions
|
||||
renderAction={actionManager.renderAction}
|
||||
className={clsx("zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={clsx("eraser-buttons zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("eraser", { size: "small" })}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{showFinalize && (
|
||||
<FinalizeAction
|
||||
renderAction={actionManager.renderAction}
|
||||
className={clsx("zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Col>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"layer-ui__wrapper__footer-center zen-mode-transition",
|
||||
{
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
appState.zenModeEnabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{renderCustomFooter?.(false, appState)}
|
||||
</div>
|
||||
<ExitZenModeAction
|
||||
executeAction={actionManager.executeAction}
|
||||
showExitZenModeBtn={showExitZenModeBtn}
|
||||
/>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -10,7 +10,7 @@ import { calculateScrollCenter, getSelectedElements } from "../scene";
|
||||
import { ExportType } from "../scene/types";
|
||||
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||
import CollabButton from "./CollabButton";
|
||||
import { ErrorDialog } from "./ErrorDialog";
|
||||
@@ -39,7 +39,7 @@ import { trackEvent } from "../analytics";
|
||||
import { useDevice } from "../components/App";
|
||||
import { Stats } from "./Stats";
|
||||
import { actionToggleStats } from "../actions/actionToggleStats";
|
||||
import { actionToggleZenMode } from "../actions";
|
||||
import Footer from "./Footer";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
@@ -71,8 +71,8 @@ const LayerUI = ({
|
||||
appState,
|
||||
files,
|
||||
setAppState,
|
||||
canvas,
|
||||
elements,
|
||||
canvas,
|
||||
onCollabButtonClick,
|
||||
onLockToggle,
|
||||
onPenModeToggle,
|
||||
@@ -210,8 +210,8 @@ const LayerUI = ({
|
||||
)}
|
||||
</Stack.Row>
|
||||
<BackgroundPickerAndDarkModeToggle
|
||||
actionManager={actionManager}
|
||||
appState={appState}
|
||||
actionManager={actionManager}
|
||||
setAppState={setAppState}
|
||||
showThemeBtn={showThemeBtn}
|
||||
/>
|
||||
@@ -244,7 +244,6 @@ const LayerUI = ({
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
renderAction={actionManager.renderAction}
|
||||
activeTool={appState.activeTool.type}
|
||||
/>
|
||||
</Island>
|
||||
</Section>
|
||||
@@ -279,7 +278,6 @@ const LayerUI = ({
|
||||
libraryReturnUrl={libraryReturnUrl}
|
||||
focusContainer={focusContainer}
|
||||
library={library}
|
||||
theme={appState.theme}
|
||||
files={files}
|
||||
id={id}
|
||||
appState={appState}
|
||||
@@ -383,100 +381,7 @@ const LayerUI = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderBottomAppMenu = () => {
|
||||
return (
|
||||
<footer
|
||||
role="contentinfo"
|
||||
className="layer-ui__wrapper__footer App-menu App-menu_bottom"
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"layer-ui__wrapper__footer-left zen-mode-transition",
|
||||
{
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
appState.zenModeEnabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Stack.Col gap={2}>
|
||||
<Section heading="canvasActions">
|
||||
<Island padding={1}>
|
||||
<ZoomActions
|
||||
renderAction={actionManager.renderAction}
|
||||
zoom={appState.zoom}
|
||||
/>
|
||||
</Island>
|
||||
{!appState.viewModeEnabled && (
|
||||
<>
|
||||
<div
|
||||
className={clsx("undo-redo-buttons zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("undo", { size: "small" })}
|
||||
{actionManager.renderAction("redo", { size: "small" })}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx("eraser-buttons zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("eraser", { size: "small" })}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!appState.viewModeEnabled &&
|
||||
appState.multiElement &&
|
||||
device.isTouchScreen && (
|
||||
<div
|
||||
className={clsx("finalize-button zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("finalize", { size: "small" })}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Col>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"layer-ui__wrapper__footer-center zen-mode-transition",
|
||||
{
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
appState.zenModeEnabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{renderCustomFooter?.(false, appState)}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"layer-ui__wrapper__footer-right zen-mode-transition",
|
||||
{
|
||||
"transition-right disable-pointerEvents": appState.zenModeEnabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{actionManager.renderAction("toggleShortcuts")}
|
||||
</div>
|
||||
<button
|
||||
className={clsx("disable-zen-mode", {
|
||||
"disable-zen-mode--visible": showExitZenModeBtn,
|
||||
})}
|
||||
onClick={() => actionManager.executeAction(actionToggleZenMode)}
|
||||
>
|
||||
{t("buttons.exitZenMode")}
|
||||
</button>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
const dialogs = (
|
||||
return (
|
||||
<>
|
||||
{appState.isLoading && <LoadingMessage delay={250} />}
|
||||
{appState.errorMessage && (
|
||||
@@ -504,84 +409,77 @@ const LayerUI = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{device.isMobile && (
|
||||
<MobileMenu
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
actionManager={actionManager}
|
||||
libraryMenu={libraryMenu}
|
||||
renderJSONExportDialog={renderJSONExportDialog}
|
||||
renderImageExportDialog={renderImageExportDialog}
|
||||
setAppState={setAppState}
|
||||
onCollabButtonClick={onCollabButtonClick}
|
||||
onLockToggle={() => onLockToggle()}
|
||||
onPenModeToggle={onPenModeToggle}
|
||||
canvas={canvas}
|
||||
isCollaborating={isCollaborating}
|
||||
renderCustomFooter={renderCustomFooter}
|
||||
showThemeBtn={showThemeBtn}
|
||||
onImageAction={onImageAction}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderCustomStats={renderCustomStats}
|
||||
/>
|
||||
)}
|
||||
|
||||
const renderStats = () => {
|
||||
if (!appState.showStats) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Stats
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
elements={elements}
|
||||
onClose={() => {
|
||||
actionManager.executeAction(actionToggleStats);
|
||||
}}
|
||||
renderCustomStats={renderCustomStats}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return device.isMobile ? (
|
||||
<>
|
||||
{dialogs}
|
||||
<MobileMenu
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
actionManager={actionManager}
|
||||
libraryMenu={libraryMenu}
|
||||
renderJSONExportDialog={renderJSONExportDialog}
|
||||
renderImageExportDialog={renderImageExportDialog}
|
||||
setAppState={setAppState}
|
||||
onCollabButtonClick={onCollabButtonClick}
|
||||
onLockToggle={() => onLockToggle()}
|
||||
onPenModeToggle={onPenModeToggle}
|
||||
canvas={canvas}
|
||||
isCollaborating={isCollaborating}
|
||||
renderCustomFooter={renderCustomFooter}
|
||||
showThemeBtn={showThemeBtn}
|
||||
onImageAction={onImageAction}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderStats={renderStats}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper", {
|
||||
"disable-pointerEvents":
|
||||
appState.draggingElement ||
|
||||
appState.resizingElement ||
|
||||
(appState.editingElement &&
|
||||
!isTextElement(appState.editingElement)),
|
||||
})}
|
||||
style={
|
||||
appState.isLibraryOpen &&
|
||||
appState.isLibraryMenuDocked &&
|
||||
device.canDeviceFitSidebar
|
||||
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{dialogs}
|
||||
{renderFixedSideContainer()}
|
||||
{renderBottomAppMenu()}
|
||||
{renderStats()}
|
||||
{appState.scrolledOutside && (
|
||||
<button
|
||||
className="scroll-back-to-content"
|
||||
onClick={() => {
|
||||
setAppState({
|
||||
...calculateScrollCenter(elements, appState, canvas),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("buttons.scrollBackToContent")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{!device.isMobile && (
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper", {
|
||||
"disable-pointerEvents":
|
||||
appState.draggingElement ||
|
||||
appState.resizingElement ||
|
||||
(appState.editingElement &&
|
||||
!isTextElement(appState.editingElement)),
|
||||
})}
|
||||
style={
|
||||
appState.isLibraryOpen &&
|
||||
appState.isLibraryMenuDocked &&
|
||||
device.canDeviceFitSidebar
|
||||
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{renderFixedSideContainer()}
|
||||
<Footer
|
||||
appState={appState}
|
||||
actionManager={actionManager}
|
||||
renderCustomFooter={renderCustomFooter}
|
||||
showExitZenModeBtn={showExitZenModeBtn}
|
||||
/>
|
||||
{appState.showStats && (
|
||||
<Stats
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
elements={elements}
|
||||
onClose={() => {
|
||||
actionManager.executeAction(actionToggleStats);
|
||||
}}
|
||||
renderCustomStats={renderCustomStats}
|
||||
/>
|
||||
)}
|
||||
{appState.scrolledOutside && (
|
||||
<button
|
||||
className="scroll-back-to-content"
|
||||
onClick={() => {
|
||||
setAppState({
|
||||
...calculateScrollCenter(elements, appState, canvas),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("buttons.scrollBackToContent")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{appState.isLibraryOpen && (
|
||||
<div className="layer-ui__sidebar">{libraryMenu}</div>
|
||||
)}
|
||||
|
||||
@@ -80,7 +80,6 @@ export const LibraryMenu = ({
|
||||
onInsertLibraryItems,
|
||||
pendingElements,
|
||||
onAddToLibrary,
|
||||
theme,
|
||||
setAppState,
|
||||
files,
|
||||
libraryReturnUrl,
|
||||
@@ -93,7 +92,6 @@ export const LibraryMenu = ({
|
||||
onClose: () => void;
|
||||
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
||||
onAddToLibrary: () => void;
|
||||
theme: AppState["theme"];
|
||||
files: BinaryFiles;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||
@@ -105,7 +103,6 @@ export const LibraryMenu = ({
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const device = useDevice();
|
||||
|
||||
useOnClickOutside(
|
||||
ref,
|
||||
useCallback(
|
||||
@@ -290,7 +287,7 @@ export const LibraryMenu = ({
|
||||
appState={appState}
|
||||
libraryReturnUrl={libraryReturnUrl}
|
||||
library={library}
|
||||
theme={theme}
|
||||
theme={appState.theme}
|
||||
files={files}
|
||||
id={id}
|
||||
selectedItems={selectedItems}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { AppState } from "../types";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { t } from "../i18n";
|
||||
import Stack from "./Stack";
|
||||
@@ -18,6 +18,8 @@ import { UserList } from "./UserList";
|
||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||
import { LibraryButton } from "./LibraryButton";
|
||||
import { PenModeButton } from "./PenModeButton";
|
||||
import { Stats } from "./Stats";
|
||||
import { actionToggleStats } from "../actions";
|
||||
|
||||
type MobileMenuProps = {
|
||||
appState: AppState;
|
||||
@@ -42,7 +44,7 @@ type MobileMenuProps = {
|
||||
isMobile: boolean,
|
||||
appState: AppState,
|
||||
) => JSX.Element | null;
|
||||
renderStats: () => JSX.Element | null;
|
||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||
};
|
||||
|
||||
export const MobileMenu = ({
|
||||
@@ -62,7 +64,7 @@ export const MobileMenu = ({
|
||||
showThemeBtn,
|
||||
onImageAction,
|
||||
renderTopRightUI,
|
||||
renderStats,
|
||||
renderCustomStats,
|
||||
}: MobileMenuProps) => {
|
||||
const renderToolbar = () => {
|
||||
return (
|
||||
@@ -184,7 +186,17 @@ export const MobileMenu = ({
|
||||
return (
|
||||
<>
|
||||
{!appState.viewModeEnabled && renderToolbar()}
|
||||
{renderStats()}
|
||||
{!appState.openMenu && appState.showStats && (
|
||||
<Stats
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
elements={elements}
|
||||
onClose={() => {
|
||||
actionManager.executeAction(actionToggleStats);
|
||||
}}
|
||||
renderCustomStats={renderCustomStats}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="App-bottom-bar"
|
||||
style={{
|
||||
@@ -221,7 +233,6 @@ export const MobileMenu = ({
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
renderAction={actionManager.renderAction}
|
||||
activeTool={appState.activeTool.type}
|
||||
/>
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import { getCommonBounds } from "../element/bounds";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { useDevice } from "../components/App";
|
||||
import { getTargetElements } from "../scene";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
import { close } from "./icons";
|
||||
@@ -16,13 +15,10 @@ export const Stats = (props: {
|
||||
onClose: () => void;
|
||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||
}) => {
|
||||
const device = useDevice();
|
||||
const boundingBox = getCommonBounds(props.elements);
|
||||
const selectedElements = getTargetElements(props.elements, props.appState);
|
||||
const selectedBoundingBox = getCommonBounds(selectedElements);
|
||||
if (device.isMobile && props.appState.openMenu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Stats">
|
||||
<Island padding={2}>
|
||||
|
||||
@@ -187,3 +187,5 @@ ToolButton.defaultProps = {
|
||||
className: "",
|
||||
size: "medium",
|
||||
};
|
||||
|
||||
ToolButton.displayName = "ToolButton";
|
||||
|
||||
@@ -32,6 +32,7 @@ import { getElementAbsoluteCoords } from "./";
|
||||
|
||||
import "./Hyperlink.scss";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { useExcalidrawAppState } from "../components/App";
|
||||
|
||||
const CONTAINER_WIDTH = 320;
|
||||
const SPACE_BOTTOM = 85;
|
||||
@@ -48,15 +49,15 @@ let IS_HYPERLINK_TOOLTIP_VISIBLE = false;
|
||||
|
||||
export const Hyperlink = ({
|
||||
element,
|
||||
appState,
|
||||
setAppState,
|
||||
onLinkOpen,
|
||||
}: {
|
||||
element: NonDeletedExcalidrawElement;
|
||||
appState: AppState;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
||||
}) => {
|
||||
const appState = useExcalidrawAppState();
|
||||
|
||||
const linkVal = element.link || "";
|
||||
|
||||
const [inputVal, setInputVal] = useState(linkVal);
|
||||
|
||||
@@ -107,12 +107,19 @@ const solveQuadratic = (
|
||||
return false;
|
||||
}
|
||||
|
||||
const t1 = (-b + Math.sqrt(sqrtPart)) / (2 * a);
|
||||
const t2 = (-b - Math.sqrt(sqrtPart)) / (2 * a);
|
||||
|
||||
let s1 = null;
|
||||
let s2 = null;
|
||||
|
||||
let t1 = Infinity;
|
||||
let t2 = Infinity;
|
||||
|
||||
if (a === 0) {
|
||||
t1 = t2 = -c / b;
|
||||
} else {
|
||||
t1 = (-b + Math.sqrt(sqrtPart)) / (2 * a);
|
||||
t2 = (-b - Math.sqrt(sqrtPart)) / (2 * a);
|
||||
}
|
||||
|
||||
if (t1 >= 0 && t1 <= 1) {
|
||||
s1 = getBezierValueForT(t1, p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
@@ -686,7 +686,9 @@ export class LinearElementEditor {
|
||||
|
||||
const point = element.points[index];
|
||||
const { x, y } = element;
|
||||
return rotate(x + point[0], y + point[1], cx, cy, element.angle);
|
||||
return point
|
||||
? rotate(x + point[0], y + point[1], cx, cy, element.angle)
|
||||
: rotate(x, y, cx, cy, element.angle);
|
||||
}
|
||||
|
||||
static pointFromAbsoluteCoords(
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import { DEFAULT_VERSION } from "../constants";
|
||||
import { t } from "../i18n";
|
||||
import { copyTextToSystemClipboard } from "../clipboard";
|
||||
import { AppState } from "../types";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
type StorageSizes = { scene: number; total: number };
|
||||
|
||||
const STORAGE_SIZE_TIMEOUT = 500;
|
||||
@@ -20,6 +22,8 @@ const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => {
|
||||
|
||||
type Props = {
|
||||
setToast: (message: string) => void;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
appState: AppState;
|
||||
};
|
||||
const CustomStats = (props: Props) => {
|
||||
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
||||
@@ -31,7 +35,7 @@ const CustomStats = (props: Props) => {
|
||||
getStorageSizes((sizes) => {
|
||||
setStorageSizes(sizes);
|
||||
});
|
||||
});
|
||||
}, [props.elements, props.appState]);
|
||||
useEffect(() => () => getStorageSizes.cancel(), []);
|
||||
|
||||
const version = getVersion();
|
||||
|
||||
@@ -169,8 +169,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV === ENV.TEST ||
|
||||
process.env.NODE_ENV === ENV.DEVELOPMENT ||
|
||||
process.env.REACT_APP_VERCEL_ENV === "preview"
|
||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
||||
) {
|
||||
window.collab = window.collab || ({} as Window["collab"]);
|
||||
Object.defineProperties(window, {
|
||||
|
||||
@@ -666,10 +666,15 @@ const ExcalidrawWrapper = () => {
|
||||
[langCode],
|
||||
);
|
||||
|
||||
const renderCustomStats = () => {
|
||||
const renderCustomStats = (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
return (
|
||||
<CustomStats
|
||||
setToast={(message) => excalidrawAPI!.setToast({ message })}
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
3
src/global.d.ts
vendored
3
src/global.d.ts
vendored
@@ -16,9 +16,6 @@ interface Window {
|
||||
EXCALIDRAW_EXPORT_SOURCE: string;
|
||||
EXCALIDRAW_THROTTLE_RENDER: boolean | undefined;
|
||||
gtag: Function;
|
||||
logTime: (name: string, time?: number) => void;
|
||||
logTimeAverage: (name: string, time?: number) => void;
|
||||
DEBUG_LOG_TIMES: boolean;
|
||||
}
|
||||
|
||||
// https://github.com/facebook/create-react-app/blob/ddcb7d5/packages/react-scripts/lib/react-app.d.ts
|
||||
|
||||
@@ -29,6 +29,8 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
#### Features
|
||||
|
||||
- [`loadLibraryFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadLibraryFromBlob) now takes an additional parameter `defaultStatus` which sets the default status of library item if not present, defaults to `unpublished` [#5067](https://github.com/excalidraw/excalidraw/pull/5067).
|
||||
|
||||
- Add [`UIOptions.dockedSidebarBreakpoint`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#dockedSidebarBreakpoint) to customize at which point to break from the docked sidebar [#5274](https://github.com/excalidraw/excalidraw/pull/5274).
|
||||
|
||||
- Added support for supplying user `id` in the Collaborator object (see `collaborators` in [`updateScene()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene)), which will be used to deduplicate users when rendering collaborator avatar list. Cursors will still be rendered for every user. [#5309](https://github.com/excalidraw/excalidraw/pull/5309)
|
||||
|
||||
@@ -482,8 +482,8 @@ You can add `customData` to elements when passing them as `initialData`, or usin
|
||||
|
||||
You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
|
||||
|
||||
| API | signature | Usage |
|
||||
| --- | --- | --- | --- |
|
||||
| API | Signature | Usage |
|
||||
| --- | --- | --- |
|
||||
| ready | `boolean` | This is set to true once Excalidraw is rendered |
|
||||
| readyPromise | [resolvablePromise](https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L317) | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readyPromise) |
|
||||
| [updateScene](#updateScene) | <code>(scene: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L207">sceneData</a>) => void </code> | updates the scene with the sceneData |
|
||||
@@ -496,11 +496,11 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
|
||||
| history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
|
||||
| scrollToContent | <code> (target?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106">ExcalidrawElement</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106">ExcalidrawElement</a>[]) => void </code> | Scroll the nearest element out of the elements supplied to the center. Defaults to the elements on the scene. |
|
||||
| refresh | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You don't have to call this when the position is changed on page scroll or when the excalidraw container resizes (we handle that ourselves). For any other cases if the position of excalidraw is updated (example due to scroll on parent container and not page scroll) you should call this API. |
|
||||
| [importLibrary](#importlibrary) | `(url: string, token?: string) => void` | Imports library from given URL |
|
||||
| [setToast](#setToast) | `({message: string, closable?:boolean, duration?:number} | null) => void` | This API can be used to show the toast with custom message. |
|
||||
| [importLibrary](#importlibrary) | <code>(url: string, token?: string) => void</code> | Imports library from given URL |
|
||||
| [setToast](#setToast) | <code>({ message: string, closable?:boolean, duration?:number } | null) => void</code> | This API can be used to show the toast with custom message. |
|
||||
| [id](#id) | string | Unique ID for the excalidraw component. |
|
||||
| [getFiles](#getFiles) | <code>() => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64">files</a> </code> | This API can be used to get the files present in the scene. It may contain files that aren't referenced by any element, so if you're persisting the files to a storage, you should compare them against stored elements. |
|
||||
| [setActiveTool](#setActiveTool) | <code>(tool: { type: typeof <a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L4">SHAPES</a>[number]["value"] | "eraser" } | { type: "custom"; customType: string }) => void</code> | This API can be used to set the active tool |
|
||||
| [setActiveTool](#setActiveTool) | <code>(tool: { type: typeof <a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L4">SHAPES</a> [number]["value"]| "eraser" } | { type: "custom"; customType: string }) => void</code> | This API can be used to set the active tool |
|
||||
| [setCursor](#setCursor) | <code>(cursor: string) => void </code> | This API can be used to set customise the mouse cursor on the canvas |
|
||||
| [resetCursor](#resetCursor) | <code>() => void </code> | This API can be used to reset to default mouse cursor on the canvas |
|
||||
|
||||
@@ -1101,10 +1101,10 @@ import { loadLibraryFromBlob } from "@excalidraw/excalidraw";
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
loadLibraryFromBlob(blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>)
|
||||
loadLibraryFromBlob(blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>, defaultStatus: "published" | "unpublished")
|
||||
</pre>
|
||||
|
||||
This function loads the library from the blob.
|
||||
This function loads the library from the blob. Additonally takes `defaultStatus` param which sets the default status for library item if not present, defaults to `unpublished`.
|
||||
|
||||
#### `loadFromBlob`
|
||||
|
||||
|
||||
@@ -330,7 +330,6 @@ export const _renderScene = ({
|
||||
if (canvas === null) {
|
||||
return { atLeastOneVisibleElement: false };
|
||||
}
|
||||
window.logTimeAverage("renderScene");
|
||||
const {
|
||||
renderScrollbars = true,
|
||||
renderSelection = true,
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("Test dragCreate", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -73,7 +73,7 @@ describe("Test dragCreate", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -104,7 +104,7 @@ describe("Test dragCreate", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -135,7 +135,7 @@ describe("Test dragCreate", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -170,7 +170,7 @@ describe("Test dragCreate", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("move element", () => {
|
||||
fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
@@ -120,7 +120,7 @@ describe("duplicate element on move when ALT is clicked", () => {
|
||||
fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
||||
@@ -102,7 +102,7 @@ describe("multi point mode in linear elements", () => {
|
||||
key: KEYS.ENTER,
|
||||
});
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(14);
|
||||
expect(renderScene).toHaveBeenCalledTimes(13);
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||
@@ -145,7 +145,7 @@ describe("multi point mode in linear elements", () => {
|
||||
key: KEYS.ENTER,
|
||||
});
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(14);
|
||||
expect(renderScene).toHaveBeenCalledTimes(13);
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||
|
||||
126
yarn.lock
126
yarn.lock
@@ -2998,7 +2998,7 @@ async-limiter@~1.0.0:
|
||||
resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
|
||||
async@^2.6.2, async@^2.6.4:
|
||||
async@^2.6.2:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
|
||||
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
|
||||
@@ -3269,13 +3269,6 @@ base@^0.11.1:
|
||||
mixin-deep "^1.2.0"
|
||||
pascalcase "^0.1.1"
|
||||
|
||||
basic-auth@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
|
||||
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
|
||||
dependencies:
|
||||
safe-buffer "5.1.2"
|
||||
|
||||
batch@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
|
||||
@@ -3734,14 +3727,6 @@ chalk@^4.0.0, chalk@^4.1.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
char-regex@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
|
||||
@@ -4164,11 +4149,6 @@ core-util-is@~1.0.0:
|
||||
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
corser@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
|
||||
integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==
|
||||
|
||||
cosmiconfig@^5.0.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
|
||||
@@ -4237,14 +4217,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
cross-env@7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
|
||||
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.1"
|
||||
|
||||
cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
@@ -4557,7 +4530,7 @@ debug@4:
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^3.1.1, debug@^3.2.6, debug@^3.2.7:
|
||||
debug@^3.1.1, debug@^3.2.6:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
|
||||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
||||
@@ -6337,13 +6310,6 @@ html-encoding-sniffer@^2.0.1:
|
||||
dependencies:
|
||||
whatwg-encoding "^1.0.5"
|
||||
|
||||
html-encoding-sniffer@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
|
||||
integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
|
||||
dependencies:
|
||||
whatwg-encoding "^2.0.0"
|
||||
|
||||
html-entities@^1.2.1, html-entities@^1.3.1:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz"
|
||||
@@ -6444,7 +6410,7 @@ http-proxy-middleware@0.19.1:
|
||||
lodash "^4.17.11"
|
||||
micromatch "^3.1.10"
|
||||
|
||||
http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||
http-proxy@^1.17.0:
|
||||
version "1.18.1"
|
||||
resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz"
|
||||
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
|
||||
@@ -6453,25 +6419,6 @@ http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||
follow-redirects "^1.0.0"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
http-server@14.1.1:
|
||||
version "14.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e"
|
||||
integrity sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==
|
||||
dependencies:
|
||||
basic-auth "^2.0.1"
|
||||
chalk "^4.1.2"
|
||||
corser "^2.0.1"
|
||||
he "^1.2.0"
|
||||
html-encoding-sniffer "^3.0.0"
|
||||
http-proxy "^1.18.1"
|
||||
mime "^1.6.0"
|
||||
minimist "^1.2.6"
|
||||
opener "^1.5.1"
|
||||
portfinder "^1.0.28"
|
||||
secure-compare "3.0.1"
|
||||
union "~0.5.0"
|
||||
url-join "^4.0.1"
|
||||
|
||||
https-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz"
|
||||
@@ -6514,13 +6461,6 @@ iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
icss-utils@^4.0.0, icss-utils@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz"
|
||||
@@ -8235,7 +8175,7 @@ mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24:
|
||||
dependencies:
|
||||
mime-db "1.46.0"
|
||||
|
||||
mime@1.6.0, mime@^1.6.0:
|
||||
mime@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
@@ -8282,7 +8222,7 @@ minimatch@3.0.4, minimatch@^3.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
@@ -8354,13 +8294,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mkdirp@^0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
||||
@@ -8771,11 +8704,6 @@ open@^7.0.2:
|
||||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
opener@^1.5.1:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
|
||||
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
|
||||
|
||||
opn@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz"
|
||||
@@ -9241,15 +9169,6 @@ portfinder@^1.0.26:
|
||||
debug "^3.1.1"
|
||||
mkdirp "^0.5.5"
|
||||
|
||||
portfinder@^1.0.28:
|
||||
version "1.0.32"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81"
|
||||
integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==
|
||||
dependencies:
|
||||
async "^2.6.4"
|
||||
debug "^3.2.7"
|
||||
mkdirp "^0.5.6"
|
||||
|
||||
posix-character-classes@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz"
|
||||
@@ -10144,13 +10063,6 @@ qs@6.7.0:
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.4.0:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
query-string@^4.1.0:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz"
|
||||
@@ -10838,7 +10750,7 @@ safe-regex@^1.1.0:
|
||||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0:
|
||||
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
@@ -10929,11 +10841,6 @@ schema-utils@^3.0.0:
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
secure-compare@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
|
||||
integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==
|
||||
|
||||
select-hose@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz"
|
||||
@@ -12125,13 +12032,6 @@ union-value@^1.0.0:
|
||||
is-extendable "^0.1.1"
|
||||
set-value "^2.0.1"
|
||||
|
||||
union@~0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075"
|
||||
integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==
|
||||
dependencies:
|
||||
qs "^6.4.0"
|
||||
|
||||
uniq@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz"
|
||||
@@ -12208,11 +12108,6 @@ urix@^0.1.0:
|
||||
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
|
||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||
|
||||
url-join@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
|
||||
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
|
||||
|
||||
url-loader@4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz"
|
||||
@@ -12519,13 +12414,6 @@ whatwg-encoding@^1.0.5:
|
||||
dependencies:
|
||||
iconv-lite "0.4.24"
|
||||
|
||||
whatwg-encoding@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
|
||||
integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
|
||||
dependencies:
|
||||
iconv-lite "0.6.3"
|
||||
|
||||
whatwg-fetch@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz"
|
||||
|
||||
Reference in New Issue
Block a user