Skip to content

Commit 633954d

Browse files
committed
fix bug in readonly view (via timetravel) of countdown timer.
1 parent 95d8ae6 commit 633954d

File tree

5 files changed

+71
-84
lines changed

5 files changed

+71
-84
lines changed

src/packages/frontend/editors/stopwatch/stopwatch.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface StopwatchProps {
4444
total?: number; // total time accumulated before entering current state
4545
style?: CSSProperties;
4646
timeStyle?: CSSProperties;
47+
readOnly?: boolean; // can't change, and won't display something when timer goes off!
4748
}
4849

4950
export default function Stopwatch(props: StopwatchProps) {
@@ -223,7 +224,7 @@ export default function Stopwatch(props: StopwatchProps) {
223224
: undefined),
224225
}}
225226
/>
226-
{props.countdown && amount == 0 && (
227+
{props.countdown && amount == 0 && !props.readOnly && (
227228
<Modal
228229
title={
229230
<>
@@ -378,7 +379,7 @@ export default function Stopwatch(props: StopwatchProps) {
378379
{renderLabel()}
379380
</Col>
380381
</Row>
381-
{!props.noButtons && (
382+
{!props.noButtons && !props.readOnly && (
382383
<Row style={{ marginTop: "5px" }}>
383384
<Col md={24}>{renderButtons()}</Col>
384385
</Row>
@@ -391,7 +392,7 @@ export default function Stopwatch(props: StopwatchProps) {
391392
return (
392393
<div style={{ display: "flex" }}>
393394
{renderTime()}
394-
{!props.noButtons && (
395+
{!props.noButtons && !props.readOnly && (
395396
<div style={{ marginTop: "3px", marginLeft: "5px" }}>
396397
{renderButtons()}
397398
</div>

src/packages/frontend/frame-editors/whiteboard-editor/actions.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CodeEditorState,
1515
} from "../code-editor/actions";
1616
import { Tool } from "./tools/spec";
17-
import { Element, Elements, Point } from "./types";
17+
import { Element, Elements, Point, Rect } from "./types";
1818
import { uuid } from "@cocalc/util/misc";
1919
import {
2020
DEFAULT_WIDTH,
@@ -269,17 +269,14 @@ export class Actions extends BaseActions<State> {
269269
this.set_frame_tree({ id, hideMap: !node.get("hideMap") });
270270
}
271271

272-
// TODO: serious concern -- this visibleWindow stuff is getting persisted
273-
// to localStorage, but doesn't need to be, which is a waste.
274-
saveVisibleWindow(
275-
id: string,
276-
visibleWindow: { xMin: number; yMin: number; xMax: number; yMax: number }
277-
): void {
278-
this.set_frame_tree({ id, visibleWindow });
272+
// The viewport = exactly the part of the canvas that is VISIBLE to the user
273+
// in data coordinates, of course, like everything here.
274+
saveViewport(id: string, viewport: Rect): void {
275+
this.set_frame_tree({ id, viewport });
279276
}
280277

281-
setVisibleWindowCenter(id: string, center: { x: number; y: number }) {
282-
this.set_frame_tree({ id, visibleWindowCenter: center });
278+
setViewportCenter(id: string, center: { x: number; y: number }) {
279+
this.set_frame_tree({ id, viewportCenter: center });
283280
}
284281

285282
saveCenter(id: string, center: { x: number; y: number }) {

src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx

+56-70
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import { useFrameContext } from "./hooks";
6969
import usePinchToZoom from "@cocalc/frontend/frame-editors/frame-tree/pinch-to-zoom";
7070
import Grid from "./elements/grid";
7171
import {
72+
centerOfRect,
7273
compressPath,
7374
fontSizeToZoom,
7475
ZOOM100,
@@ -160,35 +161,27 @@ export default function Canvas({
160161

161162
// Whenever the scale changes, make sure the current center of the screen
162163
// is preserved.
163-
const [lastScale, setLastScale] = useState<number>(canvasScale);
164+
const lastViewport = useRef<Rect | undefined>(undefined);
164165
useEffect(() => {
165166
if (isNavigator) return;
166-
if (canvasScale == lastScale) return;
167-
const ctr = getCenterPositionWindow();
168-
if (ctr == null) return;
169-
const { x, y } = ctr;
170-
const new_x = (lastScale / canvasScale) * x;
171-
const new_y = (lastScale / canvasScale) * y;
172-
const delta_x = x - new_x;
173-
const delta_y = y - new_y;
174-
const c = canvasRef.current;
175-
if (c == null) return;
176-
c.scrollLeft += delta_x;
177-
c.scrollTop += delta_y;
178-
setLastScale(canvasScale);
179-
}, [canvasScale]);
180-
181-
useEffect(() => {
182-
const { current } = canvasRef;
183-
if (current != null) {
184-
const scaledMargin = (margin ?? 0) * canvasScale;
185-
current.scrollTop = scaledMargin;
186-
current.scrollLeft = scaledMargin;
167+
const viewport = getViewportData();
168+
if (lastViewport.current != null && viewport != null) {
169+
const last = centerOfRect(lastViewport.current);
170+
const cur = centerOfRect(viewport);
171+
const tx = last.x - cur.x;
172+
const ty = last.y - cur.y;
173+
const c = canvasRef.current;
174+
if (c == null) return;
175+
c.scrollLeft += tx * canvasScale;
176+
c.scrollTop += ty * canvasScale;
187177
}
188-
}, []);
178+
lastViewport.current = viewport;
179+
}, [canvasScale, margin]);
189180

181+
// maintain state about the viewport so it can be displayed
182+
// in the navmap, and also restored later.
190183
useEffect(() => {
191-
updateVisibleWindow();
184+
updateViewport();
192185
}, [font_size, scale, transforms.width, transforms.height]);
193186

194187
const frame = useFrameContext();
@@ -197,10 +190,10 @@ export default function Canvas({
197190
const restoring = useRef<boolean>(true);
198191
useEffect(() => {
199192
if (isNavigator || restoring.current) return;
200-
const center = frame.desc.get("visibleWindowCenter")?.toJS();
193+
const center = frame.desc.get("viewportCenter")?.toJS();
201194
if (center == null) return;
202195
setCenterPositionData(center);
203-
}, [frame.desc.get("visibleWindowCenter")]);
196+
}, [frame.desc.get("viewportCenter")]);
204197

205198
useEffect(() => {
206199
if (isNavigator) return;
@@ -272,24 +265,31 @@ export default function Canvas({
272265
c.scrollTop = scrollTopGoal;
273266
}
274267

275-
// when fitToScreen is true, compute data then set font_size to get zoom (plus offset) to
276-
// everything is visible properly on the page; also set fitToScreen back to false in
268+
// when fitToScreen is true, compute data then set font_size to
269+
// get zoom (plus offset) to everything is visible properly
270+
// on the page; also set fitToScreen back to false in
277271
// frame tree data
278272
useEffect(() => {
279-
if (frame.desc.get("fitToScreen") && !isNavigator) {
280-
try {
281-
const viewport = getViewportData();
282-
if (viewport == null) return;
283-
const rect = rectSpan(elements);
284-
const { scale } = fitRectToRect(rect, viewport);
285-
frame.actions.set_font_size(frame.id, (font_size ?? ZOOM100) * scale);
286-
setCenterPositionData({
287-
x: rect.x + rect.w / 2,
288-
y: rect.y + rect.h / 2,
289-
});
290-
} finally {
291-
frame.actions.fitToScreen(frame.id, false);
273+
if (isNavigator || !frame.desc.get("fitToScreen")) return;
274+
try {
275+
const viewport = getViewportData();
276+
if (viewport == null) return;
277+
const rect = rectSpan(elements);
278+
setCenterPositionData({
279+
x: rect.x + rect.w / 2,
280+
y: rect.y + rect.h / 2,
281+
});
282+
const { scale } = fitRectToRect(rect, viewport);
283+
if (scale != 1) {
284+
// ensure lastViewport is up to date before zooming.
285+
lastViewport.current = getViewportData();
286+
frame.actions.set_font_size(
287+
frame.id,
288+
Math.round((font_size ?? ZOOM100) * scale)
289+
);
292290
}
291+
} finally {
292+
frame.actions.fitToScreen(frame.id, false);
293293
}
294294
}, [frame.desc.get("fitToScreen")]);
295295

@@ -326,6 +326,7 @@ export default function Canvas({
326326
element={element}
327327
focused={focused}
328328
canvasScale={canvasScale}
329+
readOnly={readOnly || isNavigator}
329330
/>
330331
);
331332
if (!isNavRectangle && (element.style || selected || isNavigator)) {
@@ -450,9 +451,8 @@ export default function Canvas({
450451

451452
if (isNavigator) {
452453
// The navigator rectangle
453-
const visible = frame.desc.get("visibleWindow")?.toJS();
454+
const visible = frame.desc.get("viewport")?.toJS();
454455
if (visible) {
455-
const { xMin, yMin, xMax, yMax } = visible;
456456
v.unshift(
457457
<Draggable
458458
key="nav"
@@ -467,14 +467,11 @@ export default function Canvas({
467467
onStop={(data: MouseEvent) => {
468468
if (!navDrag.current) return;
469469
const { x0, y0 } = navDrag.current;
470-
const visible = frame.desc.get("visibleWindow")?.toJS();
470+
const visible = frame.desc.get("viewport")?.toJS();
471471
if (visible == null) return;
472-
const ctr = {
473-
x: (visible.xMax + visible.xMin) / 2,
474-
y: (visible.yMax + visible.yMin) / 2,
475-
};
472+
const ctr = centerOfRect(visible);
476473
const { x, y } = data;
477-
frame.actions.setVisibleWindowCenter(frame.id, {
474+
frame.actions.setViewportCenter(frame.id, {
478475
x: ctr.x + (x - x0) / canvasScale,
479476
y: ctr.y + (y - y0) / canvasScale,
480477
});
@@ -484,10 +481,7 @@ export default function Canvas({
484481
{processElement(
485482
{
486483
id: "nav-frame",
487-
x: xMin,
488-
y: yMin,
489-
w: xMax - xMin,
490-
h: yMax - yMin,
484+
...visible,
491485
z: MAX_ELEMENTS + 1,
492486
type: "frame",
493487
data: { color: "#888", radius: 0.5 },
@@ -615,25 +609,17 @@ export default function Canvas({
615609
}
616610
}
617611

618-
const updateVisibleWindow = isNavigator
612+
const updateViewport = isNavigator
619613
? () => {}
620614
: useMemo(() => {
621615
return throttle(() => {
622-
const elt = canvasRef.current;
623-
if (!elt) return;
624-
// upper left corner of visible window
625-
const { scrollLeft, scrollTop } = elt;
626-
// width and height of visible window
627-
const { width, height } = elt.getBoundingClientRect();
628-
const { x: xMin, y: yMin } = transforms.windowToDataNoScale(
629-
scrollLeft / canvasScale,
630-
scrollTop / canvasScale
631-
);
632-
const xMax = xMin + width / canvasScale;
633-
const yMax = yMin + height / canvasScale;
634-
frame.actions.saveVisibleWindow(frame.id, { xMin, yMin, xMax, yMax });
616+
const viewport = getViewportData();
617+
if (viewport) {
618+
frame.actions.saveViewport(frame.id, viewport);
619+
lastViewport.current = viewport;
620+
}
635621
}, 50);
636-
}, [transforms, canvasScale]);
622+
}, [transforms, canvasScale, margin]);
637623

638624
const onMouseDown = (e) => {
639625
if (selectedTool == "select" || selectedTool == "frame") {
@@ -816,15 +802,15 @@ export default function Canvas({
816802
navDrag.current = null;
817803
return;
818804
}
819-
frame.actions.setVisibleWindowCenter(frame.id, evtToData(e));
805+
frame.actions.setViewportCenter(frame.id, evtToData(e));
820806
return;
821807
}
822808
if (!readOnly) {
823809
handleClick(e);
824810
}
825811
}}
826812
onScroll={() => {
827-
updateVisibleWindow();
813+
updateViewport();
828814
saveCenterPosition();
829815
}}
830816
onMouseDown={onMouseDown}

src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface Props {
1919
element: Element;
2020
focused: boolean;
2121
canvasScale: number;
22+
readOnly?: boolean;
2223
}
2324

2425
export default function Render(props: Props) {

src/packages/frontend/frame-editors/whiteboard-editor/elements/timer.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ import type { Element } from "../types";
1717
interface Props {
1818
element: Element;
1919
focused?: boolean;
20+
readOnly?: boolean;
2021
}
2122

2223
function getTime(): number {
2324
return webapp_client.server_time() - 0;
2425
}
2526

26-
export default function Stopwatch({ element, focused }: Props) {
27+
export default function Stopwatch({ element, focused, readOnly }: Props) {
2728
const { actions } = useFrameContext();
2829
const eltRef = useRef<Element>(element);
2930
eltRef.current = element;
@@ -37,6 +38,7 @@ export default function Stopwatch({ element, focused }: Props) {
3738
return (
3839
<>
3940
<StopwatchEditor
41+
readOnly={readOnly}
4042
noLabel
4143
noDelete
4244
compact

0 commit comments

Comments
 (0)