diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index 93ae38478c..ea4f8d067e 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -27,6 +27,7 @@ import { LiveCollaborationTrigger, TTDDialog, TTDDialogTrigger, + getCommonBounds, } from "../packages/excalidraw/index"; import { AppState, @@ -35,6 +36,7 @@ import { BinaryFiles, ExcalidrawInitialDataState, UIAppState, + ScrollConstraints, } from "../packages/excalidraw/types"; import { debounce, @@ -98,15 +100,161 @@ import { useAtomWithInitialValue } from "../packages/excalidraw/jotai"; import { appJotaiStore } from "./app-jotai"; import "./index.scss"; -import { ResolutionType } from "../packages/excalidraw/utility-types"; +import { Merge, ResolutionType } from "../packages/excalidraw/utility-types"; import { ShareableLinkDialog } from "../packages/excalidraw/components/ShareableLinkDialog"; import { openConfirmModal } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState"; import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm"; import Trans from "../packages/excalidraw/components/Trans"; import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog"; +import { getSelectedElements } from "../packages/excalidraw/scene"; polyfill(); +type DebugScrollConstraints = Merge< + ScrollConstraints, + { viewportZoomFactor: number; enabled: boolean } +>; + +const ConstraintsSettings = ({ + initialConstraints, + excalidrawAPI, +}: { + initialConstraints: DebugScrollConstraints; + excalidrawAPI: ExcalidrawImperativeAPI; +}) => { + const [constraints, setConstraints] = + useState(initialConstraints); + + useEffect(() => { + // add JSON-stringified constraints into url hash for easy sharing + const hash = new URLSearchParams(window.location.hash.slice(1)); + hash.set( + "constraints", + encodeURIComponent( + window.btoa(JSON.stringify(constraints)).replace(/=+/, ""), + ), + ); + window.location.hash = decodeURIComponent(hash.toString()); + }, [constraints]); + + const [selection, setSelection] = useState([]); + useEffect(() => { + return excalidrawAPI.onChange((elements, appState) => { + setSelection(getSelectedElements(elements, appState)); + }); + }, [excalidrawAPI]); + + return ( +
+ enabled:{" "} + + setConstraints((s) => ({ ...s, enabled: e.target.checked })) + } + /> + x:{" "} + + setConstraints((s) => ({ + ...s, + x: parseInt(e.target.value) ?? 0, + })) + } + /> + y:{" "} + + setConstraints((s) => ({ + ...s, + y: parseInt(e.target.value) ?? 0, + })) + } + /> + w:{" "} + + setConstraints((s) => ({ + ...s, + x: parseInt(e.target.value) ?? 200, + })) + } + /> + h:{" "} + + setConstraints((s) => ({ + ...s, + x: parseInt(e.target.value) ?? 200, + })) + } + /> + zoomFactor: + + setConstraints((s) => ({ + ...s, + viewportZoomFactor: parseFloat(e.target.value.toString()) ?? 0.7, + })) + } + /> + lockZoom:{" "} + + setConstraints((s) => ({ ...s, lockZoom: e.target.checked })) + } + /> + {selection.length > 0 && ( + + )} +
+ ); +}; + window.EXCALIDRAW_THROTTLE_RENDER = true; let isSelfEmbedding = false; @@ -151,10 +299,22 @@ const initializeScene = async (opts: { ) > => { const searchParams = new URLSearchParams(window.location.search); + const hashParams = new URLSearchParams(window.location.hash.slice(1)); const id = searchParams.get("id"); - const jsonBackendMatch = window.location.hash.match( - /^#json=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/, - ); + const shareableLink = hashParams.get("json")?.split(","); + + if (shareableLink) { + hashParams.delete("json"); + const hash = `#${decodeURIComponent(hashParams.toString())}`; + window.history.replaceState( + {}, + APP_NAME, + `${window.location.origin}${hash}`, + ); + } + + console.log({ shareableLink }); + const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/); const localDataState = importFromLocalStorage(); @@ -164,7 +324,7 @@ const initializeScene = async (opts: { } = await loadScene(null, null, localDataState); let roomLinkData = getCollaborationLinkData(window.location.href); - const isExternalScene = !!(id || jsonBackendMatch || roomLinkData); + const isExternalScene = !!(id || shareableLink || roomLinkData); if (isExternalScene) { if ( // don't prompt if scene is empty @@ -174,16 +334,17 @@ const initializeScene = async (opts: { // otherwise, prompt whether user wants to override current scene (await openConfirmModal(shareableLinkConfirmDialog)) ) { - if (jsonBackendMatch) { + if (shareableLink) { scene = await loadScene( - jsonBackendMatch[1], - jsonBackendMatch[2], + shareableLink[0], + shareableLink[1], localDataState, ); + console.log(">>>>", scene); } scene.scrollToContent = true; if (!roomLinkData) { - window.history.replaceState({}, APP_NAME, window.location.origin); + // window.history.replaceState({}, APP_NAME, window.location.origin); } } else { // https://github.com/excalidraw/excalidraw/issues/1919 @@ -200,7 +361,7 @@ const initializeScene = async (opts: { } roomLinkData = null; - window.history.replaceState({}, APP_NAME, window.location.origin); + // window.history.replaceState({}, APP_NAME, window.location.origin); } } else if (externalUrlMatch) { window.history.replaceState({}, APP_NAME, window.location.origin); @@ -261,12 +422,12 @@ const initializeScene = async (opts: { key: roomLinkData.roomKey, }; } else if (scene) { - return isExternalScene && jsonBackendMatch + return isExternalScene && shareableLink ? { scene, isExternalScene, - id: jsonBackendMatch[1], - key: jsonBackendMatch[2], + id: shareableLink[0], + key: shareableLink[1], } : { scene, isExternalScene: false }; } @@ -672,6 +833,31 @@ const ExcalidrawWrapper = () => { [setShareDialogState], ); + const [constraints] = useState(() => { + const stored = new URLSearchParams(location.hash.slice(1)).get( + "constraints", + ); + let storedConstraints = {}; + if (stored) { + try { + storedConstraints = JSON.parse(window.atob(stored)); + } catch {} + } + + return { + x: 0, + y: 0, + width: document.body.clientWidth, + height: document.body.clientHeight, + lockZoom: false, + viewportZoomFactor: 0.7, + enabled: true, + ...storedConstraints, + }; + }); + + console.log(constraints); + // browsers generally prevent infinite self-embedding, there are // cases where it still happens, and while we disallow self-embedding // by not whitelisting our own origin, this serves as an additional guard @@ -753,14 +939,14 @@ const ExcalidrawWrapper = () => { /> ); }} - scrollConstraints={{ - x: 0, - y: 0, - width: 2560, - height: 1300, - lockZoom: false, - }} + scrollConstraints={constraints.enabled ? constraints : undefined} > + {excalidrawAPI && ( + + )} { - const DEFAULT_VIEWPORT_ZOOM_FACTOR = 0.7; + const DEFAULT_VIEWPORT_ZOOM_FACTOR = 0.2; const viewportZoomFactor = scrollConstraints.viewportZoomFactor ? Math.min(1, Math.max(scrollConstraints.viewportZoomFactor, 0.1))