feat: add sidebar for libraries panel (#5274)
Co-authored-by: dwelle <luzar.david@gmail.com> Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
parent
4712393b62
commit
cdf352d4c3
@ -7,7 +7,7 @@ import { DarkModeToggle } from "../components/DarkModeToggle";
|
|||||||
import { loadFromJSON, saveAsJSON } from "../data";
|
import { loadFromJSON, saveAsJSON } from "../data";
|
||||||
import { resaveAsImageWithScene } from "../data/resave";
|
import { resaveAsImageWithScene } from "../data/resave";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDeviceType } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { CheckboxItem } from "../components/CheckboxItem";
|
import { CheckboxItem } from "../components/CheckboxItem";
|
||||||
@ -204,7 +204,7 @@ export const actionSaveFileToDisk = register({
|
|||||||
icon={saveAs}
|
icon={saveAs}
|
||||||
title={t("buttons.saveAs")}
|
title={t("buttons.saveAs")}
|
||||||
aria-label={t("buttons.saveAs")}
|
aria-label={t("buttons.saveAs")}
|
||||||
showAriaLabel={useDeviceType().isMobile}
|
showAriaLabel={useDevice().isMobile}
|
||||||
hidden={!nativeFileSystemSupported}
|
hidden={!nativeFileSystemSupported}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
data-testid="save-as-button"
|
data-testid="save-as-button"
|
||||||
@ -248,7 +248,7 @@ export const actionLoadScene = register({
|
|||||||
icon={load}
|
icon={load}
|
||||||
title={t("buttons.load")}
|
title={t("buttons.load")}
|
||||||
aria-label={t("buttons.load")}
|
aria-label={t("buttons.load")}
|
||||||
showAriaLabel={useDeviceType().isMobile}
|
showAriaLabel={useDevice().isMobile}
|
||||||
onClick={updateData}
|
onClick={updateData}
|
||||||
data-testid="load-button"
|
data-testid="load-button"
|
||||||
/>
|
/>
|
||||||
|
@ -30,7 +30,7 @@ const trackAction = (
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
action.trackEvent.category,
|
action.trackEvent.category,
|
||||||
action.trackEvent.action || action.name,
|
action.trackEvent.action || action.name,
|
||||||
`${source} (${app.deviceType.isMobile ? "mobile" : "desktop"})`,
|
`${source} (${app.device.isMobile ? "mobile" : "desktop"})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
gridSize: null,
|
gridSize: null,
|
||||||
isBindingEnabled: true,
|
isBindingEnabled: true,
|
||||||
isLibraryOpen: false,
|
isLibraryOpen: false,
|
||||||
|
isLibraryMenuDocked: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
isRotating: false,
|
isRotating: false,
|
||||||
@ -146,7 +147,8 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
gridSize: { browser: true, export: true, server: true },
|
gridSize: { browser: true, export: true, server: true },
|
||||||
height: { browser: false, export: false, server: false },
|
height: { browser: false, export: false, server: false },
|
||||||
isBindingEnabled: { browser: false, export: false, server: false },
|
isBindingEnabled: { browser: false, export: false, server: false },
|
||||||
isLibraryOpen: { browser: false, export: false, server: false },
|
isLibraryOpen: { browser: true, export: false, server: false },
|
||||||
|
isLibraryMenuDocked: { browser: true, export: false, server: false },
|
||||||
isLoading: { browser: false, export: false, server: false },
|
isLoading: { browser: false, export: false, server: false },
|
||||||
isResizing: { browser: false, export: false, server: false },
|
isResizing: { browser: false, export: false, server: false },
|
||||||
isRotating: { browser: false, export: false, server: false },
|
isRotating: { browser: false, export: false, server: false },
|
||||||
|
@ -3,7 +3,7 @@ import { ActionManager } from "../actions/manager";
|
|||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import { ExcalidrawElement, PointerType } from "../element/types";
|
import { ExcalidrawElement, PointerType } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDeviceType } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
import {
|
import {
|
||||||
canChangeSharpness,
|
canChangeSharpness,
|
||||||
canHaveArrowheads,
|
canHaveArrowheads,
|
||||||
@ -52,7 +52,7 @@ export const SelectedShapeActions = ({
|
|||||||
isSingleElementBoundContainer = true;
|
isSingleElementBoundContainer = true;
|
||||||
}
|
}
|
||||||
const isEditing = Boolean(appState.editingElement);
|
const isEditing = Boolean(appState.editingElement);
|
||||||
const deviceType = useDeviceType();
|
const device = useDevice();
|
||||||
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||||
|
|
||||||
const showFillIcons =
|
const showFillIcons =
|
||||||
@ -177,8 +177,8 @@ export const SelectedShapeActions = ({
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.actions")}</legend>
|
<legend>{t("labels.actions")}</legend>
|
||||||
<div className="buttonList">
|
<div className="buttonList">
|
||||||
{!deviceType.isMobile && renderAction("duplicateSelection")}
|
{!device.isMobile && renderAction("duplicateSelection")}
|
||||||
{!deviceType.isMobile && renderAction("deleteSelectedElements")}
|
{!device.isMobile && renderAction("deleteSelectedElements")}
|
||||||
{renderAction("group")}
|
{renderAction("group")}
|
||||||
{renderAction("ungroup")}
|
{renderAction("ungroup")}
|
||||||
{showLinkIcon && renderAction("hyperlink")}
|
{showLinkIcon && renderAction("hyperlink")}
|
||||||
|
@ -64,6 +64,8 @@ import {
|
|||||||
MQ_MAX_HEIGHT_LANDSCAPE,
|
MQ_MAX_HEIGHT_LANDSCAPE,
|
||||||
MQ_MAX_WIDTH_LANDSCAPE,
|
MQ_MAX_WIDTH_LANDSCAPE,
|
||||||
MQ_MAX_WIDTH_PORTRAIT,
|
MQ_MAX_WIDTH_PORTRAIT,
|
||||||
|
MQ_RIGHT_SIDEBAR_MIN_WIDTH,
|
||||||
|
MQ_SM_MAX_WIDTH,
|
||||||
POINTER_BUTTON,
|
POINTER_BUTTON,
|
||||||
SCROLL_TIMEOUT,
|
SCROLL_TIMEOUT,
|
||||||
TAP_TWICE_TIMEOUT,
|
TAP_TWICE_TIMEOUT,
|
||||||
@ -194,7 +196,7 @@ import {
|
|||||||
LibraryItems,
|
LibraryItems,
|
||||||
PointerDownState,
|
PointerDownState,
|
||||||
SceneData,
|
SceneData,
|
||||||
DeviceType,
|
Device,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
@ -220,7 +222,6 @@ import {
|
|||||||
} from "../utils";
|
} from "../utils";
|
||||||
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
||||||
import LayerUI from "./LayerUI";
|
import LayerUI from "./LayerUI";
|
||||||
import { Stats } from "./Stats";
|
|
||||||
import { Toast } from "./Toast";
|
import { Toast } from "./Toast";
|
||||||
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
||||||
import {
|
import {
|
||||||
@ -259,12 +260,14 @@ import {
|
|||||||
isLocalLink,
|
isLocalLink,
|
||||||
} from "../element/Hyperlink";
|
} from "../element/Hyperlink";
|
||||||
|
|
||||||
const defaultDeviceTypeContext: DeviceType = {
|
const deviceContextInitialValue = {
|
||||||
|
isSmScreen: false,
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
isTouchScreen: false,
|
isTouchScreen: false,
|
||||||
|
canDeviceFitSidebar: false,
|
||||||
};
|
};
|
||||||
const DeviceTypeContext = React.createContext(defaultDeviceTypeContext);
|
const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
|
||||||
export const useDeviceType = () => useContext(DeviceTypeContext);
|
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;
|
||||||
@ -296,10 +299,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
rc: RoughCanvas | null = null;
|
rc: RoughCanvas | null = null;
|
||||||
unmounted: boolean = false;
|
unmounted: boolean = false;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
deviceType: DeviceType = {
|
device: Device = deviceContextInitialValue;
|
||||||
isMobile: false,
|
|
||||||
isTouchScreen: false,
|
|
||||||
};
|
|
||||||
detachIsMobileMqHandler?: () => void;
|
detachIsMobileMqHandler?: () => void;
|
||||||
|
|
||||||
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
||||||
@ -353,12 +353,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
showHyperlinkPopup: false,
|
showHyperlinkPopup: false,
|
||||||
|
isLibraryMenuDocked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
|
|
||||||
this.library = new Library(this);
|
this.library = new Library(this);
|
||||||
|
|
||||||
if (excalidrawRef) {
|
if (excalidrawRef) {
|
||||||
const readyPromise =
|
const readyPromise =
|
||||||
("current" in excalidrawRef && excalidrawRef.current?.readyPromise) ||
|
("current" in excalidrawRef && excalidrawRef.current?.readyPromise) ||
|
||||||
@ -485,7 +485,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
<div
|
<div
|
||||||
className={clsx("excalidraw excalidraw-container", {
|
className={clsx("excalidraw excalidraw-container", {
|
||||||
"excalidraw--view-mode": viewModeEnabled,
|
"excalidraw--view-mode": viewModeEnabled,
|
||||||
"excalidraw--mobile": this.deviceType.isMobile,
|
"excalidraw--mobile": this.device.isMobile,
|
||||||
})}
|
})}
|
||||||
ref={this.excalidrawContainerRef}
|
ref={this.excalidrawContainerRef}
|
||||||
onDrop={this.handleAppOnDrop}
|
onDrop={this.handleAppOnDrop}
|
||||||
@ -497,7 +497,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
<ExcalidrawContainerContext.Provider
|
<ExcalidrawContainerContext.Provider
|
||||||
value={this.excalidrawContainerValue}
|
value={this.excalidrawContainerValue}
|
||||||
>
|
>
|
||||||
<DeviceTypeContext.Provider value={this.deviceType}>
|
<DeviceContext.Provider value={this.device}>
|
||||||
<LayerUI
|
<LayerUI
|
||||||
canvas={this.canvas}
|
canvas={this.canvas}
|
||||||
appState={this.state}
|
appState={this.state}
|
||||||
@ -521,6 +521,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isCollaborating={this.props.isCollaborating}
|
isCollaborating={this.props.isCollaborating}
|
||||||
renderTopRightUI={renderTopRightUI}
|
renderTopRightUI={renderTopRightUI}
|
||||||
renderCustomFooter={renderFooter}
|
renderCustomFooter={renderFooter}
|
||||||
|
renderCustomStats={renderCustomStats}
|
||||||
viewModeEnabled={viewModeEnabled}
|
viewModeEnabled={viewModeEnabled}
|
||||||
showExitZenModeBtn={
|
showExitZenModeBtn={
|
||||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||||
@ -548,15 +549,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
onLinkOpen={this.props.onLinkOpen}
|
onLinkOpen={this.props.onLinkOpen}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{this.state.showStats && (
|
|
||||||
<Stats
|
|
||||||
appState={this.state}
|
|
||||||
setAppState={this.setAppState}
|
|
||||||
elements={this.scene.getNonDeletedElements()}
|
|
||||||
onClose={this.toggleStats}
|
|
||||||
renderCustomStats={renderCustomStats}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{this.state.toastMessage !== null && (
|
{this.state.toastMessage !== null && (
|
||||||
<Toast
|
<Toast
|
||||||
message={this.state.toastMessage}
|
message={this.state.toastMessage}
|
||||||
@ -564,7 +556,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<main>{this.renderCanvas()}</main>
|
<main>{this.renderCanvas()}</main>
|
||||||
</DeviceTypeContext.Provider>
|
</DeviceContext.Provider>
|
||||||
</ExcalidrawContainerContext.Provider>
|
</ExcalidrawContainerContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -763,7 +755,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const scene = restore(initialData, null, null);
|
const scene = restore(initialData, null, null);
|
||||||
scene.appState = {
|
scene.appState = {
|
||||||
...scene.appState,
|
...scene.appState,
|
||||||
isLibraryOpen: this.state.isLibraryOpen,
|
// we're falling back to current (pre-init) state when deciding
|
||||||
|
// whether to open the library, to handle a case where we
|
||||||
|
// update the state outside of initialData (e.g. when loading the app
|
||||||
|
// with a library install link, which should auto-open the library)
|
||||||
|
isLibraryOpen:
|
||||||
|
initialData?.appState?.isLibraryOpen || this.state.isLibraryOpen,
|
||||||
activeTool:
|
activeTool:
|
||||||
scene.appState.activeTool.type === "image"
|
scene.appState.activeTool.type === "image"
|
||||||
? { ...scene.appState.activeTool, type: "selection" }
|
? { ...scene.appState.activeTool, type: "selection" }
|
||||||
@ -794,6 +791,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private refreshDeviceState = (container: HTMLDivElement) => {
|
||||||
|
const { width, height } = container.getBoundingClientRect();
|
||||||
|
const sidebarBreakpoint =
|
||||||
|
this.props.UIOptions.dockedSidebarBreakpoint != null
|
||||||
|
? this.props.UIOptions.dockedSidebarBreakpoint
|
||||||
|
: MQ_RIGHT_SIDEBAR_MIN_WIDTH;
|
||||||
|
this.device = updateObject(this.device, {
|
||||||
|
isSmScreen: width < MQ_SM_MAX_WIDTH,
|
||||||
|
isMobile:
|
||||||
|
width < MQ_MAX_WIDTH_PORTRAIT ||
|
||||||
|
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE),
|
||||||
|
canDeviceFitSidebar: width > sidebarBreakpoint,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.excalidrawContainerValue.container =
|
this.excalidrawContainerValue.container =
|
||||||
@ -835,34 +847,53 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.focusContainer();
|
this.focusContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.excalidrawContainerRef.current &&
|
||||||
|
// bounding rects don't work in tests so updating
|
||||||
|
// the state on init would result in making the test enviro run
|
||||||
|
// in mobile breakpoint (0 width/height), making everything fail
|
||||||
|
process.env.NODE_ENV !== "test"
|
||||||
|
) {
|
||||||
|
this.refreshDeviceState(this.excalidrawContainerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
if ("ResizeObserver" in window && this.excalidrawContainerRef?.current) {
|
if ("ResizeObserver" in window && this.excalidrawContainerRef?.current) {
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
// compute isMobile state
|
// recompute device dimensions state
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
const { width, height } =
|
this.refreshDeviceState(this.excalidrawContainerRef.current!);
|
||||||
this.excalidrawContainerRef.current!.getBoundingClientRect();
|
|
||||||
this.deviceType = updateObject(this.deviceType, {
|
|
||||||
isMobile:
|
|
||||||
width < MQ_MAX_WIDTH_PORTRAIT ||
|
|
||||||
(height < MQ_MAX_HEIGHT_LANDSCAPE &&
|
|
||||||
width < MQ_MAX_WIDTH_LANDSCAPE),
|
|
||||||
});
|
|
||||||
// refresh offsets
|
// refresh offsets
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
this.updateDOMRect();
|
this.updateDOMRect();
|
||||||
});
|
});
|
||||||
this.resizeObserver?.observe(this.excalidrawContainerRef.current);
|
this.resizeObserver?.observe(this.excalidrawContainerRef.current);
|
||||||
} else if (window.matchMedia) {
|
} else if (window.matchMedia) {
|
||||||
const mediaQuery = window.matchMedia(
|
const mdScreenQuery = window.matchMedia(
|
||||||
`(max-width: ${MQ_MAX_WIDTH_PORTRAIT}px), (max-height: ${MQ_MAX_HEIGHT_LANDSCAPE}px) and (max-width: ${MQ_MAX_WIDTH_LANDSCAPE}px)`,
|
`(max-width: ${MQ_MAX_WIDTH_PORTRAIT}px), (max-height: ${MQ_MAX_HEIGHT_LANDSCAPE}px) and (max-width: ${MQ_MAX_WIDTH_LANDSCAPE}px)`,
|
||||||
);
|
);
|
||||||
|
const smScreenQuery = window.matchMedia(
|
||||||
|
`(max-width: ${MQ_SM_MAX_WIDTH}px)`,
|
||||||
|
);
|
||||||
|
const canDeviceFitSidebarMediaQuery = window.matchMedia(
|
||||||
|
`(min-width: ${
|
||||||
|
// NOTE this won't update if a different breakpoint is supplied
|
||||||
|
// after mount
|
||||||
|
this.props.UIOptions.dockedSidebarBreakpoint != null
|
||||||
|
? this.props.UIOptions.dockedSidebarBreakpoint
|
||||||
|
: MQ_RIGHT_SIDEBAR_MIN_WIDTH
|
||||||
|
}px)`,
|
||||||
|
);
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
this.deviceType = updateObject(this.deviceType, {
|
this.excalidrawContainerRef.current!.getBoundingClientRect();
|
||||||
isMobile: mediaQuery.matches,
|
this.device = updateObject(this.device, {
|
||||||
|
isSmScreen: smScreenQuery.matches,
|
||||||
|
isMobile: mdScreenQuery.matches,
|
||||||
|
canDeviceFitSidebar: canDeviceFitSidebarMediaQuery.matches,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
mediaQuery.addListener(handler);
|
mdScreenQuery.addListener(handler);
|
||||||
this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler);
|
this.detachIsMobileMqHandler = () =>
|
||||||
|
mdScreenQuery.removeListener(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(window.location.search.slice(1));
|
const searchParams = new URLSearchParams(window.location.search.slice(1));
|
||||||
@ -1003,6 +1034,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
|
if (
|
||||||
|
this.excalidrawContainerRef.current &&
|
||||||
|
prevProps.UIOptions.dockedSidebarBreakpoint !==
|
||||||
|
this.props.UIOptions.dockedSidebarBreakpoint
|
||||||
|
) {
|
||||||
|
this.refreshDeviceState(this.excalidrawContainerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prevState.scrollX !== this.state.scrollX ||
|
prevState.scrollX !== this.state.scrollX ||
|
||||||
prevState.scrollY !== this.state.scrollY
|
prevState.scrollY !== this.state.scrollY
|
||||||
@ -1175,7 +1214,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
theme: this.state.theme,
|
theme: this.state.theme,
|
||||||
imageCache: this.imageCache,
|
imageCache: this.imageCache,
|
||||||
isExporting: false,
|
isExporting: false,
|
||||||
renderScrollbars: !this.deviceType.isMobile,
|
renderScrollbars: !this.device.isMobile,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1453,11 +1492,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
this.scene.replaceAllElements(nextElements);
|
this.scene.replaceAllElements(nextElements);
|
||||||
this.history.resumeRecording();
|
this.history.resumeRecording();
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
selectGroupsForSelectedElements(
|
selectGroupsForSelectedElements(
|
||||||
{
|
{
|
||||||
...this.state,
|
...this.state,
|
||||||
isLibraryOpen: false,
|
isLibraryOpen:
|
||||||
|
this.state.isLibraryOpen && this.device.canDeviceFitSidebar
|
||||||
|
? this.state.isLibraryMenuDocked
|
||||||
|
: false,
|
||||||
selectedElementIds: newElements.reduce((map, element) => {
|
selectedElementIds: newElements.reduce((map, element) => {
|
||||||
if (!isBoundToContainer(element)) {
|
if (!isBoundToContainer(element)) {
|
||||||
map[element.id] = true;
|
map[element.id] = true;
|
||||||
@ -1529,7 +1572,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
"toolbar",
|
"toolbar",
|
||||||
"toggleLock",
|
"toggleLock",
|
||||||
`${source} (${this.deviceType.isMobile ? "mobile" : "desktop"})`,
|
`${source} (${this.device.isMobile ? "mobile" : "desktop"})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
@ -1560,10 +1603,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.actionManager.executeAction(actionToggleZenMode);
|
this.actionManager.executeAction(actionToggleZenMode);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleStats = () => {
|
|
||||||
this.actionManager.executeAction(actionToggleStats);
|
|
||||||
};
|
|
||||||
|
|
||||||
scrollToContent = (
|
scrollToContent = (
|
||||||
target:
|
target:
|
||||||
| ExcalidrawElement
|
| ExcalidrawElement
|
||||||
@ -1721,7 +1760,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.code === CODES.ZERO) {
|
if (event.code === CODES.ZERO) {
|
||||||
this.setState({ isLibraryOpen: !this.state.isLibraryOpen });
|
const nextState = !this.state.isLibraryOpen;
|
||||||
|
this.setState({ isLibraryOpen: nextState });
|
||||||
|
// track only openings
|
||||||
|
if (nextState) {
|
||||||
|
trackEvent(
|
||||||
|
"library",
|
||||||
|
"toggleLibrary (open)",
|
||||||
|
`keyboard (${this.device.isMobile ? "mobile" : "desktop"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArrowKey(event.key)) {
|
if (isArrowKey(event.key)) {
|
||||||
@ -1815,7 +1863,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
"toolbar",
|
"toolbar",
|
||||||
shape,
|
shape,
|
||||||
`keyboard (${this.deviceType.isMobile ? "mobile" : "desktop"})`,
|
`keyboard (${this.device.isMobile ? "mobile" : "desktop"})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.setActiveTool({ type: shape });
|
this.setActiveTool({ type: shape });
|
||||||
@ -2440,7 +2488,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
element,
|
element,
|
||||||
this.state,
|
this.state,
|
||||||
[scenePointer.x, scenePointer.y],
|
[scenePointer.x, scenePointer.y],
|
||||||
this.deviceType.isMobile,
|
this.device.isMobile,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -2472,7 +2520,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
this.state,
|
this.state,
|
||||||
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
||||||
this.deviceType.isMobile,
|
this.device.isMobile,
|
||||||
);
|
);
|
||||||
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
||||||
this.lastPointerUp!,
|
this.lastPointerUp!,
|
||||||
@ -2482,7 +2530,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
this.state,
|
this.state,
|
||||||
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
||||||
this.deviceType.isMobile,
|
this.device.isMobile,
|
||||||
);
|
);
|
||||||
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
||||||
const url = this.hitLinkElement.link;
|
const url = this.hitLinkElement.link;
|
||||||
@ -2921,10 +2969,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.deviceType.isTouchScreen &&
|
!this.device.isTouchScreen &&
|
||||||
["pen", "touch"].includes(event.pointerType)
|
["pen", "touch"].includes(event.pointerType)
|
||||||
) {
|
) {
|
||||||
this.deviceType = updateObject(this.deviceType, { isTouchScreen: true });
|
this.device = updateObject(this.device, { isTouchScreen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPanning) {
|
if (isPanning) {
|
||||||
@ -3066,7 +3114,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
event: React.PointerEvent<HTMLCanvasElement>,
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
this.lastPointerUp = event;
|
this.lastPointerUp = event;
|
||||||
if (this.deviceType.isTouchScreen) {
|
if (this.device.isTouchScreen) {
|
||||||
const scenePointer = viewportCoordsToSceneCoords(
|
const scenePointer = viewportCoordsToSceneCoords(
|
||||||
{ clientX: event.clientX, clientY: event.clientY },
|
{ clientX: event.clientX, clientY: event.clientY },
|
||||||
this.state,
|
this.state,
|
||||||
@ -3084,7 +3132,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.hitLinkElement &&
|
this.hitLinkElement &&
|
||||||
!this.state.selectedElementIds[this.hitLinkElement.id]
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
||||||
) {
|
) {
|
||||||
this.redirectToLink(event, this.deviceType.isTouchScreen);
|
this.redirectToLink(event, this.device.isTouchScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.removePointer(event);
|
this.removePointer(event);
|
||||||
@ -3456,7 +3504,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState.hit.element,
|
pointerDownState.hit.element,
|
||||||
this.state,
|
this.state,
|
||||||
[pointerDownState.origin.x, pointerDownState.origin.y],
|
[pointerDownState.origin.x, pointerDownState.origin.y],
|
||||||
this.deviceType.isMobile,
|
this.device.isMobile,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@ -5563,7 +5611,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
} else {
|
} else {
|
||||||
ContextMenu.push({
|
ContextMenu.push({
|
||||||
options: [
|
options: [
|
||||||
this.deviceType.isMobile &&
|
this.device.isMobile &&
|
||||||
navigator.clipboard && {
|
navigator.clipboard && {
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
name: "paste",
|
name: "paste",
|
||||||
@ -5575,7 +5623,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
contextItemLabel: "labels.paste",
|
contextItemLabel: "labels.paste",
|
||||||
},
|
},
|
||||||
this.deviceType.isMobile && navigator.clipboard && separator,
|
this.device.isMobile && navigator.clipboard && separator,
|
||||||
probablySupportsClipboardBlob &&
|
probablySupportsClipboardBlob &&
|
||||||
elements.length > 0 &&
|
elements.length > 0 &&
|
||||||
actionCopyAsPng,
|
actionCopyAsPng,
|
||||||
@ -5620,9 +5668,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
} else {
|
} else {
|
||||||
ContextMenu.push({
|
ContextMenu.push({
|
||||||
options: [
|
options: [
|
||||||
this.deviceType.isMobile && actionCut,
|
this.device.isMobile && actionCut,
|
||||||
this.deviceType.isMobile && navigator.clipboard && actionCopy,
|
this.device.isMobile && navigator.clipboard && actionCopy,
|
||||||
this.deviceType.isMobile &&
|
this.device.isMobile &&
|
||||||
navigator.clipboard && {
|
navigator.clipboard && {
|
||||||
name: "paste",
|
name: "paste",
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
@ -5634,7 +5682,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
contextItemLabel: "labels.paste",
|
contextItemLabel: "labels.paste",
|
||||||
},
|
},
|
||||||
this.deviceType.isMobile && separator,
|
this.device.isMobile && separator,
|
||||||
...options,
|
...options,
|
||||||
separator,
|
separator,
|
||||||
actionCopyStyles,
|
actionCopyStyles,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDeviceType } from "./App";
|
import { useDevice } from "./App";
|
||||||
import { trash } from "./icons";
|
import { trash } from "./icons";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ const ClearCanvas = ({ onConfirm }: { onConfirm: () => void }) => {
|
|||||||
icon={trash}
|
icon={trash}
|
||||||
title={t("buttons.clearReset")}
|
title={t("buttons.clearReset")}
|
||||||
aria-label={t("buttons.clearReset")}
|
aria-label={t("buttons.clearReset")}
|
||||||
showAriaLabel={useDeviceType().isMobile}
|
showAriaLabel={useDevice().isMobile}
|
||||||
onClick={toggleDialog}
|
onClick={toggleDialog}
|
||||||
data-testid="clear-canvas-button"
|
data-testid="clear-canvas-button"
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDeviceType } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
import { users } from "./icons";
|
import { users } from "./icons";
|
||||||
|
|
||||||
import "./CollabButton.scss";
|
import "./CollabButton.scss";
|
||||||
@ -26,7 +26,7 @@ const CollabButton = ({
|
|||||||
type="button"
|
type="button"
|
||||||
title={t("labels.liveCollaboration")}
|
title={t("labels.liveCollaboration")}
|
||||||
aria-label={t("labels.liveCollaboration")}
|
aria-label={t("labels.liveCollaboration")}
|
||||||
showAriaLabel={useDeviceType().isMobile}
|
showAriaLabel={useDevice().isMobile}
|
||||||
>
|
>
|
||||||
{collaboratorCount > 0 && (
|
{collaboratorCount > 0 && (
|
||||||
<div className="CollabButton-collaborators">{collaboratorCount}</div>
|
<div className="CollabButton-collaborators">{collaboratorCount}</div>
|
||||||
|
@ -2,7 +2,7 @@ import clsx from "clsx";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useExcalidrawContainer, useDeviceType } from "../components/App";
|
import { useExcalidrawContainer, useDevice } from "../components/App";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import "./Dialog.scss";
|
import "./Dialog.scss";
|
||||||
import { back, close } from "./icons";
|
import { back, close } from "./icons";
|
||||||
@ -94,7 +94,7 @@ export const Dialog = (props: DialogProps) => {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label={t("buttons.close")}
|
aria-label={t("buttons.close")}
|
||||||
>
|
>
|
||||||
{useDeviceType().isMobile ? back : close}
|
{useDevice().isMobile ? back : close}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="Dialog__content">{props.children}</div>
|
<div className="Dialog__content">{props.children}</div>
|
||||||
|
@ -5,7 +5,7 @@ import { canvasToBlob } from "../data/blob";
|
|||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { CanvasError } from "../errors";
|
import { CanvasError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDeviceType } from "./App";
|
import { useDevice } from "./App";
|
||||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
import { exportToCanvas } from "../scene/export";
|
import { exportToCanvas } from "../scene/export";
|
||||||
import { AppState, BinaryFiles } from "../types";
|
import { AppState, BinaryFiles } from "../types";
|
||||||
@ -250,7 +250,7 @@ export const ImageExportDialog = ({
|
|||||||
icon={exportImage}
|
icon={exportImage}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t("buttons.exportImage")}
|
aria-label={t("buttons.exportImage")}
|
||||||
showAriaLabel={useDeviceType().isMobile}
|
showAriaLabel={useDevice().isMobile}
|
||||||
title={t("buttons.exportImage")}
|
title={t("buttons.exportImage")}
|
||||||
/>
|
/>
|
||||||
{modalIsShown && (
|
{modalIsShown && (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDeviceType } from "./App";
|
import { useDevice } from "./App";
|
||||||
import { AppState, ExportOpts, BinaryFiles } from "../types";
|
import { AppState, ExportOpts, BinaryFiles } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { exportFile, exportToFileIcon, link } from "./icons";
|
import { exportFile, exportToFileIcon, link } from "./icons";
|
||||||
@ -117,7 +117,7 @@ export const JSONExportDialog = ({
|
|||||||
icon={exportFile}
|
icon={exportFile}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t("buttons.export")}
|
aria-label={t("buttons.export")}
|
||||||
showAriaLabel={useDeviceType().isMobile}
|
showAriaLabel={useDevice().isMobile}
|
||||||
title={t("buttons.export")}
|
title={t("buttons.export")}
|
||||||
/>
|
/>
|
||||||
{modalIsShown && (
|
{modalIsShown && (
|
||||||
|
@ -1,9 +1,63 @@
|
|||||||
@import "open-color/open-color";
|
@import "open-color/open-color";
|
||||||
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
.layer-ui__sidebar {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--sat);
|
||||||
|
bottom: var(--sab);
|
||||||
|
right: var(--sar);
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
box-shadow: var(--shadow-island);
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
margin: var(--space-factor);
|
||||||
|
width: calc(#{$right-sidebar-width} - var(--space-factor) * 2);
|
||||||
|
|
||||||
|
.Island {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon__icon {
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon__icon__close {
|
||||||
|
.Modal__close {
|
||||||
|
width: calc(var(--space-factor) * 7);
|
||||||
|
height: calc(var(--space-factor) * 7);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Island {
|
||||||
|
--padding: 0;
|
||||||
|
background-color: var(--island-bg-color);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
padding: calc(var(--padding) * var(--space-factor));
|
||||||
|
position: relative;
|
||||||
|
transition: box-shadow 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
|
.layer-ui__wrapper.animate {
|
||||||
|
transition: width 0.1s ease-in-out;
|
||||||
|
}
|
||||||
.layer-ui__wrapper {
|
.layer-ui__wrapper {
|
||||||
|
// when the rightside sidebar is docked, we need to resize the UI by its
|
||||||
|
// width, making the nested UI content shift to the left. To do this,
|
||||||
|
// we need the UI container to actually have dimensions set, but
|
||||||
|
// then we also need to disable pointer events else the canvas below
|
||||||
|
// wouldn't be interactive.
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
z-index: var(--zIndex-layerUI);
|
z-index: var(--zIndex-layerUI);
|
||||||
|
|
||||||
&__top-right {
|
&__top-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { CLASSES } from "../constants";
|
import { CLASSES, LIBRARY_SIDEBAR_WIDTH } from "../constants";
|
||||||
import { exportCanvas } from "../data";
|
import { exportCanvas } from "../data";
|
||||||
import { isTextElement, showSelectedShapeActions } from "../element";
|
import { isTextElement, showSelectedShapeActions } from "../element";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
@ -36,7 +36,9 @@ import "./LayerUI.scss";
|
|||||||
import "./Toolbar.scss";
|
import "./Toolbar.scss";
|
||||||
import { PenModeButton } from "./PenModeButton";
|
import { PenModeButton } from "./PenModeButton";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { useDeviceType } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
|
import { Stats } from "./Stats";
|
||||||
|
import { actionToggleStats } from "../actions/actionToggleStats";
|
||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
@ -55,14 +57,9 @@ interface LayerUIProps {
|
|||||||
toggleZenMode: () => void;
|
toggleZenMode: () => void;
|
||||||
langCode: Language["code"];
|
langCode: Language["code"];
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
renderTopRightUI?: (
|
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
||||||
isMobile: boolean,
|
renderCustomFooter?: ExcalidrawProps["renderFooter"];
|
||||||
appState: AppState,
|
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||||
) => JSX.Element | null;
|
|
||||||
renderCustomFooter?: (
|
|
||||||
isMobile: boolean,
|
|
||||||
appState: AppState,
|
|
||||||
) => JSX.Element | null;
|
|
||||||
viewModeEnabled: boolean;
|
viewModeEnabled: boolean;
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
UIOptions: AppProps["UIOptions"];
|
UIOptions: AppProps["UIOptions"];
|
||||||
@ -71,7 +68,6 @@ interface LayerUIProps {
|
|||||||
id: string;
|
id: string;
|
||||||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayerUI = ({
|
const LayerUI = ({
|
||||||
actionManager,
|
actionManager,
|
||||||
appState,
|
appState,
|
||||||
@ -90,6 +86,7 @@ const LayerUI = ({
|
|||||||
isCollaborating,
|
isCollaborating,
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
renderCustomFooter,
|
renderCustomFooter,
|
||||||
|
renderCustomStats,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
UIOptions,
|
UIOptions,
|
||||||
@ -98,7 +95,7 @@ const LayerUI = ({
|
|||||||
id,
|
id,
|
||||||
onImageAction,
|
onImageAction,
|
||||||
}: LayerUIProps) => {
|
}: LayerUIProps) => {
|
||||||
const deviceType = useDeviceType();
|
const device = useDevice();
|
||||||
|
|
||||||
const renderJSONExportDialog = () => {
|
const renderJSONExportDialog = () => {
|
||||||
if (!UIOptions.canvasActions.export) {
|
if (!UIOptions.canvasActions.export) {
|
||||||
@ -344,7 +341,7 @@ const LayerUI = ({
|
|||||||
<HintViewer
|
<HintViewer
|
||||||
appState={appState}
|
appState={appState}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
isMobile={deviceType.isMobile}
|
isMobile={device.isMobile}
|
||||||
/>
|
/>
|
||||||
{heading}
|
{heading}
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
@ -366,7 +363,6 @@ const LayerUI = ({
|
|||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
{libraryMenu}
|
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
@ -383,7 +379,7 @@ const LayerUI = ({
|
|||||||
collaborators={appState.collaborators}
|
collaborators={appState.collaborators}
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
/>
|
/>
|
||||||
{renderTopRightUI?.(deviceType.isMobile, appState)}
|
{renderTopRightUI?.(device.isMobile, appState)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FixedSideContainer>
|
</FixedSideContainer>
|
||||||
@ -436,7 +432,7 @@ const LayerUI = ({
|
|||||||
)}
|
)}
|
||||||
{!viewModeEnabled &&
|
{!viewModeEnabled &&
|
||||||
appState.multiElement &&
|
appState.multiElement &&
|
||||||
deviceType.isTouchScreen && (
|
device.isTouchScreen && (
|
||||||
<div
|
<div
|
||||||
className={clsx("finalize-button zen-mode-transition", {
|
className={clsx("finalize-button zen-mode-transition", {
|
||||||
"layer-ui__wrapper__footer-left--transition-left":
|
"layer-ui__wrapper__footer-left--transition-left":
|
||||||
@ -513,7 +509,24 @@ const LayerUI = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return deviceType.isMobile ? (
|
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}
|
{dialogs}
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
@ -534,20 +547,31 @@ const LayerUI = ({
|
|||||||
showThemeBtn={showThemeBtn}
|
showThemeBtn={showThemeBtn}
|
||||||
onImageAction={onImageAction}
|
onImageAction={onImageAction}
|
||||||
renderTopRightUI={renderTopRightUI}
|
renderTopRightUI={renderTopRightUI}
|
||||||
|
renderStats={renderStats}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={clsx("layer-ui__wrapper", {
|
className={clsx("layer-ui__wrapper", {
|
||||||
"disable-pointerEvents":
|
"disable-pointerEvents":
|
||||||
appState.draggingElement ||
|
appState.draggingElement ||
|
||||||
appState.resizingElement ||
|
appState.resizingElement ||
|
||||||
(appState.editingElement && !isTextElement(appState.editingElement)),
|
(appState.editingElement &&
|
||||||
|
!isTextElement(appState.editingElement)),
|
||||||
})}
|
})}
|
||||||
|
style={
|
||||||
|
appState.isLibraryOpen &&
|
||||||
|
appState.isLibraryMenuDocked &&
|
||||||
|
device.canDeviceFitSidebar
|
||||||
|
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
||||||
|
: {}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{dialogs}
|
{dialogs}
|
||||||
{renderFixedSideContainer()}
|
{renderFixedSideContainer()}
|
||||||
{renderBottomAppMenu()}
|
{renderBottomAppMenu()}
|
||||||
|
{renderStats()}
|
||||||
{appState.scrolledOutside && (
|
{appState.scrolledOutside && (
|
||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
@ -561,6 +585,10 @@ const LayerUI = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{appState.isLibraryOpen && (
|
||||||
|
<div className="layer-ui__sidebar">{libraryMenu}</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ import clsx from "clsx";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { capitalizeString } from "../utils";
|
import { capitalizeString } from "../utils";
|
||||||
|
import { trackEvent } from "../analytics";
|
||||||
|
import { useDevice } from "./App";
|
||||||
|
|
||||||
const LIBRARY_ICON = (
|
const LIBRARY_ICON = (
|
||||||
<svg viewBox="0 0 576 512">
|
<svg viewBox="0 0 576 512">
|
||||||
@ -18,6 +20,7 @@ export const LibraryButton: React.FC<{
|
|||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
}> = ({ appState, setAppState, isMobile }) => {
|
}> = ({ appState, setAppState, isMobile }) => {
|
||||||
|
const device = useDevice();
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@ -34,7 +37,19 @@ export const LibraryButton: React.FC<{
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="editor-library"
|
name="editor-library"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setAppState({ isLibraryOpen: event.target.checked });
|
document
|
||||||
|
.querySelector(".layer-ui__wrapper")
|
||||||
|
?.classList.remove("animate");
|
||||||
|
const nextState = event.target.checked;
|
||||||
|
setAppState({ isLibraryOpen: nextState });
|
||||||
|
// track only openings
|
||||||
|
if (nextState) {
|
||||||
|
trackEvent(
|
||||||
|
"library",
|
||||||
|
"toggleLibrary (open)",
|
||||||
|
`toolbar (${device.isMobile ? "mobile" : "desktop"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
checked={appState.isLibraryOpen}
|
checked={appState.isLibraryOpen}
|
||||||
aria-label={capitalizeString(t("toolBar.library"))}
|
aria-label={capitalizeString(t("toolBar.library"))}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.layer-ui__library {
|
.layer-ui__library {
|
||||||
margin: auto;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -11,8 +10,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2px 0;
|
margin: 2px 0 15px 0;
|
||||||
|
|
||||||
.Spinner {
|
.Spinner {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
@ -21,14 +19,18 @@
|
|||||||
// 2px from the left to account for focus border of left-most button
|
// 2px from the left to account for focus border of left-most button
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
margin-inline-start: auto;
|
|
||||||
// 17px for scrollbar (needed for overlay scrollbars on Big Sur?) + 1px extra
|
|
||||||
padding-inline-end: 18px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layer-ui__sidebar {
|
||||||
|
.layer-ui__library {
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.library-menu-items-container {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-ui__library-message {
|
.layer-ui__library-message {
|
||||||
@ -65,4 +67,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-menu-browse-button {
|
||||||
|
width: 80%;
|
||||||
|
min-height: 22px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: $oc-white;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-decoration: none !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary-darker);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--color-primary-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-menu-browse-button--mobile {
|
||||||
|
min-height: 22px;
|
||||||
|
margin-left: auto;
|
||||||
|
a {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import { trackEvent } from "../analytics";
|
|||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
|
import { useDevice } from "./App";
|
||||||
|
|
||||||
const useOnClickOutside = (
|
const useOnClickOutside = (
|
||||||
ref: RefObject<HTMLElement>,
|
ref: RefObject<HTMLElement>,
|
||||||
@ -103,17 +104,30 @@ export const LibraryMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useOnClickOutside(ref, (event) => {
|
const device = useDevice();
|
||||||
|
|
||||||
|
useOnClickOutside(
|
||||||
|
ref,
|
||||||
|
useCallback(
|
||||||
|
(event) => {
|
||||||
// If click on the library icon, do nothing.
|
// If click on the library icon, do nothing.
|
||||||
if ((event.target as Element).closest(".ToolIcon__library")) {
|
if ((event.target as Element).closest(".ToolIcon__library")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!appState.isLibraryMenuDocked || !device.canDeviceFitSidebar) {
|
||||||
onClose();
|
onClose();
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
[onClose, appState.isLibraryMenuDocked, device.canDeviceFitSidebar],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === KEYS.ESCAPE) {
|
if (
|
||||||
|
event.key === KEYS.ESCAPE &&
|
||||||
|
(!appState.isLibraryMenuDocked || !device.canDeviceFitSidebar)
|
||||||
|
) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -121,7 +135,7 @@ export const LibraryMenu = ({
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
|
document.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [onClose]);
|
}, [onClose, appState.isLibraryMenuDocked, device.canDeviceFitSidebar]);
|
||||||
|
|
||||||
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
|
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
|
||||||
const [showPublishLibraryDialog, setShowPublishLibraryDialog] =
|
const [showPublishLibraryDialog, setShowPublishLibraryDialog] =
|
||||||
@ -273,6 +287,7 @@ export const LibraryMenu = ({
|
|||||||
onInsertLibraryItems={onInsertLibraryItems}
|
onInsertLibraryItems={onInsertLibraryItems}
|
||||||
pendingElements={pendingElements}
|
pendingElements={pendingElements}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
|
appState={appState}
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
library={library}
|
library={library}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
@ -2,8 +2,17 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.library-menu-items-container {
|
.library-menu-items-container {
|
||||||
.library-actions {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.library-actions {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-right: auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
button .library-actions-counter {
|
button .library-actions-counter {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -87,12 +96,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&__items {
|
&__items {
|
||||||
max-height: 50vh;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
margin-top: 0.5rem;
|
overflow-x: hidden;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
margin: 0.6em 0.2em;
|
margin: 0.6em 0.2em;
|
||||||
|
@ -12,9 +12,9 @@ import {
|
|||||||
LibraryItems,
|
LibraryItems,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { arrayToMap, muteFSAbortError } from "../utils";
|
import { arrayToMap, muteFSAbortError } from "../utils";
|
||||||
import { useDeviceType } from "./App";
|
import { useDevice } from "./App";
|
||||||
import ConfirmDialog from "./ConfirmDialog";
|
import ConfirmDialog from "./ConfirmDialog";
|
||||||
import { exportToFileIcon, load, publishIcon, trash } from "./icons";
|
import { close, exportToFileIcon, load, publishIcon, trash } from "./icons";
|
||||||
import { LibraryUnit } from "./LibraryUnit";
|
import { LibraryUnit } from "./LibraryUnit";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
@ -25,6 +25,9 @@ import { MIME_TYPES, VERSIONS } from "../constants";
|
|||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import { fileOpen } from "../data/filesystem";
|
import { fileOpen } from "../data/filesystem";
|
||||||
|
|
||||||
|
import { SidebarLockButton } from "./SidebarLockButton";
|
||||||
|
import { trackEvent } from "../analytics";
|
||||||
|
|
||||||
const LibraryMenuItems = ({
|
const LibraryMenuItems = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
libraryItems,
|
libraryItems,
|
||||||
@ -34,6 +37,7 @@ const LibraryMenuItems = ({
|
|||||||
pendingElements,
|
pendingElements,
|
||||||
theme,
|
theme,
|
||||||
setAppState,
|
setAppState,
|
||||||
|
appState,
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
library,
|
library,
|
||||||
files,
|
files,
|
||||||
@ -52,6 +56,7 @@ const LibraryMenuItems = ({
|
|||||||
theme: AppState["theme"];
|
theme: AppState["theme"];
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
appState: AppState;
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
library: Library;
|
library: Library;
|
||||||
id: string;
|
id: string;
|
||||||
@ -88,9 +93,7 @@ const LibraryMenuItems = ({
|
|||||||
}, [selectedItems, onRemoveFromLibrary, resetLibrary]);
|
}, [selectedItems, onRemoveFromLibrary, resetLibrary]);
|
||||||
|
|
||||||
const [showRemoveLibAlert, setShowRemoveLibAlert] = useState(false);
|
const [showRemoveLibAlert, setShowRemoveLibAlert] = useState(false);
|
||||||
|
const device = useDevice();
|
||||||
const isMobile = useDeviceType().isMobile;
|
|
||||||
|
|
||||||
const renderLibraryActions = () => {
|
const renderLibraryActions = () => {
|
||||||
const itemsSelected = !!selectedItems.length;
|
const itemsSelected = !!selectedItems.length;
|
||||||
const items = itemsSelected
|
const items = itemsSelected
|
||||||
@ -101,7 +104,7 @@ const LibraryMenuItems = ({
|
|||||||
: t("buttons.resetLibrary");
|
: t("buttons.resetLibrary");
|
||||||
return (
|
return (
|
||||||
<div className="library-actions">
|
<div className="library-actions">
|
||||||
{(!itemsSelected || !isMobile) && (
|
{!itemsSelected && (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
key="import"
|
key="import"
|
||||||
type="button"
|
type="button"
|
||||||
@ -186,7 +189,7 @@ const LibraryMenuItems = ({
|
|||||||
className="library-actions--publish"
|
className="library-actions--publish"
|
||||||
onClick={onPublish}
|
onClick={onPublish}
|
||||||
>
|
>
|
||||||
{!isMobile && <label>{t("buttons.publishLibrary")}</label>}
|
{!device.isMobile && <label>{t("buttons.publishLibrary")}</label>}
|
||||||
{selectedItems.length > 0 && (
|
{selectedItems.length > 0 && (
|
||||||
<span className="library-actions-counter">
|
<span className="library-actions-counter">
|
||||||
{selectedItems.length}
|
{selectedItems.length}
|
||||||
@ -195,11 +198,25 @@ const LibraryMenuItems = ({
|
|||||||
</ToolButton>
|
</ToolButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{device.isMobile && (
|
||||||
|
<div className="library-menu-browse-button--mobile">
|
||||||
|
<a
|
||||||
|
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
|
||||||
|
window.name || "_blank"
|
||||||
|
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
|
||||||
|
VERSIONS.excalidrawLibrary
|
||||||
|
}`}
|
||||||
|
target="_excalidraw_libraries"
|
||||||
|
>
|
||||||
|
{t("labels.libraries")}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CELLS_PER_ROW = isMobile ? 4 : 6;
|
const CELLS_PER_ROW = device.isMobile && !device.isSmScreen ? 6 : 4;
|
||||||
|
|
||||||
const referrer =
|
const referrer =
|
||||||
libraryReturnUrl || window.location.origin + window.location.pathname;
|
libraryReturnUrl || window.location.origin + window.location.pathname;
|
||||||
@ -356,15 +373,141 @@ const LibraryMenuItems = ({
|
|||||||
(item) => item.status === "published",
|
(item) => item.status === "published",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderLibraryHeader = () => {
|
||||||
return (
|
return (
|
||||||
<div className="library-menu-items-container">
|
<>
|
||||||
{showRemoveLibAlert && renderRemoveLibAlert()}
|
|
||||||
<div className="layer-ui__library-header" key="library-header">
|
<div className="layer-ui__library-header" key="library-header">
|
||||||
{renderLibraryActions()}
|
{renderLibraryActions()}
|
||||||
{isLoading ? (
|
{device.canDeviceFitSidebar && (
|
||||||
|
<>
|
||||||
|
<div className="layer-ui__sidebar-lock-button">
|
||||||
|
<SidebarLockButton
|
||||||
|
checked={appState.isLibraryMenuDocked}
|
||||||
|
onChange={() => {
|
||||||
|
document
|
||||||
|
.querySelector(".layer-ui__wrapper")
|
||||||
|
?.classList.add("animate");
|
||||||
|
const nextState = !appState.isLibraryMenuDocked;
|
||||||
|
setAppState({
|
||||||
|
isLibraryMenuDocked: nextState,
|
||||||
|
});
|
||||||
|
trackEvent(
|
||||||
|
"library",
|
||||||
|
`toggleLibraryDock (${nextState ? "dock" : "undock"})`,
|
||||||
|
`sidebar (${device.isMobile ? "mobile" : "desktop"})`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!device.isMobile && (
|
||||||
|
<div className="ToolIcon__icon__close">
|
||||||
|
<button
|
||||||
|
className="Modal__close"
|
||||||
|
onClick={() =>
|
||||||
|
setAppState({
|
||||||
|
isLibraryOpen: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
aria-label={t("buttons.close")}
|
||||||
|
>
|
||||||
|
{close}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLibraryMenuItems = () => {
|
||||||
|
return (
|
||||||
|
<Stack.Col
|
||||||
|
className="library-menu-items-container__items"
|
||||||
|
align="start"
|
||||||
|
gap={1}
|
||||||
|
style={{
|
||||||
|
flex: publishedItems.length > 0 ? 1 : "0 0 auto",
|
||||||
|
marginBottom: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<div className="separator">
|
||||||
|
{(pendingElements.length > 0 ||
|
||||||
|
unpublishedItems.length > 0 ||
|
||||||
|
publishedItems.length > 0) && (
|
||||||
|
<div>{t("labels.personalLib")}</div>
|
||||||
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: "auto",
|
||||||
|
marginRight: "1rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
fontWeight: "normal",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ transform: "translateY(2px)" }}>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!pendingElements.length && !unpublishedItems.length ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 65,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "100%",
|
||||||
|
fontSize: ".9rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No items yet!
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: ".6rem 0",
|
||||||
|
fontSize: ".8em",
|
||||||
|
width: "70%",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{publishedItems.length > 0
|
||||||
|
? t("library.hint_emptyPrivateLibrary")
|
||||||
|
: t("library.hint_emptyLibrary")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
renderLibrarySection([
|
||||||
|
// append pending library item
|
||||||
|
...(pendingElements.length
|
||||||
|
? [{ id: null, elements: pendingElements }]
|
||||||
|
: []),
|
||||||
|
...unpublishedItems,
|
||||||
|
])
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
<>
|
||||||
|
{(publishedItems.length > 0 ||
|
||||||
|
(!device.isMobile &&
|
||||||
|
(pendingElements.length > 0 || unpublishedItems.length > 0))) && (
|
||||||
|
<div className="separator">{t("labels.excalidrawLib")}</div>
|
||||||
|
)}
|
||||||
|
{publishedItems.length > 0 && renderLibrarySection(publishedItems)}
|
||||||
|
</>
|
||||||
|
</Stack.Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLibraryFooter = () => {
|
||||||
|
return (
|
||||||
<a
|
<a
|
||||||
|
className="library-menu-browse-button"
|
||||||
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
|
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
|
||||||
window.name || "_blank"
|
window.name || "_blank"
|
||||||
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
|
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
|
||||||
@ -374,30 +517,25 @@ const LibraryMenuItems = ({
|
|||||||
>
|
>
|
||||||
{t("labels.libraries")}
|
{t("labels.libraries")}
|
||||||
</a>
|
</a>
|
||||||
)}
|
);
|
||||||
</div>
|
};
|
||||||
<Stack.Col
|
|
||||||
className="library-menu-items-container__items"
|
return (
|
||||||
align="start"
|
<div
|
||||||
gap={1}
|
className="library-menu-items-container"
|
||||||
|
style={
|
||||||
|
device.isMobile
|
||||||
|
? {
|
||||||
|
minHeight: "200px",
|
||||||
|
maxHeight: "70vh",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<>
|
{showRemoveLibAlert && renderRemoveLibAlert()}
|
||||||
<div className="separator">{t("labels.personalLib")}</div>
|
{renderLibraryHeader()}
|
||||||
{renderLibrarySection([
|
{renderLibraryMenuItems()}
|
||||||
// append pending library item
|
{!device.isMobile && renderLibraryFooter()}
|
||||||
...(pendingElements.length
|
|
||||||
? [{ id: null, elements: pendingElements }]
|
|
||||||
: []),
|
|
||||||
...unpublishedItems,
|
|
||||||
])}
|
|
||||||
</>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<div className="separator">{t("labels.excalidrawLib")} </div>
|
|
||||||
|
|
||||||
{renderLibrarySection(publishedItems)}
|
|
||||||
</>
|
|
||||||
</Stack.Col>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
.library-unit {
|
.library-unit {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid var(--button-gray-2);
|
border: 1px solid transparent;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -21,10 +21,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.theme--dark .library-unit {
|
|
||||||
border-color: rgb(48, 48, 48);
|
|
||||||
}
|
|
||||||
|
|
||||||
.library-unit__dragger {
|
.library-unit__dragger {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useDeviceType } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
import { exportToSvg } from "../scene/export";
|
import { exportToSvg } from "../scene/export";
|
||||||
import { BinaryFiles, LibraryItem } from "../types";
|
import { BinaryFiles, LibraryItem } from "../types";
|
||||||
import "./LibraryUnit.scss";
|
import "./LibraryUnit.scss";
|
||||||
@ -67,7 +67,7 @@ export const LibraryUnit = ({
|
|||||||
}, [elements, files]);
|
}, [elements, files]);
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const isMobile = useDeviceType().isMobile;
|
const isMobile = useDevice().isMobile;
|
||||||
const adder = isPending && (
|
const adder = isPending && (
|
||||||
<div className="library-unit__adder">{PLUS_ICON}</div>
|
<div className="library-unit__adder">{PLUS_ICON}</div>
|
||||||
);
|
);
|
||||||
|
@ -43,6 +43,7 @@ type MobileMenuProps = {
|
|||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
|
renderStats: () => JSX.Element | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MobileMenu = ({
|
export const MobileMenu = ({
|
||||||
@ -63,6 +64,7 @@ export const MobileMenu = ({
|
|||||||
showThemeBtn,
|
showThemeBtn,
|
||||||
onImageAction,
|
onImageAction,
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
|
renderStats,
|
||||||
}: MobileMenuProps) => {
|
}: MobileMenuProps) => {
|
||||||
const renderToolbar = () => {
|
const renderToolbar = () => {
|
||||||
return (
|
return (
|
||||||
@ -184,6 +186,7 @@ export const MobileMenu = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!viewModeEnabled && renderToolbar()}
|
{!viewModeEnabled && renderToolbar()}
|
||||||
|
{renderStats()}
|
||||||
<div
|
<div
|
||||||
className="App-bottom-bar"
|
className="App-bottom-bar"
|
||||||
style={{
|
style={{
|
||||||
|
@ -4,7 +4,7 @@ import React, { useState, useLayoutEffect, useRef } from "react";
|
|||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { useExcalidrawContainer, useDeviceType } from "./App";
|
import { useExcalidrawContainer, useDevice } from "./App";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { THEME } from "../constants";
|
import { THEME } from "../constants";
|
||||||
|
|
||||||
@ -59,17 +59,17 @@ export const Modal = (props: {
|
|||||||
const useBodyRoot = (theme: AppState["theme"]) => {
|
const useBodyRoot = (theme: AppState["theme"]) => {
|
||||||
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const deviceType = useDeviceType();
|
const device = useDevice();
|
||||||
const isMobileRef = useRef(deviceType.isMobile);
|
const isMobileRef = useRef(device.isMobile);
|
||||||
isMobileRef.current = deviceType.isMobile;
|
isMobileRef.current = device.isMobile;
|
||||||
|
|
||||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (div) {
|
if (div) {
|
||||||
div.classList.toggle("excalidraw--mobile", deviceType.isMobile);
|
div.classList.toggle("excalidraw--mobile", device.isMobile);
|
||||||
}
|
}
|
||||||
}, [div, deviceType.isMobile]);
|
}, [div, device.isMobile]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const isDarkTheme =
|
const isDarkTheme =
|
||||||
|
22
src/components/SidebarLockButton.scss
Normal file
22
src/components/SidebarLockButton.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.layer-ui__sidebar-lock-button {
|
||||||
|
@include toolbarButtonColorStates;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
}
|
||||||
|
.ToolIcon_type_floating .side_lock_icon {
|
||||||
|
width: calc(var(--space-factor) * 7);
|
||||||
|
height: calc(var(--space-factor) * 7);
|
||||||
|
svg {
|
||||||
|
// mirror
|
||||||
|
transform: scale(-1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon_type_checkbox {
|
||||||
|
&:not(.ToolIcon_toggle_opaque):checked + .side_lock_icon {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/components/SidebarLockButton.tsx
Normal file
46
src/components/SidebarLockButton.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import "./ToolIcon.scss";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { ToolButtonSize } from "./ToolButton";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { Tooltip } from "./Tooltip";
|
||||||
|
|
||||||
|
import "./SidebarLockButton.scss";
|
||||||
|
|
||||||
|
type SidebarLockIconProps = {
|
||||||
|
checked: boolean;
|
||||||
|
onChange?(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SIZE: ToolButtonSize = "medium";
|
||||||
|
|
||||||
|
const SIDE_LIBRARY_TOGGLE_ICON = (
|
||||||
|
<svg viewBox="0 0 24 24" fill="#ffffff">
|
||||||
|
<path d="M19 22H5a3 3 0 01-3-3V5a3 3 0 013-3h14a3 3 0 013 3v14a3 3 0 01-3 3zm0-18h-9v16h9a1.01 1.01 0 001-1V5a1.01 1.01 0 00-1-1z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SidebarLockButton = (props: SidebarLockIconProps) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label={t("labels.sidebarLock")}>
|
||||||
|
<label
|
||||||
|
className={clsx(
|
||||||
|
"ToolIcon ToolIcon__lock ToolIcon_type_floating",
|
||||||
|
`ToolIcon_size_${DEFAULT_SIZE}`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="ToolIcon_type_checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
onChange={props.onChange}
|
||||||
|
checked={props.checked}
|
||||||
|
aria-label={t("labels.sidebarLock")}
|
||||||
|
/>{" "}
|
||||||
|
<div className="ToolIcon__icon side_lock_icon" tabIndex={0}>
|
||||||
|
{SIDE_LIBRARY_TOGGLE_ICON}
|
||||||
|
</div>{" "}
|
||||||
|
</label>{" "}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
@ -41,6 +41,7 @@ const ColStack = ({
|
|||||||
align,
|
align,
|
||||||
justifyContent,
|
justifyContent,
|
||||||
className,
|
className,
|
||||||
|
style,
|
||||||
}: StackProps) => {
|
}: StackProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -49,6 +50,7 @@ const ColStack = ({
|
|||||||
"--gap": gap,
|
"--gap": gap,
|
||||||
justifyItems: align,
|
justifyItems: align,
|
||||||
justifyContent,
|
justifyContent,
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
right: 12px;
|
right: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 24px 8px 0;
|
margin: 0 24px 8px 0;
|
||||||
|
@ -2,7 +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 { useDeviceType } from "../components/App";
|
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";
|
||||||
@ -16,16 +16,13 @@ export const Stats = (props: {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||||
}) => {
|
}) => {
|
||||||
const deviceType = useDeviceType();
|
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) {
|
||||||
if (deviceType.isMobile && props.appState.openMenu) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Stats">
|
<div className="Stats">
|
||||||
<Island padding={2}>
|
<Island padding={2}>
|
||||||
|
@ -1,26 +1,5 @@
|
|||||||
@import "open-color/open-color.scss";
|
@import "open-color/open-color.scss";
|
||||||
|
@import "../css/variables.module";
|
||||||
@mixin toolbarButtonColorStates {
|
|
||||||
.ToolIcon_type_radio,
|
|
||||||
.ToolIcon_type_checkbox {
|
|
||||||
& + .ToolIcon__icon:active {
|
|
||||||
background: var(--color-primary-light);
|
|
||||||
}
|
|
||||||
&:checked + .ToolIcon__icon {
|
|
||||||
background: var(--color-primary);
|
|
||||||
--icon-fill-color: #{$oc-white};
|
|
||||||
--keybinding-color: #{$oc-white};
|
|
||||||
}
|
|
||||||
&:checked + .ToolIcon__icon:active {
|
|
||||||
background: var(--color-primary-darker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ToolIcon__keybinding {
|
|
||||||
bottom: 4px;
|
|
||||||
right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.App-toolbar-container {
|
.App-toolbar-container {
|
||||||
|
@ -155,9 +155,19 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// breakpoints
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// sm screen
|
||||||
|
export const MQ_SM_MAX_WIDTH = 640;
|
||||||
|
// md screen
|
||||||
export const MQ_MAX_WIDTH_PORTRAIT = 730;
|
export const MQ_MAX_WIDTH_PORTRAIT = 730;
|
||||||
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
||||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||||
|
// sidebar
|
||||||
|
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const LIBRARY_SIDEBAR_WIDTH = parseInt(cssVariables.rightSidebarWidth);
|
||||||
|
|
||||||
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
|
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
|
||||||
|
|
||||||
|
@ -350,7 +350,6 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
left: 0.25rem;
|
left: 0.25rem;
|
||||||
@ -391,6 +390,7 @@
|
|||||||
|
|
||||||
.App-menu__left {
|
.App-menu__left {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
box-shadow: var(--shadow-island);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-select {
|
.dropdown-select {
|
||||||
@ -449,6 +449,7 @@
|
|||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-icon {
|
.help-icon {
|
||||||
@ -567,6 +568,22 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use custom, minimalistic scrollbar
|
||||||
|
// (doesn't work in Firefox)
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--button-gray-2);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--button-gray-3);
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:active {
|
||||||
|
background: var(--button-gray-2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ErrorSplash.excalidraw {
|
.ErrorSplash.excalidraw {
|
||||||
|
@ -6,8 +6,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin toolbarButtonColorStates {
|
||||||
|
.ToolIcon_type_radio,
|
||||||
|
.ToolIcon_type_checkbox {
|
||||||
|
& + .ToolIcon__icon:active {
|
||||||
|
background: var(--color-primary-light);
|
||||||
|
}
|
||||||
|
&:checked + .ToolIcon__icon {
|
||||||
|
background: var(--color-primary);
|
||||||
|
--icon-fill-color: #{$oc-white};
|
||||||
|
--keybinding-color: #{$oc-white};
|
||||||
|
}
|
||||||
|
&:checked + .ToolIcon__icon:active {
|
||||||
|
background: var(--color-primary-darker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon__keybinding {
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$theme-filter: "invert(93%) hue-rotate(180deg)";
|
$theme-filter: "invert(93%) hue-rotate(180deg)";
|
||||||
|
$right-sidebar-width: "302px";
|
||||||
|
|
||||||
:export {
|
:export {
|
||||||
themeFilter: unquote($theme-filter);
|
themeFilter: unquote($theme-filter);
|
||||||
|
rightSidebarWidth: unquote($right-sidebar-width);
|
||||||
}
|
}
|
||||||
|
@ -283,6 +283,11 @@ export const restoreAppState = (
|
|||||||
value: appState.zoom as NormalizedZoomValue,
|
value: appState.zoom as NormalizedZoomValue,
|
||||||
}
|
}
|
||||||
: appState.zoom || defaultAppState.zoom,
|
: appState.zoom || defaultAppState.zoom,
|
||||||
|
// when sidebar docked and user left it open in last session,
|
||||||
|
// keep it open. If not docked, keep it closed irrespective of last state.
|
||||||
|
isLibraryOpen: nextAppState.isLibraryMenuDocked
|
||||||
|
? nextAppState.isLibraryOpen
|
||||||
|
: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -120,7 +120,12 @@
|
|||||||
"lockAll": "Lock all",
|
"lockAll": "Lock all",
|
||||||
"unlockAll": "Unlock all"
|
"unlockAll": "Unlock all"
|
||||||
},
|
},
|
||||||
"statusPublished": "Published"
|
"statusPublished": "Published",
|
||||||
|
"sidebarLock": "Keep sidebar open"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"hint_emptyLibrary": "Select an item on canvas to add it here, or install a library from the public repository, below.",
|
||||||
|
"hint_emptyPrivateLibrary": "Select an item on canvas to add it here."
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Reset the canvas",
|
"clearReset": "Reset the canvas",
|
||||||
|
@ -17,6 +17,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
#### Features
|
#### Features
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
- Export API to [set](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#setCursor) and [reset](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#resetCursor) mouse cursor on the canvas [#5215](https://github.com/excalidraw/excalidraw/pull/5215).
|
- Export API to [set](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#setCursor) and [reset](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#resetCursor) mouse cursor on the canvas [#5215](https://github.com/excalidraw/excalidraw/pull/5215).
|
||||||
|
@ -639,7 +639,7 @@ This prop sets the name of the drawing which will be used when exporting the dra
|
|||||||
|
|
||||||
#### `UIOptions`
|
#### `UIOptions`
|
||||||
|
|
||||||
This prop can be used to customise UI of Excalidraw. Currently we support customising only [`canvasActions`](#canvasActions). It accepts the below parameters
|
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasActions) and [`dockedSidebarBreakpoint`](dockedSidebarBreakpoint). It accepts the below parameters
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
{ canvasActions: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L208"> CanvasActions<a/> }
|
{ canvasActions: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L208"> CanvasActions<a/> }
|
||||||
@ -657,6 +657,12 @@ This prop can be used to customise UI of Excalidraw. Currently we support custom
|
|||||||
| `theme` | boolean | true | Implies whether to show `Theme toggle` |
|
| `theme` | boolean | true | Implies whether to show `Theme toggle` |
|
||||||
| `saveAsImage` | boolean | true | Implies whether to show `Save as image button` |
|
| `saveAsImage` | boolean | true | Implies whether to show `Save as image button` |
|
||||||
|
|
||||||
|
##### `dockedSidebarBreakpoint`
|
||||||
|
|
||||||
|
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L167). If the `width` of the `excalidraw` container exceeds `dockedSidebarBreakpoint`, the sidebar will be dockable. If user choses to dock the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
|
||||||
|
|
||||||
|
![image](https://user-images.githubusercontent.com/11256141/174664866-c698c3fa-197b-43ff-956c-d79852c7b326.png)
|
||||||
|
|
||||||
#### `exportOpts`
|
#### `exportOpts`
|
||||||
|
|
||||||
The below attributes can be set in `UIOptions.canvasActions.export` to customize the export dialog. If `UIOptions.canvasActions.export` is `false` the export button will not be rendered.
|
The below attributes can be set in `UIOptions.canvasActions.export` to customize the export dialog. If `UIOptions.canvasActions.export` is `false` the export button will not be rendered.
|
||||||
|
@ -44,6 +44,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
const canvasActions = props.UIOptions?.canvasActions;
|
const canvasActions = props.UIOptions?.canvasActions;
|
||||||
|
|
||||||
const UIOptions: AppProps["UIOptions"] = {
|
const UIOptions: AppProps["UIOptions"] = {
|
||||||
|
...props.UIOptions,
|
||||||
canvasActions: {
|
canvasActions: {
|
||||||
...DEFAULT_UI_OPTIONS.canvasActions,
|
...DEFAULT_UI_OPTIONS.canvasActions,
|
||||||
...canvasActions,
|
...canvasActions,
|
||||||
|
@ -38,6 +38,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -211,6 +212,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -388,6 +390,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -726,6 +729,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1064,6 +1068,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1241,6 +1246,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1454,6 +1460,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1726,6 +1733,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -2082,6 +2090,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -2882,6 +2891,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -3220,6 +3230,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -3558,6 +3569,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -3976,6 +3988,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4254,6 +4267,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4613,6 +4627,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4719,6 +4734,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4803,6 +4819,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
|
@ -38,6 +38,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -547,6 +548,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1062,6 +1064,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": false,
|
"isBindingEnabled": false,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1922,6 +1925,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -2143,6 +2147,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -2649,6 +2654,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -2925,6 +2931,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -3102,6 +3109,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -3591,6 +3599,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -3848,6 +3857,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4069,6 +4079,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4334,6 +4345,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4609,6 +4621,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5031,6 +5044,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5355,6 +5369,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5654,6 +5669,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5881,6 +5897,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6058,6 +6075,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6559,6 +6577,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6907,6 +6926,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -9146,6 +9166,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -9544,6 +9565,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -9820,6 +9842,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -10057,6 +10080,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -10363,6 +10387,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -10540,6 +10565,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -10717,6 +10743,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -10894,6 +10921,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11101,6 +11129,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11308,6 +11337,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11533,6 +11563,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11740,6 +11771,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11917,6 +11949,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -12124,6 +12157,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -12301,6 +12335,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -12478,6 +12513,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -12703,6 +12739,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -13497,6 +13534,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -13773,6 +13811,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -13881,6 +13920,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -13987,6 +14027,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -14167,6 +14208,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -14518,6 +14560,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -14735,6 +14778,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -15647,6 +15691,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -15753,6 +15798,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -16592,6 +16638,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -17039,6 +17086,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -17338,6 +17386,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -17446,6 +17495,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -17990,6 +18040,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -18096,6 +18147,7 @@ Object {
|
|||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
|
@ -38,6 +38,7 @@ Object {
|
|||||||
"fileHandle": null,
|
"fileHandle": null,
|
||||||
"gridSize": null,
|
"gridSize": null,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isLibraryMenuDocked": false,
|
||||||
"isLibraryOpen": false,
|
"isLibraryOpen": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
|
26
src/types.ts
26
src/types.ts
@ -162,6 +162,7 @@ export type AppState = {
|
|||||||
offsetLeft: number;
|
offsetLeft: number;
|
||||||
|
|
||||||
isLibraryOpen: boolean;
|
isLibraryOpen: boolean;
|
||||||
|
isLibraryMenuDocked: boolean;
|
||||||
fileHandle: FileSystemHandle | null;
|
fileHandle: FileSystemHandle | null;
|
||||||
collaborators: Map<string, Collaborator>;
|
collaborators: Map<string, Collaborator>;
|
||||||
showStats: boolean;
|
showStats: boolean;
|
||||||
@ -291,7 +292,10 @@ export interface ExcalidrawProps {
|
|||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
UIOptions?: UIOptions;
|
UIOptions?: {
|
||||||
|
dockedSidebarBreakpoint?: number;
|
||||||
|
canvasActions?: CanvasActions;
|
||||||
|
};
|
||||||
detectScroll?: boolean;
|
detectScroll?: boolean;
|
||||||
handleKeyboardGlobally?: boolean;
|
handleKeyboardGlobally?: boolean;
|
||||||
onLibraryChange?: (libraryItems: LibraryItems) => void | Promise<any>;
|
onLibraryChange?: (libraryItems: LibraryItems) => void | Promise<any>;
|
||||||
@ -349,18 +353,18 @@ type CanvasActions = {
|
|||||||
saveAsImage?: boolean;
|
saveAsImage?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UIOptions = {
|
export type AppProps = Merge<
|
||||||
canvasActions?: CanvasActions;
|
ExcalidrawProps,
|
||||||
};
|
{
|
||||||
|
|
||||||
export type AppProps = ExcalidrawProps & {
|
|
||||||
UIOptions: {
|
UIOptions: {
|
||||||
canvasActions: Required<CanvasActions> & { export: ExportOpts };
|
canvasActions: Required<CanvasActions> & { export: ExportOpts };
|
||||||
|
dockedSidebarBreakpoint?: number;
|
||||||
};
|
};
|
||||||
detectScroll: boolean;
|
detectScroll: boolean;
|
||||||
handleKeyboardGlobally: boolean;
|
handleKeyboardGlobally: boolean;
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
};
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
/** A subset of App class properties that we need to use elsewhere
|
/** A subset of App class properties that we need to use elsewhere
|
||||||
* in the app, eg Manager. Factored out into a separate type to keep DRY. */
|
* in the app, eg Manager. Factored out into a separate type to keep DRY. */
|
||||||
@ -377,7 +381,7 @@ export type AppClassProperties = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
deviceType: App["deviceType"];
|
device: App["device"];
|
||||||
scene: App["scene"];
|
scene: App["scene"];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -473,7 +477,9 @@ export type ExcalidrawImperativeAPI = {
|
|||||||
resetCursor: InstanceType<typeof App>["resetCursor"];
|
resetCursor: InstanceType<typeof App>["resetCursor"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeviceType = {
|
export type Device = Readonly<{
|
||||||
|
isSmScreen: boolean;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
isTouchScreen: boolean;
|
isTouchScreen: boolean;
|
||||||
};
|
canDeviceFitSidebar: boolean;
|
||||||
|
}>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user