[v10.4.x] Canvas: Infinite Pan Backport (#85608)
This commit is contained in:
@@ -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 |
|
||||
|
||||
+5
@@ -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 */}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user