Canvas: Add "infinite" pan / zoom functionality (#84968)
* attempt at solving issue and resetting bounds to match panel content * cleanup failed experiments * add toggle for infinite pan - dont love naming but open to suggestions; clean up scene transform function * add ability to "infinite" zoom (i.e. render more canvas via zooming out) * slight refactor * revert refactor as it broke things
This commit is contained in:
+5
@@ -111,6 +111,10 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
|
||||
};
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* Enable infinite pan
|
||||
*/
|
||||
infinitePan: boolean;
|
||||
/**
|
||||
* Enable inline editing
|
||||
*/
|
||||
@@ -144,6 +148,7 @@ export interface Options {
|
||||
}
|
||||
|
||||
export const defaultOptions: Partial<Options> = {
|
||||
infinitePan: true,
|
||||
inlineEditing: true,
|
||||
panZoom: true,
|
||||
showAdvancedTypes: true,
|
||||
|
||||
@@ -14,6 +14,15 @@ export const SceneTransformWrapper = ({ scene, children: sceneDiv }: SceneTransf
|
||||
const onZoom = (zoomPanPinchRef: ReactZoomPanPinchRef) => {
|
||||
const scale = zoomPanPinchRef.state.scale;
|
||||
scene.scale = scale;
|
||||
|
||||
if (scene.shouldInfinitePan) {
|
||||
const isScaleZoomedOut = scale < 1;
|
||||
|
||||
if (isScaleZoomedOut) {
|
||||
scene.updateSize(scene.width / scale, scene.height / scale);
|
||||
scene.panel.forceUpdate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onZoomStop = (zoomPanPinchRef: ReactZoomPanPinchRef) => {
|
||||
@@ -46,6 +55,25 @@ export const SceneTransformWrapper = ({ scene, children: sceneDiv }: SceneTransf
|
||||
}
|
||||
};
|
||||
|
||||
const onPanning = (_: ReactZoomPanPinchRef, event: MouseEvent | TouchEvent) => {
|
||||
if (scene.shouldInfinitePan && event instanceof MouseEvent) {
|
||||
// Get deltaX and deltaY from pan event and add it to current canvas dimensions
|
||||
let deltaX = event.movementX;
|
||||
let deltaY = event.movementY;
|
||||
if (deltaX > 0) {
|
||||
deltaX = 0;
|
||||
}
|
||||
if (deltaY > 0) {
|
||||
deltaY = 0;
|
||||
}
|
||||
|
||||
// TODO: Consider bounding to the scene elements instead of allowing "infinite" panning
|
||||
// TODO: Consider making scene grow in all directions vs just down to the right / bottom
|
||||
scene.updateSize(scene.width - deltaX, scene.height - deltaY);
|
||||
scene.panel.forceUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
const onSceneContainerMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
// If pan and zoom is disabled or context menu is visible, don't pan
|
||||
if ((!scene.shouldPanZoom || scene.contextMenuVisible) && (e.button === 1 || (e.button === 2 && e.ctrlKey))) {
|
||||
@@ -60,6 +88,9 @@ export const SceneTransformWrapper = ({ scene, children: sceneDiv }: SceneTransf
|
||||
}
|
||||
};
|
||||
|
||||
// Set panel content overflow to hidden to prevent canvas content from overflowing
|
||||
scene.div?.parentElement?.parentElement?.parentElement?.parentElement?.setAttribute('style', `overflow: hidden`);
|
||||
|
||||
return (
|
||||
<TransformWrapper
|
||||
doubleClick={{ mode: 'reset' }}
|
||||
@@ -67,9 +98,11 @@ export const SceneTransformWrapper = ({ scene, children: sceneDiv }: SceneTransf
|
||||
onZoom={onZoom}
|
||||
onZoomStop={onZoomStop}
|
||||
onTransformed={onTransformed}
|
||||
limitToBounds={true}
|
||||
disabled={!config.featureToggles.canvasPanelPanZoom || !scene.shouldPanZoom}
|
||||
panning={{ allowLeftClickPan: false }}
|
||||
limitToBounds={!scene.shouldInfinitePan}
|
||||
minScale={scene.shouldInfinitePan ? 0.1 : undefined}
|
||||
onPanning={onPanning}
|
||||
>
|
||||
<TransformComponent>
|
||||
{/* The <div> element has child elements that allow for mouse events, so we need to disable the linter rule */}
|
||||
|
||||
@@ -75,6 +75,7 @@ export class Scene {
|
||||
isEditingEnabled?: boolean;
|
||||
shouldShowAdvancedTypes?: boolean;
|
||||
shouldPanZoom?: boolean;
|
||||
shouldInfinitePan?: boolean;
|
||||
skipNextSelectionBroadcast = false;
|
||||
ignoreDataUpdate = false;
|
||||
panel: CanvasPanel;
|
||||
@@ -112,10 +113,11 @@ export class Scene {
|
||||
enableEditing: boolean,
|
||||
showAdvancedTypes: boolean,
|
||||
panZoom: boolean,
|
||||
infinitePan: boolean,
|
||||
public onSave: (cfg: CanvasFrameOptions) => void,
|
||||
panel: CanvasPanel
|
||||
) {
|
||||
this.root = this.load(cfg, enableEditing, showAdvancedTypes, panZoom);
|
||||
this.root = this.load(cfg, enableEditing, showAdvancedTypes, panZoom, infinitePan);
|
||||
|
||||
this.subscription = this.editModeEnabled.subscribe((open) => {
|
||||
if (!this.moveable || !this.isEditingEnabled) {
|
||||
@@ -148,7 +150,13 @@ export class Scene {
|
||||
return !this.byName.has(v);
|
||||
};
|
||||
|
||||
load(cfg: CanvasFrameOptions, enableEditing: boolean, showAdvancedTypes: boolean, panZoom: boolean) {
|
||||
load(
|
||||
cfg: CanvasFrameOptions,
|
||||
enableEditing: boolean,
|
||||
showAdvancedTypes: boolean,
|
||||
panZoom: boolean,
|
||||
infinitePan: boolean
|
||||
) {
|
||||
this.root = new RootElement(
|
||||
cfg ?? {
|
||||
type: 'frame',
|
||||
@@ -161,6 +169,7 @@ export class Scene {
|
||||
this.isEditingEnabled = enableEditing;
|
||||
this.shouldShowAdvancedTypes = showAdvancedTypes;
|
||||
this.shouldPanZoom = panZoom;
|
||||
this.shouldInfinitePan = infinitePan;
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.div) {
|
||||
|
||||
@@ -68,6 +68,7 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
this.props.options.inlineEditing,
|
||||
this.props.options.showAdvancedTypes,
|
||||
this.props.options.panZoom,
|
||||
this.props.options.infinitePan,
|
||||
this.onUpdateScene,
|
||||
this
|
||||
);
|
||||
@@ -229,7 +230,14 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
const shouldShowAdvancedTypesSwitched =
|
||||
this.props.options.showAdvancedTypes !== nextProps.options.showAdvancedTypes;
|
||||
const panZoomSwitched = this.props.options.panZoom !== nextProps.options.panZoom;
|
||||
if (this.needsReload || inlineEditingSwitched || shouldShowAdvancedTypesSwitched || panZoomSwitched) {
|
||||
const infinitePanSwitched = this.props.options.infinitePan !== nextProps.options.infinitePan;
|
||||
if (
|
||||
this.needsReload ||
|
||||
inlineEditingSwitched ||
|
||||
shouldShowAdvancedTypesSwitched ||
|
||||
panZoomSwitched ||
|
||||
infinitePanSwitched
|
||||
) {
|
||||
if (inlineEditingSwitched) {
|
||||
// Replace scene div to prevent selecto instance leaks
|
||||
this.scene.revId++;
|
||||
@@ -240,7 +248,8 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
nextProps.options.root,
|
||||
nextProps.options.inlineEditing,
|
||||
nextProps.options.showAdvancedTypes,
|
||||
nextProps.options.panZoom
|
||||
nextProps.options.panZoom,
|
||||
nextProps.options.infinitePan
|
||||
);
|
||||
this.scene.updateSize(nextProps.width, nextProps.height);
|
||||
this.scene.updateData(nextProps.data);
|
||||
|
||||
@@ -39,6 +39,14 @@ export const addStandardCanvasEditorOptions = (builder: PanelOptionsEditorBuilde
|
||||
editor: PanZoomHelp,
|
||||
showIf: (opts) => config.featureToggles.canvasPanelPanZoom && opts.panZoom,
|
||||
});
|
||||
builder.addBooleanSwitch({
|
||||
path: 'infinitePan',
|
||||
name: 'Infinite panning',
|
||||
description:
|
||||
'Enable infinite panning - useful for expansive canvases. Warning: this an experimental feature and currently only works well with elements that are top / left constrained',
|
||||
defaultValue: false,
|
||||
showIf: (opts) => config.featureToggles.canvasPanelPanZoom && opts.panZoom,
|
||||
});
|
||||
};
|
||||
|
||||
export const plugin = new PanelPlugin<Options>(CanvasPanel)
|
||||
|
||||
@@ -94,6 +94,8 @@ composableKinds: PanelCfg: {
|
||||
showAdvancedTypes: bool | *true
|
||||
// Enable pan and zoom
|
||||
panZoom: bool | *true
|
||||
// Enable infinite pan
|
||||
infinitePan: bool | *true
|
||||
// The root element of canvas (frame), where all canvas elements are nested
|
||||
// TODO: Figure out how to define a default value for this
|
||||
root: {
|
||||
|
||||
@@ -109,6 +109,10 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
|
||||
};
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* Enable infinite pan
|
||||
*/
|
||||
infinitePan: boolean;
|
||||
/**
|
||||
* Enable inline editing
|
||||
*/
|
||||
@@ -142,6 +146,7 @@ export interface Options {
|
||||
}
|
||||
|
||||
export const defaultOptions: Partial<Options> = {
|
||||
infinitePan: true,
|
||||
inlineEditing: true,
|
||||
panZoom: true,
|
||||
showAdvancedTypes: true,
|
||||
|
||||
Reference in New Issue
Block a user