[v10.4.x] Canvas: Infinite Pan Backport (#85608)

This commit is contained in:
Drew Slobodnjak
2024-04-04 11:02:27 -07:00
committed by GitHub
parent 8da97103ad
commit 6b124e2031
8 changed files with 77 additions and 5 deletions
@@ -141,6 +141,7 @@ It extends [BaseDimensionConfig](#basedimensionconfig).
| Property | Type | Required | Default | Description |
|---------------------|-----------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------|
| `infinitePan` | boolean | **Yes** | `true` | Enable infinite pan |
| `inlineEditing` | boolean | **Yes** | `true` | Enable inline editing |
| `panZoom` | boolean | **Yes** | `true` | Enable pan and zoom |
| `root` | [object](#root) | **Yes** | | The root element of canvas (frame), where all canvas elements are nested<br/>TODO: Figure out how to define a default value for this |
@@ -105,6 +105,10 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
};
export interface Options {
/**
* Enable infinite pan
*/
infinitePan: boolean;
/**
* Enable inline editing
*/
@@ -138,6 +142,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) => {
@@ -22,6 +31,25 @@ export const SceneTransformWrapper = ({ scene, children: sceneDiv }: SceneTransf
updateMoveable(scale);
};
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 onTransformed = (
_: ReactZoomPanPinchRef,
state: {
@@ -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 */}
+11 -2
View File
@@ -71,6 +71,7 @@ export class Scene {
isEditingEnabled?: boolean;
shouldShowAdvancedTypes?: boolean;
shouldPanZoom?: boolean;
shouldInfinitePan?: boolean;
skipNextSelectionBroadcast = false;
ignoreDataUpdate = false;
panel: CanvasPanel;
@@ -108,10 +109,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) {
@@ -144,7 +146,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',
@@ -157,6 +165,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)
@@ -90,6 +90,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: {
@@ -102,6 +102,10 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
};
export interface Options {
/**
* Enable infinite pan
*/
infinitePan: boolean;
/**
* Enable inline editing
*/
@@ -135,6 +139,7 @@ export interface Options {
}
export const defaultOptions: Partial<Options> = {
infinitePan: true,
inlineEditing: true,
panZoom: true,
showAdvancedTypes: true,