store position in location hash, allowing resharing
This commit is contained in:
parent
d29419bcf7
commit
9ea1e903db
|
@ -19,17 +19,65 @@ export const CanvasWrapper = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const parseHashParams = (canvas: Canvas) => {
|
||||
// maybe move this to a utility inside routes.ts
|
||||
|
||||
let { hash } = new URL(window.location.href);
|
||||
if (hash.indexOf("#") === 0) {
|
||||
hash = hash.slice(1);
|
||||
}
|
||||
let params = new URLSearchParams(hash);
|
||||
|
||||
let position: {
|
||||
x?: number;
|
||||
y?: number;
|
||||
zoom?: number;
|
||||
} = {};
|
||||
|
||||
if (params.has("x") && !isNaN(parseInt(params.get("x")!)))
|
||||
position.x = parseInt(params.get("x")!);
|
||||
if (params.has("y") && !isNaN(parseInt(params.get("y")!)))
|
||||
position.y = parseInt(params.get("y")!);
|
||||
if (params.has("zoom") && !isNaN(parseInt(params.get("zoom")!)))
|
||||
position.zoom = parseInt(params.get("zoom")!);
|
||||
|
||||
if (
|
||||
typeof position.x === "number" &&
|
||||
typeof position.y === "number" &&
|
||||
typeof position.zoom === "number"
|
||||
) {
|
||||
const { transformX, transformY } = canvas.canvasToPanZoomTransform(
|
||||
position.x,
|
||||
position.y
|
||||
);
|
||||
|
||||
return {
|
||||
x: transformX,
|
||||
y: transformY,
|
||||
zoom: position.zoom,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const CanvasInner = () => {
|
||||
const canvasRef = createRef<HTMLCanvasElement>();
|
||||
const { config, setCanvasPosition, setCursorPosition } = useAppContext();
|
||||
const PanZoom = useContext(RendererContext);
|
||||
// const { centerView } = useControls();
|
||||
|
||||
useEffect(() => {
|
||||
if (!config.canvas || !canvasRef.current) return;
|
||||
const canvas = canvasRef.current!;
|
||||
const canvasInstance = new Canvas(config, canvas, PanZoom);
|
||||
// centerView();
|
||||
|
||||
{
|
||||
// TODO: handle hash changes and move viewport
|
||||
// NOTE: this will need to be cancelled if handleViewportMove was executed recently
|
||||
|
||||
const position = parseHashParams(canvasInstance);
|
||||
if (position) {
|
||||
PanZoom.setPosition(position, { suppressEmit: true });
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewportMove = throttle((state: ViewportMoveEvent) => {
|
||||
const pos = canvasInstance.panZoomTransformToCanvas();
|
||||
|
@ -45,7 +93,7 @@ const CanvasInner = () => {
|
|||
window.location.replace(Routes.canvas(canvasPosition));
|
||||
}, 1000);
|
||||
|
||||
const handleCursorPos = (pos: IPosition) => {
|
||||
const handleCursorPos = throttle((pos: IPosition) => {
|
||||
if (
|
||||
pos.x < 0 ||
|
||||
pos.y < 0 ||
|
||||
|
@ -54,9 +102,10 @@ const CanvasInner = () => {
|
|||
) {
|
||||
setCursorPosition();
|
||||
} else {
|
||||
setCursorPosition(pos);
|
||||
// fixes not passing the current value
|
||||
setCursorPosition({ ...pos });
|
||||
}
|
||||
};
|
||||
}, 1);
|
||||
|
||||
PanZoom.addListener("viewportMove", handleViewportMove);
|
||||
canvasInstance.on("cursorPos", handleCursorPos);
|
||||
|
@ -66,7 +115,9 @@ const CanvasInner = () => {
|
|||
PanZoom.removeListener("viewportMove", handleViewportMove);
|
||||
canvasInstance.off("cursorPos", handleCursorPos);
|
||||
};
|
||||
}, [PanZoom, canvasRef, config, setCanvasPosition]);
|
||||
|
||||
// do not include canvasRef, it causes infinite re-renders
|
||||
}, [PanZoom, config, setCanvasPosition, setCursorPosition]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
|
|
|
@ -158,6 +158,22 @@ export class Canvas extends EventEmitter<CanvasEvents> {
|
|||
});
|
||||
}
|
||||
|
||||
canvasToPanZoomTransform(x: number, y: number) {
|
||||
let transformX = 0;
|
||||
let transformY = 0;
|
||||
|
||||
if (this.PanZoom.flags.useZoom) {
|
||||
// CSS Zoom does not alter this (obviously)
|
||||
transformX = this.canvas.width / 2 - x;
|
||||
transformY = this.canvas.height / 2 - y;
|
||||
} else {
|
||||
transformX = this.canvas.width / 2 - x;
|
||||
transformY = this.canvas.height / 2 - y;
|
||||
}
|
||||
|
||||
return { transformX, transformY };
|
||||
}
|
||||
|
||||
panZoomTransformToCanvas() {
|
||||
const { x, y, scale: zoom } = this.PanZoom.transform;
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
|
|
|
@ -82,6 +82,8 @@ interface ISetup {
|
|||
* [minimum scale, maximum scale]
|
||||
*/
|
||||
scale: [number, number];
|
||||
|
||||
initialTransform?: TransformState;
|
||||
}
|
||||
|
||||
// TODO: move these event interfaces out
|
||||
|
@ -106,9 +108,12 @@ interface PanZoomEvents {
|
|||
click: (e: ClickEvent) => void;
|
||||
hover: (e: HoverEvent) => void;
|
||||
viewportMove: (e: ViewportMoveEvent) => void;
|
||||
initialize: () => void;
|
||||
}
|
||||
|
||||
export class PanZoom extends EventEmitter<PanZoomEvents> {
|
||||
private initialized = false;
|
||||
|
||||
public $wrapper: HTMLDivElement = null as any;
|
||||
public $zoom: HTMLDivElement = null as any;
|
||||
public $move: HTMLDivElement = null as any;
|
||||
|
@ -165,6 +170,53 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
|
|||
this.detectFlags();
|
||||
this.registerMouseEvents();
|
||||
this.registerTouchEvents();
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
if (this.setup.initialTransform) {
|
||||
// use initial transform if it is set
|
||||
// initialTransform is set from #setPosition() when PanZoom is not initalized
|
||||
|
||||
let { x, y, scale } = this.setup.initialTransform;
|
||||
|
||||
this.transform.x = x;
|
||||
this.transform.y = y;
|
||||
this.transform.scale = scale;
|
||||
this.update({ suppressEmit: true });
|
||||
}
|
||||
|
||||
this.emit("initialize");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transform data
|
||||
*
|
||||
* @param position
|
||||
* @param position.x Transform X
|
||||
* @param position.y Transform Y
|
||||
* @param position.zoom Zoom scale
|
||||
* @param flags
|
||||
* @param flags.suppressEmit If true, don't emit a viewport change
|
||||
* @returns
|
||||
*/
|
||||
setPosition(
|
||||
{ x, y, zoom }: { x: number; y: number; zoom: number },
|
||||
{ suppressEmit } = { suppressEmit: false }
|
||||
) {
|
||||
if (!this.initialized) {
|
||||
// elements are not yet available, store them to be used upon initialization
|
||||
this.setup.initialTransform = {
|
||||
x,
|
||||
y,
|
||||
scale: zoom,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
this.transform.x = x;
|
||||
this.transform.y = y;
|
||||
this.transform.scale = zoom;
|
||||
this.update({ suppressEmit });
|
||||
}
|
||||
|
||||
detectFlags() {
|
||||
|
@ -453,12 +505,28 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
|
|||
);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.emit("viewportMove", {
|
||||
scale: this.transform.scale,
|
||||
x: this.transform.x,
|
||||
y: this.transform.y,
|
||||
});
|
||||
/**
|
||||
* Update viewport scale and position
|
||||
*
|
||||
* @param flags
|
||||
* @param flags.suppressEmit Do not emit viewportMove
|
||||
*/
|
||||
update(
|
||||
{
|
||||
suppressEmit,
|
||||
}: {
|
||||
suppressEmit: boolean;
|
||||
} = {
|
||||
suppressEmit: false,
|
||||
}
|
||||
) {
|
||||
if (!suppressEmit) {
|
||||
this.emit("viewportMove", {
|
||||
scale: this.transform.scale,
|
||||
x: this.transform.x,
|
||||
y: this.transform.y,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.flags.useZoom) {
|
||||
this.$zoom.style.setProperty("zoom", this.transform.scale * 100 + "%");
|
||||
|
|
Loading…
Reference in New Issue