phx-react
Version:
PHX REACT
136 lines • 6.8 kB
JavaScript
import { Excalidraw } from '@excalidraw/excalidraw';
import * as React from 'react';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import Button from '../../ui/Button';
import Modal from '../../ui/Modal';
export const useCallbackRefState = () => {
const [refValue, setRefValue] = React.useState(null);
const refCallback = React.useCallback((value) => setRefValue(value), []);
return [refValue, refCallback];
};
/**
* @explorer-desc
* A component which renders a modal with Excalidraw (a painting app)
* which can be used to export an editable image
*/
export default function ExcalidrawModal({ closeOnClickOutside = false, initialAppState, initialElements, initialFiles, isShown = false, onClose, onDelete, onSave, }) {
const excaliDrawModelRef = useRef(null);
const [excalidrawAPI, excalidrawAPIRefCallback] = useCallbackRefState();
const [discardModalOpen, setDiscardModalOpen] = useState(false);
const [elements, setElements] = useState(initialElements);
const [files, setFiles] = useState(initialFiles);
useEffect(() => {
if (excaliDrawModelRef.current !== null) {
excaliDrawModelRef.current.focus();
}
}, []);
useEffect(() => {
var _a;
let modalOverlayElement = null;
const clickOutsideHandler = (event) => {
const target = event.target;
if (excaliDrawModelRef.current !== null &&
!excaliDrawModelRef.current.contains(target) &&
closeOnClickOutside) {
onDelete();
}
};
if (excaliDrawModelRef.current !== null) {
modalOverlayElement = (_a = excaliDrawModelRef.current) === null || _a === void 0 ? void 0 : _a.parentElement;
if (modalOverlayElement !== null) {
modalOverlayElement === null || modalOverlayElement === void 0 ? void 0 : modalOverlayElement.addEventListener('click', clickOutsideHandler);
}
}
return () => {
if (modalOverlayElement !== null) {
modalOverlayElement === null || modalOverlayElement === void 0 ? void 0 : modalOverlayElement.removeEventListener('click', clickOutsideHandler);
}
};
}, [closeOnClickOutside, onDelete]);
useLayoutEffect(() => {
const currentModalRef = excaliDrawModelRef.current;
const onKeyDown = (event) => {
if (event.key === 'Escape') {
onDelete();
}
};
if (currentModalRef !== null) {
currentModalRef.addEventListener('keydown', onKeyDown);
}
return () => {
if (currentModalRef !== null) {
currentModalRef.removeEventListener('keydown', onKeyDown);
}
};
}, [elements, files, onDelete]);
const save = () => {
if (elements.filter((el) => !el.isDeleted).length > 0) {
const appState = excalidrawAPI === null || excalidrawAPI === void 0 ? void 0 : excalidrawAPI.getAppState();
// We only need a subset of the state
const partialState = {
exportBackground: appState === null || appState === void 0 ? void 0 : appState.exportBackground,
exportScale: appState === null || appState === void 0 ? void 0 : appState.exportScale,
exportWithDarkMode: (appState === null || appState === void 0 ? void 0 : appState.theme) === 'dark',
isBindingEnabled: appState === null || appState === void 0 ? void 0 : appState.isBindingEnabled,
isLoading: appState === null || appState === void 0 ? void 0 : appState.isLoading,
name: appState === null || appState === void 0 ? void 0 : appState.name,
theme: appState === null || appState === void 0 ? void 0 : appState.theme,
viewBackgroundColor: appState === null || appState === void 0 ? void 0 : appState.viewBackgroundColor,
viewModeEnabled: appState === null || appState === void 0 ? void 0 : appState.viewModeEnabled,
zenModeEnabled: appState === null || appState === void 0 ? void 0 : appState.zenModeEnabled,
zoom: appState === null || appState === void 0 ? void 0 : appState.zoom,
};
onSave(elements, partialState, files);
}
else {
// delete node if the scene is clear
onDelete();
}
};
const discard = () => {
if (elements.filter((el) => !el.isDeleted).length === 0) {
// delete node if the scene is clear
onDelete();
}
else {
// Otherwise, show confirmation dialog before closing
setDiscardModalOpen(true);
}
};
function ShowDiscardDialog() {
return (React.createElement(Modal, { closeOnClickOutside: false, onClose: () => {
setDiscardModalOpen(false);
}, title: 'Discard' },
"Are you sure you want to discard the changes?",
React.createElement("div", { className: 'ExcalidrawModal__discardModal' },
React.createElement(Button, { onClick: () => {
setDiscardModalOpen(false);
onClose();
} }, "Discard"),
' ',
React.createElement(Button, { onClick: () => {
setDiscardModalOpen(false);
} }, "Cancel"))));
}
if (isShown === false) {
return null;
}
const onChange = (els, _, fls) => {
setElements(els);
setFiles(fls);
};
return createPortal(React.createElement("div", { className: 'ExcalidrawModal__overlay', role: 'dialog' },
React.createElement("div", { ref: excaliDrawModelRef, className: 'ExcalidrawModal__modal', tabIndex: -1 },
React.createElement("div", { className: 'ExcalidrawModal__row' },
discardModalOpen && React.createElement(ShowDiscardDialog, null),
React.createElement(Excalidraw, { excalidrawAPI: excalidrawAPIRefCallback, initialData: {
appState: initialAppState || { isLoading: false },
elements: initialElements,
files: initialFiles,
}, onChange: onChange }),
React.createElement("div", { className: 'ExcalidrawModal__actions' },
React.createElement("button", { className: 'action-button', onClick: discard, type: 'button' }, "Discard"),
React.createElement("button", { className: 'action-button', onClick: save, type: 'button' }, "Save"))))), document.body);
}
//# sourceMappingURL=ExcalidrawModal.js.map