Use one canvas for minimap and preserve viewport borders

This commit is contained in:
tk338g 2021-02-08 00:55:22 +01:00
parent 4c3544df4a
commit 4e3bf7e8d2

View File

@ -1,34 +1,17 @@
import "./MiniMap.scss"; import "./MiniMap.scss";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { unmountComponentAtNode, render } from "react-dom";
import { canvasToBlob } from "../data/blob";
import { getCommonBounds } from "../element"; import { getCommonBounds } from "../element";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../element/types";
import { CanvasError } from "../errors";
import { exportToCanvas } from "../scene/export"; import { exportToCanvas } from "../scene/export";
import { AppState } from "../types"; import { AppState } from "../types";
import { distance, viewportCoordsToSceneCoords } from "../utils"; import { distance, viewportCoordsToSceneCoords } from "../utils";
import { ErrorCanvasPreview } from "./ExportDialog";
import { Island } from "./Island"; import { Island } from "./Island";
const RATIO = 1.2; const RATIO = 1.2;
const MINIMAP_HEIGHT = 150; const MINIMAP_HEIGHT = 150;
const MINIMAP_WIDTH = MINIMAP_HEIGHT * RATIO; const MINIMAP_WIDTH = MINIMAP_HEIGHT * RATIO;
const renderPreview = (
content: HTMLCanvasElement | Error,
previewNode: HTMLDivElement,
) => {
unmountComponentAtNode(previewNode);
previewNode.innerHTML = "";
if (content instanceof HTMLCanvasElement) {
previewNode.appendChild(content);
} else {
render(<ErrorCanvasPreview />, previewNode);
}
};
const MinimapViewport = ({ const MinimapViewport = ({
elements, elements,
appState, appState,
@ -40,10 +23,11 @@ const MinimapViewport = ({
return null; return null;
} }
const [minX, minY, canvasWidth, canvasHeight] = getCanvasSize(elements); const [minX, minY, maxX, maxY] = getCommonBounds(elements);
const minimapScale = Math.min( const minimapScale = Math.min(
MINIMAP_WIDTH / canvasWidth, MINIMAP_WIDTH / distance(minX, maxX),
MINIMAP_HEIGHT / canvasHeight, MINIMAP_HEIGHT / distance(minY, maxY),
); );
const leftTop = viewportCoordsToSceneCoords( const leftTop = viewportCoordsToSceneCoords(
@ -60,6 +44,20 @@ const MinimapViewport = ({
const width = (rightBot.x - leftTop.x) * minimapScale; const width = (rightBot.x - leftTop.x) * minimapScale;
const height = (rightBot.y - leftTop.y) * minimapScale; const height = (rightBot.y - leftTop.y) * minimapScale;
// Set viewport boundaries
const viewportTop = Math.min(Math.max(0, top), MINIMAP_HEIGHT);
const viewportLeft = Math.min(Math.max(0, left), MINIMAP_WIDTH);
const viewportWidth = Math.min(
MINIMAP_WIDTH - viewportLeft,
width,
width + left,
);
const viewportHeight = Math.min(
MINIMAP_HEIGHT - viewportTop,
height,
height + top,
);
return ( return (
<div <div
style={{ style={{
@ -67,25 +65,15 @@ const MinimapViewport = ({
boxSizing: "border-box", boxSizing: "border-box",
position: "absolute", position: "absolute",
pointerEvents: "none", pointerEvents: "none",
top: Math.max(0, top), top: viewportTop,
left: Math.max(0, left), left: viewportLeft,
width: Math.min(MINIMAP_WIDTH - Math.max(0, left), width), width: viewportWidth,
height: Math.min(MINIMAP_HEIGHT - Math.max(0, top), height), height: viewportHeight,
}} }}
/> />
); );
}; };
const getCanvasSize = (
elements: readonly NonDeletedExcalidrawElement[],
): [number, number, number, number] => {
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
const width = distance(minX, maxX);
const height = distance(minY, maxY);
return [minX, minY, width, height];
};
export function MiniMap({ export function MiniMap({
appState, appState,
elements, elements,
@ -93,20 +81,16 @@ export function MiniMap({
appState: AppState; appState: AppState;
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
}) { }) {
const previewRef = useRef<HTMLDivElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const appStateRef = useRef<AppState>(appState); const appStateRef = useRef<AppState>(appState);
appStateRef.current = appState; appStateRef.current = appState;
useEffect(() => { useEffect(() => {
const previewNode = previewRef.current; const canvasNode = canvasRef.current;
if (!previewNode) { if (!canvasNode) {
return; return;
} }
if (elements.length === 0) { exportToCanvas(
unmountComponentAtNode(previewNode);
previewNode.innerHTML = "";
}
const canvas = exportToCanvas(
elements, elements,
appStateRef.current, appStateRef.current,
{ {
@ -115,26 +99,16 @@ export function MiniMap({
shouldAddWatermark: false, shouldAddWatermark: false,
}, },
(width, height) => { (width, height) => {
const tempCanvas = document.createElement("canvas");
const scale = Math.min(MINIMAP_WIDTH / width, MINIMAP_HEIGHT / height); const scale = Math.min(MINIMAP_WIDTH / width, MINIMAP_HEIGHT / height);
tempCanvas.width = width * scale; canvasNode.width = width * scale;
tempCanvas.height = height * scale; canvasNode.height = height * scale;
return { return {
canvas: tempCanvas, canvas: canvasNode,
scale, scale,
}; };
}, },
); );
canvasToBlob(canvas)
.then(() => {
renderPreview(canvas, previewNode);
})
.catch((error) => {
console.error(error);
renderPreview(new CanvasError(), previewNode);
});
}, [elements]); }, [elements]);
return ( return (
@ -147,7 +121,7 @@ export function MiniMap({
backgroundColor: appState.viewBackgroundColor, backgroundColor: appState.viewBackgroundColor,
}} }}
> >
<div ref={previewRef} /> <canvas ref={canvasRef} />
<MinimapViewport elements={elements} appState={appState} /> <MinimapViewport elements={elements} appState={appState} />
</div> </div>
</Island> </Island>