Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d65536360 | |||
| d731a6463c | |||
| 9f325a626e | |||
| 41200ea28d |
+5
-2
@@ -31,8 +31,10 @@
|
|||||||
"@types/socket.io-client": "1.4.36",
|
"@types/socket.io-client": "1.4.36",
|
||||||
"browser-fs-access": "0.29.1",
|
"browser-fs-access": "0.29.1",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
"fake-indexeddb": "3.1.7",
|
"fake-indexeddb": "3.1.7",
|
||||||
"firebase": "8.3.3",
|
"firebase": "8.3.3",
|
||||||
|
"http-server": "14.1.1",
|
||||||
"i18next-browser-languagedetector": "6.1.4",
|
"i18next-browser-languagedetector": "6.1.4",
|
||||||
"idb-keyval": "6.0.3",
|
"idb-keyval": "6.0.3",
|
||||||
"image-blob-reduce": "3.0.1",
|
"image-blob-reduce": "3.0.1",
|
||||||
@@ -91,8 +93,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
"build:app:docker": "cross-env REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
||||||
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
"build:app": "cross-env REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
||||||
"build:version": "node ./scripts/build-version.js",
|
"build:version": "node ./scripts/build-version.js",
|
||||||
"build:prebuild": "node ./scripts/prebuild.js",
|
"build:prebuild": "node ./scripts/prebuild.js",
|
||||||
"build": "yarn build:prebuild && yarn build:app && yarn build:version",
|
"build": "yarn build:prebuild && yarn build:app && yarn build:version",
|
||||||
@@ -105,6 +107,7 @@
|
|||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||||
"start": "react-scripts start",
|
"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:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watchAll=false",
|
||||||
"test:app": "react-scripts test --passWithNoTests",
|
"test:app": "react-scripts test --passWithNoTests",
|
||||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||||
|
|||||||
+3
-1
@@ -18,4 +18,6 @@ const moveServiceWorkerScript = () => {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
moveServiceWorkerScript();
|
if (process.env.CI) {
|
||||||
|
moveServiceWorkerScript();
|
||||||
|
}
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ export const actionLoadScene = register({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, appState }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={load}
|
icon={load}
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ export class ActionManager {
|
|||||||
) {
|
) {
|
||||||
const action = this.actions[name];
|
const action = this.actions[name];
|
||||||
const PanelComponent = action.PanelComponent!;
|
const PanelComponent = action.PanelComponent!;
|
||||||
PanelComponent.displayName = "PanelComponent";
|
|
||||||
const elements = this.getElementsIncludingDeleted();
|
const elements = this.getElementsIncludingDeleted();
|
||||||
const appState = this.getAppState();
|
const appState = this.getAppState();
|
||||||
const updateData = (formState?: any) => {
|
const updateData = (formState?: any) => {
|
||||||
|
|||||||
+12
-54
@@ -26,17 +26,17 @@ import { ToolButton } from "./ToolButton";
|
|||||||
import { hasStrokeColor } from "../scene/comparisons";
|
import { hasStrokeColor } from "../scene/comparisons";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { hasBoundTextElement, isBoundToContainer } from "../element/typeChecks";
|
import { hasBoundTextElement, isBoundToContainer } from "../element/typeChecks";
|
||||||
import clsx from "clsx";
|
|
||||||
import { actionToggleZenMode } from "../actions";
|
|
||||||
|
|
||||||
export const SelectedShapeActions = ({
|
export const SelectedShapeActions = ({
|
||||||
appState,
|
appState,
|
||||||
elements,
|
elements,
|
||||||
renderAction,
|
renderAction,
|
||||||
|
activeTool,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
renderAction: ActionManager["renderAction"];
|
renderAction: ActionManager["renderAction"];
|
||||||
|
activeTool: AppState["activeTool"]["type"];
|
||||||
}) => {
|
}) => {
|
||||||
const targetElements = getTargetElements(
|
const targetElements = getTargetElements(
|
||||||
getNonDeletedElements(elements),
|
getNonDeletedElements(elements),
|
||||||
@@ -56,13 +56,13 @@ export const SelectedShapeActions = ({
|
|||||||
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||||
|
|
||||||
const showFillIcons =
|
const showFillIcons =
|
||||||
hasBackground(appState.activeTool.type) ||
|
hasBackground(activeTool) ||
|
||||||
targetElements.some(
|
targetElements.some(
|
||||||
(element) =>
|
(element) =>
|
||||||
hasBackground(element.type) && !isTransparent(element.backgroundColor),
|
hasBackground(element.type) && !isTransparent(element.backgroundColor),
|
||||||
);
|
);
|
||||||
const showChangeBackgroundIcons =
|
const showChangeBackgroundIcons =
|
||||||
hasBackground(appState.activeTool.type) ||
|
hasBackground(activeTool) ||
|
||||||
targetElements.some((element) => hasBackground(element.type));
|
targetElements.some((element) => hasBackground(element.type));
|
||||||
|
|
||||||
const showLinkIcon =
|
const showLinkIcon =
|
||||||
@@ -79,23 +79,23 @@ export const SelectedShapeActions = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panelColumn">
|
<div className="panelColumn">
|
||||||
{((hasStrokeColor(appState.activeTool.type) &&
|
{((hasStrokeColor(activeTool) &&
|
||||||
appState.activeTool.type !== "image" &&
|
activeTool !== "image" &&
|
||||||
commonSelectedType !== "image") ||
|
commonSelectedType !== "image") ||
|
||||||
targetElements.some((element) => hasStrokeColor(element.type))) &&
|
targetElements.some((element) => hasStrokeColor(element.type))) &&
|
||||||
renderAction("changeStrokeColor")}
|
renderAction("changeStrokeColor")}
|
||||||
{showChangeBackgroundIcons && renderAction("changeBackgroundColor")}
|
{showChangeBackgroundIcons && renderAction("changeBackgroundColor")}
|
||||||
{showFillIcons && renderAction("changeFillStyle")}
|
{showFillIcons && renderAction("changeFillStyle")}
|
||||||
|
|
||||||
{(hasStrokeWidth(appState.activeTool.type) ||
|
{(hasStrokeWidth(activeTool) ||
|
||||||
targetElements.some((element) => hasStrokeWidth(element.type))) &&
|
targetElements.some((element) => hasStrokeWidth(element.type))) &&
|
||||||
renderAction("changeStrokeWidth")}
|
renderAction("changeStrokeWidth")}
|
||||||
|
|
||||||
{(appState.activeTool.type === "freedraw" ||
|
{(activeTool === "freedraw" ||
|
||||||
targetElements.some((element) => element.type === "freedraw")) &&
|
targetElements.some((element) => element.type === "freedraw")) &&
|
||||||
renderAction("changeStrokeShape")}
|
renderAction("changeStrokeShape")}
|
||||||
|
|
||||||
{(hasStrokeStyle(appState.activeTool.type) ||
|
{(hasStrokeStyle(activeTool) ||
|
||||||
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
||||||
<>
|
<>
|
||||||
{renderAction("changeStrokeStyle")}
|
{renderAction("changeStrokeStyle")}
|
||||||
@@ -103,12 +103,12 @@ export const SelectedShapeActions = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(canChangeSharpness(appState.activeTool.type) ||
|
{(canChangeSharpness(activeTool) ||
|
||||||
targetElements.some((element) => canChangeSharpness(element.type))) && (
|
targetElements.some((element) => canChangeSharpness(element.type))) && (
|
||||||
<>{renderAction("changeSharpness")}</>
|
<>{renderAction("changeSharpness")}</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(hasText(appState.activeTool.type) ||
|
{(hasText(activeTool) ||
|
||||||
targetElements.some((element) => hasText(element.type))) && (
|
targetElements.some((element) => hasText(element.type))) && (
|
||||||
<>
|
<>
|
||||||
{renderAction("changeFontSize")}
|
{renderAction("changeFontSize")}
|
||||||
@@ -123,7 +123,7 @@ export const SelectedShapeActions = ({
|
|||||||
(element) =>
|
(element) =>
|
||||||
hasBoundTextElement(element) || isBoundToContainer(element),
|
hasBoundTextElement(element) || isBoundToContainer(element),
|
||||||
) && renderAction("changeVerticalAlign")}
|
) && renderAction("changeVerticalAlign")}
|
||||||
{(canHaveArrowheads(appState.activeTool.type) ||
|
{(canHaveArrowheads(activeTool) ||
|
||||||
targetElements.some((element) => canHaveArrowheads(element.type))) && (
|
targetElements.some((element) => canHaveArrowheads(element.type))) && (
|
||||||
<>{renderAction("changeArrowhead")}</>
|
<>{renderAction("changeArrowhead")}</>
|
||||||
)}
|
)}
|
||||||
@@ -271,45 +271,3 @@ export const ZoomActions = ({
|
|||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Stack.Col>
|
</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>
|
|
||||||
);
|
|
||||||
|
|||||||
+178
-89
@@ -264,6 +264,101 @@ import {
|
|||||||
} from "../element/Hyperlink";
|
} from "../element/Hyperlink";
|
||||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
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 = {
|
const deviceContextInitialValue = {
|
||||||
isSmScreen: false,
|
isSmScreen: false,
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
@@ -272,7 +367,6 @@ const deviceContextInitialValue = {
|
|||||||
};
|
};
|
||||||
const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
|
const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
|
||||||
export const useDevice = () => useContext<Device>(DeviceContext);
|
export const useDevice = () => useContext<Device>(DeviceContext);
|
||||||
|
|
||||||
const ExcalidrawContainerContext = React.createContext<{
|
const ExcalidrawContainerContext = React.createContext<{
|
||||||
container: HTMLDivElement | null;
|
container: HTMLDivElement | null;
|
||||||
id: string | null;
|
id: string | null;
|
||||||
@@ -280,22 +374,6 @@ const ExcalidrawContainerContext = React.createContext<{
|
|||||||
export const useExcalidrawContainer = () =>
|
export const useExcalidrawContainer = () =>
|
||||||
useContext(ExcalidrawContainerContext);
|
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 didTapTwice: boolean = false;
|
||||||
let tappedTwiceTimer = 0;
|
let tappedTwiceTimer = 0;
|
||||||
let cursorX = 0;
|
let cursorX = 0;
|
||||||
@@ -493,6 +571,26 @@ 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() {
|
public render() {
|
||||||
const selectedElement = getSelectedElements(
|
const selectedElement = getSelectedElements(
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
@@ -522,69 +620,65 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
value={this.excalidrawContainerValue}
|
value={this.excalidrawContainerValue}
|
||||||
>
|
>
|
||||||
<DeviceContext.Provider value={this.device}>
|
<DeviceContext.Provider value={this.device}>
|
||||||
<ExcalidrawAppStateContext.Provider value={this.state}>
|
{this.__renderUI && (
|
||||||
<ExcalidrawElementsContext.Provider
|
<LayerUI
|
||||||
value={this.scene.getNonDeletedElements()}
|
canvas={this.canvas}
|
||||||
>
|
appState={this.state}
|
||||||
<LayerUI
|
files={this.files}
|
||||||
canvas={this.canvas}
|
setAppState={this.setAppState}
|
||||||
appState={this.state}
|
actionManager={this.actionManager}
|
||||||
files={this.files}
|
elements={this.scene.getNonDeletedElements()}
|
||||||
setAppState={this.setAppState}
|
onCollabButtonClick={onCollabButtonClick}
|
||||||
actionManager={this.actionManager}
|
onLockToggle={this.toggleLock}
|
||||||
elements={this.scene.getNonDeletedElements()}
|
onPenModeToggle={this.togglePenMode}
|
||||||
onCollabButtonClick={onCollabButtonClick}
|
onInsertElements={(elements) =>
|
||||||
onLockToggle={this.toggleLock}
|
this.addElementsFromPasteOrLibrary({
|
||||||
onPenModeToggle={this.togglePenMode}
|
elements,
|
||||||
onInsertElements={(elements) =>
|
position: "center",
|
||||||
this.addElementsFromPasteOrLibrary({
|
files: null,
|
||||||
elements,
|
})
|
||||||
position: "center",
|
}
|
||||||
files: null,
|
langCode={getLanguage().code}
|
||||||
})
|
isCollaborating={this.props.isCollaborating}
|
||||||
}
|
renderTopRightUI={renderTopRightUI}
|
||||||
langCode={getLanguage().code}
|
renderCustomFooter={renderFooter}
|
||||||
isCollaborating={this.props.isCollaborating}
|
renderCustomStats={renderCustomStats}
|
||||||
renderTopRightUI={renderTopRightUI}
|
showExitZenModeBtn={
|
||||||
renderCustomFooter={renderFooter}
|
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||||
renderCustomStats={renderCustomStats}
|
this.state.zenModeEnabled
|
||||||
showExitZenModeBtn={
|
}
|
||||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
showThemeBtn={
|
||||||
this.state.zenModeEnabled
|
typeof this.props?.theme === "undefined" &&
|
||||||
}
|
this.props.UIOptions.canvasActions.theme
|
||||||
showThemeBtn={
|
}
|
||||||
typeof this.props?.theme === "undefined" &&
|
libraryReturnUrl={this.props.libraryReturnUrl}
|
||||||
this.props.UIOptions.canvasActions.theme
|
UIOptions={this.props.UIOptions}
|
||||||
}
|
focusContainer={this.focusContainer}
|
||||||
libraryReturnUrl={this.props.libraryReturnUrl}
|
library={this.library}
|
||||||
UIOptions={this.props.UIOptions}
|
id={this.id}
|
||||||
focusContainer={this.focusContainer}
|
onImageAction={this.onImageAction}
|
||||||
library={this.library}
|
/>
|
||||||
id={this.id}
|
)}
|
||||||
onImageAction={this.onImageAction}
|
<div className="excalidraw-textEditorContainer" />
|
||||||
/>
|
<div className="excalidraw-contextMenuContainer" />
|
||||||
<div className="excalidraw-textEditorContainer" />
|
{selectedElement.length === 1 && this.state.showHyperlinkPopup && (
|
||||||
<div className="excalidraw-contextMenuContainer" />
|
<Hyperlink
|
||||||
{selectedElement.length === 1 &&
|
key={selectedElement[0].id}
|
||||||
this.state.showHyperlinkPopup && (
|
element={selectedElement[0]}
|
||||||
<Hyperlink
|
appState={this.state}
|
||||||
key={selectedElement[0].id}
|
setAppState={this.setAppState}
|
||||||
element={selectedElement[0]}
|
onLinkOpen={this.props.onLinkOpen}
|
||||||
setAppState={this.setAppState}
|
/>
|
||||||
onLinkOpen={this.props.onLinkOpen}
|
)}
|
||||||
/>
|
{this.state.toast !== null && (
|
||||||
)}
|
<Toast
|
||||||
{this.state.toast !== null && (
|
message={this.state.toast.message}
|
||||||
<Toast
|
onClose={() => this.setToast(null)}
|
||||||
message={this.state.toast.message}
|
duration={this.state.toast.duration}
|
||||||
onClose={() => this.setToast(null)}
|
closable={this.state.toast.closable}
|
||||||
duration={this.state.toast.duration}
|
/>
|
||||||
closable={this.state.toast.closable}
|
)}
|
||||||
/>
|
<main>{this.renderCanvas()}</main>
|
||||||
)}
|
|
||||||
<main>{this.renderCanvas()}</main>
|
|
||||||
</ExcalidrawElementsContext.Provider>{" "}
|
|
||||||
</ExcalidrawAppStateContext.Provider>
|
|
||||||
</DeviceContext.Provider>
|
</DeviceContext.Provider>
|
||||||
</ExcalidrawContainerContext.Provider>
|
</ExcalidrawContainerContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
@@ -843,7 +937,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV === ENV.TEST ||
|
process.env.NODE_ENV === ENV.TEST ||
|
||||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
process.env.NODE_ENV === ENV.DEVELOPMENT ||
|
||||||
|
process.env.REACT_APP_VERCEL_ENV === "preview"
|
||||||
) {
|
) {
|
||||||
const setState = this.setState.bind(this);
|
const setState = this.setState.bind(this);
|
||||||
Object.defineProperties(window.h, {
|
Object.defineProperties(window.h, {
|
||||||
@@ -6166,13 +6261,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width === 0 || height === 0) {
|
|
||||||
if (cb) {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
width,
|
width,
|
||||||
@@ -6233,7 +6321,8 @@ declare global {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV === ENV.TEST ||
|
process.env.NODE_ENV === ENV.TEST ||
|
||||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
process.env.NODE_ENV === ENV.DEVELOPMENT ||
|
||||||
|
process.env.REACT_APP_VERCEL_ENV === "preview"
|
||||||
) {
|
) {
|
||||||
window.h = window.h || ({} as Window["h"]);
|
window.h = window.h || ({} as Window["h"]);
|
||||||
|
|
||||||
|
|||||||
@@ -343,8 +343,6 @@ const ColorInput = React.forwardRef(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ColorInput.displayName = "ColorInput";
|
|
||||||
|
|
||||||
export const ColorPicker = ({
|
export const ColorPicker = ({
|
||||||
type,
|
type,
|
||||||
color,
|
color,
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
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;
|
|
||||||
+177
-75
@@ -10,7 +10,7 @@ import { calculateScrollCenter, getSelectedElements } from "../scene";
|
|||||||
import { ExportType } from "../scene/types";
|
import { ExportType } from "../scene/types";
|
||||||
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
||||||
import { muteFSAbortError } from "../utils";
|
import { muteFSAbortError } from "../utils";
|
||||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
import CollabButton from "./CollabButton";
|
import CollabButton from "./CollabButton";
|
||||||
import { ErrorDialog } from "./ErrorDialog";
|
import { ErrorDialog } from "./ErrorDialog";
|
||||||
@@ -39,7 +39,7 @@ import { trackEvent } from "../analytics";
|
|||||||
import { useDevice } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
import { actionToggleStats } from "../actions/actionToggleStats";
|
import { actionToggleStats } from "../actions/actionToggleStats";
|
||||||
import Footer from "./Footer";
|
import { actionToggleZenMode } from "../actions";
|
||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
@@ -71,8 +71,8 @@ const LayerUI = ({
|
|||||||
appState,
|
appState,
|
||||||
files,
|
files,
|
||||||
setAppState,
|
setAppState,
|
||||||
elements,
|
|
||||||
canvas,
|
canvas,
|
||||||
|
elements,
|
||||||
onCollabButtonClick,
|
onCollabButtonClick,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onPenModeToggle,
|
onPenModeToggle,
|
||||||
@@ -210,8 +210,8 @@ const LayerUI = ({
|
|||||||
)}
|
)}
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
<BackgroundPickerAndDarkModeToggle
|
<BackgroundPickerAndDarkModeToggle
|
||||||
appState={appState}
|
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
|
appState={appState}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
showThemeBtn={showThemeBtn}
|
showThemeBtn={showThemeBtn}
|
||||||
/>
|
/>
|
||||||
@@ -244,6 +244,7 @@ const LayerUI = ({
|
|||||||
appState={appState}
|
appState={appState}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
renderAction={actionManager.renderAction}
|
renderAction={actionManager.renderAction}
|
||||||
|
activeTool={appState.activeTool.type}
|
||||||
/>
|
/>
|
||||||
</Island>
|
</Island>
|
||||||
</Section>
|
</Section>
|
||||||
@@ -278,6 +279,7 @@ const LayerUI = ({
|
|||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
focusContainer={focusContainer}
|
focusContainer={focusContainer}
|
||||||
library={library}
|
library={library}
|
||||||
|
theme={appState.theme}
|
||||||
files={files}
|
files={files}
|
||||||
id={id}
|
id={id}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
@@ -381,7 +383,100 @@ const LayerUI = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 = (
|
||||||
<>
|
<>
|
||||||
{appState.isLoading && <LoadingMessage delay={250} />}
|
{appState.isLoading && <LoadingMessage delay={250} />}
|
||||||
{appState.errorMessage && (
|
{appState.errorMessage && (
|
||||||
@@ -409,77 +504,84 @@ 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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!device.isMobile && (
|
const renderStats = () => {
|
||||||
<div
|
if (!appState.showStats) {
|
||||||
className={clsx("layer-ui__wrapper", {
|
return null;
|
||||||
"disable-pointerEvents":
|
}
|
||||||
appState.draggingElement ||
|
return (
|
||||||
appState.resizingElement ||
|
<Stats
|
||||||
(appState.editingElement &&
|
appState={appState}
|
||||||
!isTextElement(appState.editingElement)),
|
setAppState={setAppState}
|
||||||
})}
|
elements={elements}
|
||||||
style={
|
onClose={() => {
|
||||||
appState.isLibraryOpen &&
|
actionManager.executeAction(actionToggleStats);
|
||||||
appState.isLibraryMenuDocked &&
|
}}
|
||||||
device.canDeviceFitSidebar
|
renderCustomStats={renderCustomStats}
|
||||||
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
/>
|
||||||
: {}
|
);
|
||||||
}
|
};
|
||||||
>
|
|
||||||
{renderFixedSideContainer()}
|
return device.isMobile ? (
|
||||||
<Footer
|
<>
|
||||||
appState={appState}
|
{dialogs}
|
||||||
actionManager={actionManager}
|
<MobileMenu
|
||||||
renderCustomFooter={renderCustomFooter}
|
appState={appState}
|
||||||
showExitZenModeBtn={showExitZenModeBtn}
|
elements={elements}
|
||||||
/>
|
actionManager={actionManager}
|
||||||
{appState.showStats && (
|
libraryMenu={libraryMenu}
|
||||||
<Stats
|
renderJSONExportDialog={renderJSONExportDialog}
|
||||||
appState={appState}
|
renderImageExportDialog={renderImageExportDialog}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
elements={elements}
|
onCollabButtonClick={onCollabButtonClick}
|
||||||
onClose={() => {
|
onLockToggle={() => onLockToggle()}
|
||||||
actionManager.executeAction(actionToggleStats);
|
onPenModeToggle={onPenModeToggle}
|
||||||
}}
|
canvas={canvas}
|
||||||
renderCustomStats={renderCustomStats}
|
isCollaborating={isCollaborating}
|
||||||
/>
|
renderCustomFooter={renderCustomFooter}
|
||||||
)}
|
showThemeBtn={showThemeBtn}
|
||||||
{appState.scrolledOutside && (
|
onImageAction={onImageAction}
|
||||||
<button
|
renderTopRightUI={renderTopRightUI}
|
||||||
className="scroll-back-to-content"
|
renderStats={renderStats}
|
||||||
onClick={() => {
|
/>
|
||||||
setAppState({
|
</>
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
) : (
|
||||||
});
|
<>
|
||||||
}}
|
<div
|
||||||
>
|
className={clsx("layer-ui__wrapper", {
|
||||||
{t("buttons.scrollBackToContent")}
|
"disable-pointerEvents":
|
||||||
</button>
|
appState.draggingElement ||
|
||||||
)}
|
appState.resizingElement ||
|
||||||
</div>
|
(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>
|
||||||
{appState.isLibraryOpen && (
|
{appState.isLibraryOpen && (
|
||||||
<div className="layer-ui__sidebar">{libraryMenu}</div>
|
<div className="layer-ui__sidebar">{libraryMenu}</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const LibraryMenu = ({
|
|||||||
onInsertLibraryItems,
|
onInsertLibraryItems,
|
||||||
pendingElements,
|
pendingElements,
|
||||||
onAddToLibrary,
|
onAddToLibrary,
|
||||||
|
theme,
|
||||||
setAppState,
|
setAppState,
|
||||||
files,
|
files,
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
@@ -92,6 +93,7 @@ export const LibraryMenu = ({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
||||||
onAddToLibrary: () => void;
|
onAddToLibrary: () => void;
|
||||||
|
theme: AppState["theme"];
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
@@ -103,6 +105,7 @@ export const LibraryMenu = ({
|
|||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
|
|
||||||
useOnClickOutside(
|
useOnClickOutside(
|
||||||
ref,
|
ref,
|
||||||
useCallback(
|
useCallback(
|
||||||
@@ -287,7 +290,7 @@ export const LibraryMenu = ({
|
|||||||
appState={appState}
|
appState={appState}
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
library={library}
|
library={library}
|
||||||
theme={appState.theme}
|
theme={theme}
|
||||||
files={files}
|
files={files}
|
||||||
id={id}
|
id={id}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { AppState } from "../types";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
@@ -18,8 +18,6 @@ import { UserList } from "./UserList";
|
|||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
import { LibraryButton } from "./LibraryButton";
|
import { LibraryButton } from "./LibraryButton";
|
||||||
import { PenModeButton } from "./PenModeButton";
|
import { PenModeButton } from "./PenModeButton";
|
||||||
import { Stats } from "./Stats";
|
|
||||||
import { actionToggleStats } from "../actions";
|
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -44,7 +42,7 @@ type MobileMenuProps = {
|
|||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
renderStats: () => JSX.Element | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MobileMenu = ({
|
export const MobileMenu = ({
|
||||||
@@ -64,7 +62,7 @@ export const MobileMenu = ({
|
|||||||
showThemeBtn,
|
showThemeBtn,
|
||||||
onImageAction,
|
onImageAction,
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
renderCustomStats,
|
renderStats,
|
||||||
}: MobileMenuProps) => {
|
}: MobileMenuProps) => {
|
||||||
const renderToolbar = () => {
|
const renderToolbar = () => {
|
||||||
return (
|
return (
|
||||||
@@ -186,17 +184,7 @@ export const MobileMenu = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!appState.viewModeEnabled && renderToolbar()}
|
{!appState.viewModeEnabled && renderToolbar()}
|
||||||
{!appState.openMenu && appState.showStats && (
|
{renderStats()}
|
||||||
<Stats
|
|
||||||
appState={appState}
|
|
||||||
setAppState={setAppState}
|
|
||||||
elements={elements}
|
|
||||||
onClose={() => {
|
|
||||||
actionManager.executeAction(actionToggleStats);
|
|
||||||
}}
|
|
||||||
renderCustomStats={renderCustomStats}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className="App-bottom-bar"
|
className="App-bottom-bar"
|
||||||
style={{
|
style={{
|
||||||
@@ -233,6 +221,7 @@ export const MobileMenu = ({
|
|||||||
appState={appState}
|
appState={appState}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
renderAction={actionManager.renderAction}
|
renderAction={actionManager.renderAction}
|
||||||
|
activeTool={appState.activeTool.type}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { getCommonBounds } from "../element/bounds";
|
import { getCommonBounds } from "../element/bounds";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { useDevice } from "../components/App";
|
||||||
import { getTargetElements } from "../scene";
|
import { getTargetElements } from "../scene";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { AppState, ExcalidrawProps } from "../types";
|
||||||
import { close } from "./icons";
|
import { close } from "./icons";
|
||||||
@@ -15,10 +16,13 @@ export const Stats = (props: {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||||
}) => {
|
}) => {
|
||||||
|
const device = useDevice();
|
||||||
const boundingBox = getCommonBounds(props.elements);
|
const boundingBox = getCommonBounds(props.elements);
|
||||||
const selectedElements = getTargetElements(props.elements, props.appState);
|
const selectedElements = getTargetElements(props.elements, props.appState);
|
||||||
const selectedBoundingBox = getCommonBounds(selectedElements);
|
const selectedBoundingBox = getCommonBounds(selectedElements);
|
||||||
|
if (device.isMobile && props.appState.openMenu) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="Stats">
|
<div className="Stats">
|
||||||
<Island padding={2}>
|
<Island padding={2}>
|
||||||
|
|||||||
@@ -187,5 +187,3 @@ ToolButton.defaultProps = {
|
|||||||
className: "",
|
className: "",
|
||||||
size: "medium",
|
size: "medium",
|
||||||
};
|
};
|
||||||
|
|
||||||
ToolButton.displayName = "ToolButton";
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { getElementAbsoluteCoords } from "./";
|
|||||||
|
|
||||||
import "./Hyperlink.scss";
|
import "./Hyperlink.scss";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { useExcalidrawAppState } from "../components/App";
|
|
||||||
|
|
||||||
const CONTAINER_WIDTH = 320;
|
const CONTAINER_WIDTH = 320;
|
||||||
const SPACE_BOTTOM = 85;
|
const SPACE_BOTTOM = 85;
|
||||||
@@ -49,15 +48,15 @@ let IS_HYPERLINK_TOOLTIP_VISIBLE = false;
|
|||||||
|
|
||||||
export const Hyperlink = ({
|
export const Hyperlink = ({
|
||||||
element,
|
element,
|
||||||
|
appState,
|
||||||
setAppState,
|
setAppState,
|
||||||
onLinkOpen,
|
onLinkOpen,
|
||||||
}: {
|
}: {
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
appState: AppState;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
||||||
}) => {
|
}) => {
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
|
|
||||||
const linkVal = element.link || "";
|
const linkVal = element.link || "";
|
||||||
|
|
||||||
const [inputVal, setInputVal] = useState(linkVal);
|
const [inputVal, setInputVal] = useState(linkVal);
|
||||||
|
|||||||
+3
-10
@@ -107,19 +107,12 @@ const solveQuadratic = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const t1 = (-b + Math.sqrt(sqrtPart)) / (2 * a);
|
||||||
|
const t2 = (-b - Math.sqrt(sqrtPart)) / (2 * a);
|
||||||
|
|
||||||
let s1 = null;
|
let s1 = null;
|
||||||
let s2 = 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) {
|
if (t1 >= 0 && t1 <= 1) {
|
||||||
s1 = getBezierValueForT(t1, p0, p1, p2, p3);
|
s1 = getBezierValueForT(t1, p0, p1, p2, p3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -686,9 +686,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const point = element.points[index];
|
const point = element.points[index];
|
||||||
const { x, y } = element;
|
const { x, y } = element;
|
||||||
return point
|
return rotate(x + point[0], y + point[1], cx, cy, element.angle);
|
||||||
? rotate(x + point[0], y + point[1], cx, cy, element.angle)
|
|
||||||
: rotate(x, y, cx, cy, element.angle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static pointFromAbsoluteCoords(
|
static pointFromAbsoluteCoords(
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import {
|
|||||||
import { DEFAULT_VERSION } from "../constants";
|
import { DEFAULT_VERSION } from "../constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { copyTextToSystemClipboard } from "../clipboard";
|
import { copyTextToSystemClipboard } from "../clipboard";
|
||||||
import { AppState } from "../types";
|
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
|
||||||
type StorageSizes = { scene: number; total: number };
|
type StorageSizes = { scene: number; total: number };
|
||||||
|
|
||||||
const STORAGE_SIZE_TIMEOUT = 500;
|
const STORAGE_SIZE_TIMEOUT = 500;
|
||||||
@@ -22,8 +20,6 @@ const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setToast: (message: string) => void;
|
setToast: (message: string) => void;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
|
||||||
appState: AppState;
|
|
||||||
};
|
};
|
||||||
const CustomStats = (props: Props) => {
|
const CustomStats = (props: Props) => {
|
||||||
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
||||||
@@ -35,7 +31,7 @@ const CustomStats = (props: Props) => {
|
|||||||
getStorageSizes((sizes) => {
|
getStorageSizes((sizes) => {
|
||||||
setStorageSizes(sizes);
|
setStorageSizes(sizes);
|
||||||
});
|
});
|
||||||
}, [props.elements, props.appState]);
|
});
|
||||||
useEffect(() => () => getStorageSizes.cancel(), []);
|
useEffect(() => () => getStorageSizes.cancel(), []);
|
||||||
|
|
||||||
const version = getVersion();
|
const version = getVersion();
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV === ENV.TEST ||
|
process.env.NODE_ENV === ENV.TEST ||
|
||||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
process.env.NODE_ENV === ENV.DEVELOPMENT ||
|
||||||
|
process.env.REACT_APP_VERCEL_ENV === "preview"
|
||||||
) {
|
) {
|
||||||
window.collab = window.collab || ({} as Window["collab"]);
|
window.collab = window.collab || ({} as Window["collab"]);
|
||||||
Object.defineProperties(window, {
|
Object.defineProperties(window, {
|
||||||
|
|||||||
@@ -666,15 +666,10 @@ const ExcalidrawWrapper = () => {
|
|||||||
[langCode],
|
[langCode],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderCustomStats = (
|
const renderCustomStats = () => {
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
|
||||||
appState: AppState,
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<CustomStats
|
<CustomStats
|
||||||
setToast={(message) => excalidrawAPI!.setToast({ message })}
|
setToast={(message) => excalidrawAPI!.setToast({ message })}
|
||||||
appState={appState}
|
|
||||||
elements={elements}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+3
@@ -16,6 +16,9 @@ interface Window {
|
|||||||
EXCALIDRAW_EXPORT_SOURCE: string;
|
EXCALIDRAW_EXPORT_SOURCE: string;
|
||||||
EXCALIDRAW_THROTTLE_RENDER: boolean | undefined;
|
EXCALIDRAW_THROTTLE_RENDER: boolean | undefined;
|
||||||
gtag: Function;
|
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
|
// https://github.com/facebook/create-react-app/blob/ddcb7d5/packages/react-scripts/lib/react-app.d.ts
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
#### Features
|
#### 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).
|
- 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)
|
- 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:
|
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 |
|
| 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) |
|
| 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 |
|
| [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 |
|
| 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. |
|
| 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. |
|
| 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) | <code>(url: string, token?: string) => void</code> | Imports library from given URL |
|
| [importLibrary](#importlibrary) | `(url: string, token?: string) => void` | 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. |
|
| [setToast](#setToast) | `({message: string, closable?:boolean, duration?:number} | null) => void` | This API can be used to show the toast with custom message. |
|
||||||
| [id](#id) | string | Unique ID for the excalidraw component. |
|
| [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. |
|
| [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 |
|
| [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 |
|
| [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_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
loadLibraryFromBlob(blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>, defaultStatus: "published" | "unpublished")
|
loadLibraryFromBlob(blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
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`.
|
This function loads the library from the blob.
|
||||||
|
|
||||||
#### `loadFromBlob`
|
#### `loadFromBlob`
|
||||||
|
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ export const _renderScene = ({
|
|||||||
if (canvas === null) {
|
if (canvas === null) {
|
||||||
return { atLeastOneVisibleElement: false };
|
return { atLeastOneVisibleElement: false };
|
||||||
}
|
}
|
||||||
|
window.logTimeAverage("renderScene");
|
||||||
const {
|
const {
|
||||||
renderScrollbars = true,
|
renderScrollbars = true,
|
||||||
renderSelection = true,
|
renderSelection = true,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe("Test dragCreate", () => {
|
|||||||
// finish (position does not matter)
|
// finish (position does not matter)
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
|
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
@@ -73,7 +73,7 @@ describe("Test dragCreate", () => {
|
|||||||
// finish (position does not matter)
|
// finish (position does not matter)
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
|
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
@@ -104,7 +104,7 @@ describe("Test dragCreate", () => {
|
|||||||
// finish (position does not matter)
|
// finish (position does not matter)
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
|
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
@@ -135,7 +135,7 @@ describe("Test dragCreate", () => {
|
|||||||
// finish (position does not matter)
|
// finish (position does not matter)
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
|
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
@@ -170,7 +170,7 @@ describe("Test dragCreate", () => {
|
|||||||
// finish (position does not matter)
|
// finish (position does not matter)
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
|
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe("move element", () => {
|
|||||||
fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
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.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
||||||
fireEvent.pointerUp(canvas);
|
fireEvent.pointerUp(canvas);
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
expect(renderScene).toHaveBeenCalledTimes(8);
|
||||||
expect(h.state.selectionElement).toBeNull();
|
expect(h.state.selectionElement).toBeNull();
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ describe("multi point mode in linear elements", () => {
|
|||||||
key: KEYS.ENTER,
|
key: KEYS.ENTER,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(13);
|
expect(renderScene).toHaveBeenCalledTimes(14);
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
|
|
||||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||||
@@ -145,7 +145,7 @@ describe("multi point mode in linear elements", () => {
|
|||||||
key: KEYS.ENTER,
|
key: KEYS.ENTER,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(renderScene).toHaveBeenCalledTimes(13);
|
expect(renderScene).toHaveBeenCalledTimes(14);
|
||||||
expect(h.elements.length).toEqual(1);
|
expect(h.elements.length).toEqual(1);
|
||||||
|
|
||||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||||
|
|||||||
@@ -2998,7 +2998,7 @@ async-limiter@~1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz"
|
||||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||||
|
|
||||||
async@^2.6.2:
|
async@^2.6.2, async@^2.6.4:
|
||||||
version "2.6.4"
|
version "2.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
|
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
|
||||||
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
|
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
|
||||||
@@ -3269,6 +3269,13 @@ base@^0.11.1:
|
|||||||
mixin-deep "^1.2.0"
|
mixin-deep "^1.2.0"
|
||||||
pascalcase "^0.1.1"
|
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:
|
batch@0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
|
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
|
||||||
@@ -3727,6 +3734,14 @@ chalk@^4.0.0, chalk@^4.1.0:
|
|||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.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:
|
char-regex@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
|
||||||
@@ -4149,6 +4164,11 @@ core-util-is@~1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
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:
|
cosmiconfig@^5.0.0:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
|
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
|
||||||
@@ -4217,7 +4237,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
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:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
|
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
|
||||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||||
@@ -4530,7 +4557,7 @@ debug@4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
debug@^3.1.1, debug@^3.2.6:
|
debug@^3.1.1, debug@^3.2.6, debug@^3.2.7:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
|
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
|
||||||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
||||||
@@ -6310,6 +6337,13 @@ html-encoding-sniffer@^2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
whatwg-encoding "^1.0.5"
|
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:
|
html-entities@^1.2.1, html-entities@^1.3.1:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz"
|
||||||
@@ -6410,7 +6444,7 @@ http-proxy-middleware@0.19.1:
|
|||||||
lodash "^4.17.11"
|
lodash "^4.17.11"
|
||||||
micromatch "^3.1.10"
|
micromatch "^3.1.10"
|
||||||
|
|
||||||
http-proxy@^1.17.0:
|
http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||||
version "1.18.1"
|
version "1.18.1"
|
||||||
resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz"
|
resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz"
|
||||||
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
|
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
|
||||||
@@ -6419,6 +6453,25 @@ http-proxy@^1.17.0:
|
|||||||
follow-redirects "^1.0.0"
|
follow-redirects "^1.0.0"
|
||||||
requires-port "^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:
|
https-browserify@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz"
|
||||||
@@ -6461,6 +6514,13 @@ iconv-lite@0.4.24:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
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:
|
icss-utils@^4.0.0, icss-utils@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz"
|
||||||
@@ -8175,7 +8235,7 @@ mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db "1.46.0"
|
mime-db "1.46.0"
|
||||||
|
|
||||||
mime@1.6.0:
|
mime@1.6.0, mime@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
|
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
|
||||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
@@ -8222,7 +8282,7 @@ minimatch@3.0.4, minimatch@^3.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
|
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||||
version "1.2.6"
|
version "1.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
@@ -8294,6 +8354,13 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
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:
|
mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
||||||
@@ -8704,6 +8771,11 @@ open@^7.0.2:
|
|||||||
is-docker "^2.0.0"
|
is-docker "^2.0.0"
|
||||||
is-wsl "^2.1.1"
|
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:
|
opn@^5.5.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz"
|
resolved "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz"
|
||||||
@@ -9169,6 +9241,15 @@ portfinder@^1.0.26:
|
|||||||
debug "^3.1.1"
|
debug "^3.1.1"
|
||||||
mkdirp "^0.5.5"
|
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:
|
posix-character-classes@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz"
|
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz"
|
||||||
@@ -10063,6 +10144,13 @@ qs@6.7.0:
|
|||||||
resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz"
|
resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz"
|
||||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
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:
|
query-string@^4.1.0:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz"
|
resolved "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz"
|
||||||
@@ -10750,7 +10838,7 @@ safe-regex@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ret "~0.1.10"
|
ret "~0.1.10"
|
||||||
|
|
||||||
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0:
|
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
@@ -10841,6 +10929,11 @@ schema-utils@^3.0.0:
|
|||||||
ajv "^6.12.5"
|
ajv "^6.12.5"
|
||||||
ajv-keywords "^3.5.2"
|
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:
|
select-hose@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz"
|
||||||
@@ -12032,6 +12125,13 @@ union-value@^1.0.0:
|
|||||||
is-extendable "^0.1.1"
|
is-extendable "^0.1.1"
|
||||||
set-value "^2.0.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:
|
uniq@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz"
|
||||||
@@ -12108,6 +12208,11 @@ urix@^0.1.0:
|
|||||||
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
|
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
|
||||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
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:
|
url-loader@4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz"
|
||||||
@@ -12414,6 +12519,13 @@ whatwg-encoding@^1.0.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
iconv-lite "0.4.24"
|
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:
|
whatwg-fetch@2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz"
|
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user