From 871ab651e8ec3752caec336259042bbbf8e5e3a1 Mon Sep 17 00:00:00 2001 From: William Stein Date: Sun, 13 Feb 2022 16:03:28 -0800 Subject: [PATCH] fix bug in readonly view (via timetravel) of countdown timer. --- .../frontend/editors/stopwatch/stopwatch.tsx | 7 +- .../whiteboard-editor/actions.ts | 17 +-- .../whiteboard-editor/canvas.tsx | 126 ++++++++---------- .../whiteboard-editor/elements/render.tsx | 1 + .../whiteboard-editor/elements/timer.tsx | 4 +- 5 files changed, 71 insertions(+), 84 deletions(-) diff --git a/src/packages/frontend/editors/stopwatch/stopwatch.tsx b/src/packages/frontend/editors/stopwatch/stopwatch.tsx index e4932226948..126422f0f44 100644 --- a/src/packages/frontend/editors/stopwatch/stopwatch.tsx +++ b/src/packages/frontend/editors/stopwatch/stopwatch.tsx @@ -44,6 +44,7 @@ interface StopwatchProps { total?: number; // total time accumulated before entering current state style?: CSSProperties; timeStyle?: CSSProperties; + readOnly?: boolean; // can't change, and won't display something when timer goes off! } export default function Stopwatch(props: StopwatchProps) { @@ -223,7 +224,7 @@ export default function Stopwatch(props: StopwatchProps) { : undefined), }} /> - {props.countdown && amount == 0 && ( + {props.countdown && amount == 0 && !props.readOnly && ( @@ -378,7 +379,7 @@ export default function Stopwatch(props: StopwatchProps) { {renderLabel()} - {!props.noButtons && ( + {!props.noButtons && !props.readOnly && ( {renderButtons()} @@ -391,7 +392,7 @@ export default function Stopwatch(props: StopwatchProps) { return (
{renderTime()} - {!props.noButtons && ( + {!props.noButtons && !props.readOnly && (
{renderButtons()}
diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/actions.ts b/src/packages/frontend/frame-editors/whiteboard-editor/actions.ts index 3c5845fbfd3..2387309fd82 100644 --- a/src/packages/frontend/frame-editors/whiteboard-editor/actions.ts +++ b/src/packages/frontend/frame-editors/whiteboard-editor/actions.ts @@ -14,7 +14,7 @@ import { CodeEditorState, } from "../code-editor/actions"; import { Tool } from "./tools/spec"; -import { Element, Elements, Point } from "./types"; +import { Element, Elements, Point, Rect } from "./types"; import { uuid } from "@cocalc/util/misc"; import { DEFAULT_WIDTH, @@ -269,17 +269,14 @@ export class Actions extends BaseActions { this.set_frame_tree({ id, hideMap: !node.get("hideMap") }); } - // TODO: serious concern -- this visibleWindow stuff is getting persisted - // to localStorage, but doesn't need to be, which is a waste. - saveVisibleWindow( - id: string, - visibleWindow: { xMin: number; yMin: number; xMax: number; yMax: number } - ): void { - this.set_frame_tree({ id, visibleWindow }); + // The viewport = exactly the part of the canvas that is VISIBLE to the user + // in data coordinates, of course, like everything here. + saveViewport(id: string, viewport: Rect): void { + this.set_frame_tree({ id, viewport }); } - setVisibleWindowCenter(id: string, center: { x: number; y: number }) { - this.set_frame_tree({ id, visibleWindowCenter: center }); + setViewportCenter(id: string, center: { x: number; y: number }) { + this.set_frame_tree({ id, viewportCenter: center }); } saveCenter(id: string, center: { x: number; y: number }) { diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx index 6a654c1417a..d8f75894a62 100644 --- a/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx +++ b/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx @@ -69,6 +69,7 @@ import { useFrameContext } from "./hooks"; import usePinchToZoom from "@cocalc/frontend/frame-editors/frame-tree/pinch-to-zoom"; import Grid from "./elements/grid"; import { + centerOfRect, compressPath, fontSizeToZoom, ZOOM100, @@ -160,35 +161,27 @@ export default function Canvas({ // Whenever the scale changes, make sure the current center of the screen // is preserved. - const [lastScale, setLastScale] = useState(canvasScale); + const lastViewport = useRef(undefined); useEffect(() => { if (isNavigator) return; - if (canvasScale == lastScale) return; - const ctr = getCenterPositionWindow(); - if (ctr == null) return; - const { x, y } = ctr; - const new_x = (lastScale / canvasScale) * x; - const new_y = (lastScale / canvasScale) * y; - const delta_x = x - new_x; - const delta_y = y - new_y; - const c = canvasRef.current; - if (c == null) return; - c.scrollLeft += delta_x; - c.scrollTop += delta_y; - setLastScale(canvasScale); - }, [canvasScale]); - - useEffect(() => { - const { current } = canvasRef; - if (current != null) { - const scaledMargin = (margin ?? 0) * canvasScale; - current.scrollTop = scaledMargin; - current.scrollLeft = scaledMargin; + const viewport = getViewportData(); + if (lastViewport.current != null && viewport != null) { + const last = centerOfRect(lastViewport.current); + const cur = centerOfRect(viewport); + const tx = last.x - cur.x; + const ty = last.y - cur.y; + const c = canvasRef.current; + if (c == null) return; + c.scrollLeft += tx * canvasScale; + c.scrollTop += ty * canvasScale; } - }, []); + lastViewport.current = viewport; + }, [canvasScale, margin]); + // maintain state about the viewport so it can be displayed + // in the navmap, and also restored later. useEffect(() => { - updateVisibleWindow(); + updateViewport(); }, [font_size, scale, transforms.width, transforms.height]); const frame = useFrameContext(); @@ -197,10 +190,10 @@ export default function Canvas({ const restoring = useRef(true); useEffect(() => { if (isNavigator || restoring.current) return; - const center = frame.desc.get("visibleWindowCenter")?.toJS(); + const center = frame.desc.get("viewportCenter")?.toJS(); if (center == null) return; setCenterPositionData(center); - }, [frame.desc.get("visibleWindowCenter")]); + }, [frame.desc.get("viewportCenter")]); useEffect(() => { if (isNavigator) return; @@ -272,24 +265,31 @@ export default function Canvas({ c.scrollTop = scrollTopGoal; } - // when fitToScreen is true, compute data then set font_size to get zoom (plus offset) to - // everything is visible properly on the page; also set fitToScreen back to false in + // when fitToScreen is true, compute data then set font_size to + // get zoom (plus offset) to everything is visible properly + // on the page; also set fitToScreen back to false in // frame tree data useEffect(() => { - if (frame.desc.get("fitToScreen") && !isNavigator) { - try { - const viewport = getViewportData(); - if (viewport == null) return; - const rect = rectSpan(elements); - const { scale } = fitRectToRect(rect, viewport); - frame.actions.set_font_size(frame.id, (font_size ?? ZOOM100) * scale); - setCenterPositionData({ - x: rect.x + rect.w / 2, - y: rect.y + rect.h / 2, - }); - } finally { - frame.actions.fitToScreen(frame.id, false); + if (isNavigator || !frame.desc.get("fitToScreen")) return; + try { + const viewport = getViewportData(); + if (viewport == null) return; + const rect = rectSpan(elements); + setCenterPositionData({ + x: rect.x + rect.w / 2, + y: rect.y + rect.h / 2, + }); + const { scale } = fitRectToRect(rect, viewport); + if (scale != 1) { + // ensure lastViewport is up to date before zooming. + lastViewport.current = getViewportData(); + frame.actions.set_font_size( + frame.id, + Math.round((font_size ?? ZOOM100) * scale) + ); } + } finally { + frame.actions.fitToScreen(frame.id, false); } }, [frame.desc.get("fitToScreen")]); @@ -326,6 +326,7 @@ export default function Canvas({ element={element} focused={focused} canvasScale={canvasScale} + readOnly={readOnly} /> ); if (!isNavRectangle && (element.style || selected || isNavigator)) { @@ -450,9 +451,8 @@ export default function Canvas({ if (isNavigator) { // The navigator rectangle - const visible = frame.desc.get("visibleWindow")?.toJS(); + const visible = frame.desc.get("viewport")?.toJS(); if (visible) { - const { xMin, yMin, xMax, yMax } = visible; v.unshift( { if (!navDrag.current) return; const { x0, y0 } = navDrag.current; - const visible = frame.desc.get("visibleWindow")?.toJS(); + const visible = frame.desc.get("viewport")?.toJS(); if (visible == null) return; - const ctr = { - x: (visible.xMax + visible.xMin) / 2, - y: (visible.yMax + visible.yMin) / 2, - }; + const ctr = centerOfRect(visible); const { x, y } = data; - frame.actions.setVisibleWindowCenter(frame.id, { + frame.actions.setViewportCenter(frame.id, { x: ctr.x + (x - x0) / canvasScale, y: ctr.y + (y - y0) / canvasScale, }); @@ -484,10 +481,7 @@ export default function Canvas({ {processElement( { id: "nav-frame", - x: xMin, - y: yMin, - w: xMax - xMin, - h: yMax - yMin, + ...visible, z: MAX_ELEMENTS + 1, type: "frame", data: { color: "#888", radius: 0.5 }, @@ -615,25 +609,17 @@ export default function Canvas({ } } - const updateVisibleWindow = isNavigator + const updateViewport = isNavigator ? () => {} : useMemo(() => { return throttle(() => { - const elt = canvasRef.current; - if (!elt) return; - // upper left corner of visible window - const { scrollLeft, scrollTop } = elt; - // width and height of visible window - const { width, height } = elt.getBoundingClientRect(); - const { x: xMin, y: yMin } = transforms.windowToDataNoScale( - scrollLeft / canvasScale, - scrollTop / canvasScale - ); - const xMax = xMin + width / canvasScale; - const yMax = yMin + height / canvasScale; - frame.actions.saveVisibleWindow(frame.id, { xMin, yMin, xMax, yMax }); + const viewport = getViewportData(); + if (viewport) { + frame.actions.saveViewport(frame.id, viewport); + lastViewport.current = viewport; + } }, 50); - }, [transforms, canvasScale]); + }, [transforms, canvasScale, margin]); const onMouseDown = (e) => { if (selectedTool == "select" || selectedTool == "frame") { @@ -816,7 +802,7 @@ export default function Canvas({ navDrag.current = null; return; } - frame.actions.setVisibleWindowCenter(frame.id, evtToData(e)); + frame.actions.setViewportCenter(frame.id, evtToData(e)); return; } if (!readOnly) { @@ -824,7 +810,7 @@ export default function Canvas({ } }} onScroll={() => { - updateVisibleWindow(); + updateViewport(); saveCenterPosition(); }} onMouseDown={onMouseDown} diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx index 47d3aea1958..dd118e00197 100644 --- a/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx +++ b/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx @@ -19,6 +19,7 @@ interface Props { element: Element; focused: boolean; canvasScale: number; + readOnly?: boolean; } export default function Render(props: Props) { diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/elements/timer.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/elements/timer.tsx index 22496861be9..1af1a0dbcce 100644 --- a/src/packages/frontend/frame-editors/whiteboard-editor/elements/timer.tsx +++ b/src/packages/frontend/frame-editors/whiteboard-editor/elements/timer.tsx @@ -17,13 +17,14 @@ import type { Element } from "../types"; interface Props { element: Element; focused?: boolean; + readOnly?: boolean; } function getTime(): number { return webapp_client.server_time() - 0; } -export default function Stopwatch({ element, focused }: Props) { +export default function Stopwatch({ element, focused, readOnly }: Props) { const { actions } = useFrameContext(); const eltRef = useRef(element); eltRef.current = element; @@ -37,6 +38,7 @@ export default function Stopwatch({ element, focused }: Props) { return ( <>