diff --git a/frontend/src/board/board-scroll-and-zoom.ts b/frontend/src/board/board-scroll-and-zoom.ts index ab14325b5..254122a15 100644 --- a/frontend/src/board/board-scroll-and-zoom.ts +++ b/frontend/src/board/board-scroll-and-zoom.ts @@ -6,6 +6,7 @@ import { Board } from "../../../common/src/domain" import * as G from "../../../common/src/geometry" import { BoardCoordinateHelper } from "./board-coordinates" import { ToolController } from "./tool-selection" +import { boardContentArea } from "./boardContentArea" export type BoardZoom = { zoom: number; quickZoom: number } export type ZoomAdjustMode = "preserveCursor" | "preserveCenter" @@ -42,14 +43,15 @@ export function boardScrollAndZoomHandler( (id) => "scrollAndZoom." + id, ) - L.view(scrollElement, boardElement, localStorageKey, (el, be, key) => ({ el, be, key })) + const boardIsNonEmpty = board.pipe(L.map((b) => Object.keys(b.items).length > 0)) + L.view(scrollElement, boardElement, localStorageKey, boardIsNonEmpty, (el, be, key, neb) => ({ el, be, key, neb })) .pipe(L.applyScope(componentScope())) .forEach(({ el, be, key }) => { if (el && be) { const storedScrollAndZoom = localStorage[key] setTimeout(() => { if (storedScrollAndZoom) { - //console.log("Init position for board", key) + console.log("Restoring scroll and zoom for board from localStorage") const parsed = JSON.parse(storedScrollAndZoom) el.scrollTop = parsed.y el.scrollLeft = parsed.x @@ -61,33 +63,6 @@ export function boardScrollAndZoomHandler( } }) - function boardContentArea(b: Board) { - const width = b.width / 10 - const height = b.height / 10 - let left = b.width / 2 - width / 2 - let top = b.height / 2 - height / 2 - let right = left + width - let bottom = top + height - - Object.values(b.items).forEach((item) => { - const { x, y, width, height } = item - left = Math.min(left, x) - top = Math.min(top, y) - right = Math.max(right, x + width) - bottom = Math.max(bottom, y + height) - }) - - const marginTop = height * 0.1 - const marginLeft = height * 0.1 - - return { - x: left - marginLeft, - y: top - marginTop, - width: right - left + 2 * marginLeft, - height: bottom - top + marginTop, - } - } - scrollAndZoom.pipe(L.changes, L.debounce(100), L.applyScope(componentScope())).forEach((s) => { //console.log("Store position for board", localStorageKey.get()) localStorage[localStorageKey.get()] = JSON.stringify({ ...s, zoom: s.zoom.zoom * s.zoom.quickZoom }) diff --git a/frontend/src/board/boardContentArea.ts b/frontend/src/board/boardContentArea.ts new file mode 100644 index 000000000..4962bd85d --- /dev/null +++ b/frontend/src/board/boardContentArea.ts @@ -0,0 +1,74 @@ +import _ from "lodash" +import { Board } from "../../../common/src/domain" +import { Rect } from "../../../common/src/geometry" + +function combineRects(r1: Rect, r2: Rect): Rect { + const left = Math.min(r1.x, r2.x) + const top = Math.min(r1.y, r2.y) + const right = Math.max(r1.x + r1.width, r2.x + r2.width) + const bottom = Math.max(r1.y + r1.height, r2.y + r2.height) + return { x: left, y: top, width: right - left, height: bottom - top } +} + +function itemToRect(item: Rect): Rect { + return { x: item.x, y: item.y, width: item.width, height: item.height } +} + +function addMargin(rect: Rect, margin: number): Rect { + return { + x: rect.x - margin, + y: rect.y - margin, + width: rect.width + 2 * margin, + height: rect.height + 2 * margin, + } +} + +function setMinimumSizeKeepingCenter(rect: Rect, minimumSize: { width: number; height: number }) { + const center = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 } + const width = Math.max(rect.width, minimumSize.width) + const height = Math.max(rect.height, minimumSize.height) + return { + x: center.x - width / 2, + y: center.y - height / 2, + width, + height, + } +} + +function clampIntoKeepingSize(rect: Rect, bounds: Rect) { + const minLeft = bounds.x + const minTop = bounds.y + const maxLeft = bounds.x + bounds.width - rect.width + const maxTop = bounds.y + bounds.height - rect.height + const x = _.clamp(rect.x, minLeft, maxLeft) + const y = _.clamp(rect.y, minTop, maxTop) + return { x, y, width: rect.width, height: rect.height } +} + +export function boardContentArea(b: Board) { + // Default / minimum size for initial view + const width = b.width / 10 + const height = b.height / 10 + + const items = Object.values(b.items) + + if (!items.length) { + console.log("No items in board, centering view") + return { x: b.width / 2 - width / 2, y: b.height / 2 - height / 2, width, height } + } + + let itemsArea = itemToRect(items[0]) + items.forEach((item) => { + itemsArea = combineRects(itemsArea, itemToRect(item)) + }) + + // Now we have the area of all items, let's add some margin + itemsArea = addMargin(itemsArea, width * 0.2) + + // Grow to at least the default size keeping center point + itemsArea = setMinimumSizeKeepingCenter(itemsArea, { width, height }) + + // Clamp to board limits + itemsArea = clampIntoKeepingSize(itemsArea, { x: 0, y: 0, width: b.width, height: b.height }) + return itemsArea +}