Canvas: Implement new constraint system (#47911)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Nathan Marrs
2022-04-20 09:59:49 -07:00
committed by GitHub
parent c1f766a374
commit d442608eb9
12 changed files with 315 additions and 250 deletions

View File

@@ -1,6 +1,6 @@
import { ComponentType } from 'react';
import { RegistryItem } from '@grafana/data';
import { Anchor, BackgroundConfig, LineConfig, Placement } from './types';
import { BackgroundConfig, Constraint, LineConfig, Placement } from './types';
import { DimensionContext } from '../dimensions/context';
import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin';
@@ -19,7 +19,7 @@ export interface CanvasElementOptions<TConfig = any> {
config?: TConfig;
// Standard options available for all elements
anchor?: Anchor; // defaults top, left, width and height
constraint?: Constraint; // defaults vertical - top, horizontal - left
placement?: Placement;
background?: BackgroundConfig;
border?: LineConfig;
@@ -29,10 +29,6 @@ export interface CanvasElementProps<TConfig = any, TData = any> {
// Saved config
config: TConfig;
// Calculated position info
width: number;
height: number;
// Raw data
data?: TData;
}
@@ -53,6 +49,6 @@ export interface CanvasElementItem<TConfig = any, TData = any> extends RegistryI
getNewOptions: (options?: CanvasElementOptions) => Omit<CanvasElementOptions<TConfig>, 'type' | 'name'>;
/** Build the configuraiton UI */
/** Build the configuration UI */
registerOptionsUI?: PanelOptionsSupplier<CanvasElementOptions<TConfig>>;
}

View File

@@ -38,7 +38,7 @@ const svgStrokePathClass = css`
`;
export function IconDisplay(props: CanvasElementProps) {
const { width, height, data } = props;
const { data } = props;
if (!data?.path) {
return null;
}
@@ -59,8 +59,6 @@ export function IconDisplay(props: CanvasElementProps) {
<SVG
onClick={onClick}
src={data.path}
width={width}
height={height}
style={svgStyle}
className={svgStyle.strokeWidth ? svgStrokePathClass : undefined}
/>
@@ -78,6 +76,8 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
placement: {
width: 50,
height: 50,
top: 0,
left: 0,
},
...options,
config: {

View File

@@ -6,14 +6,13 @@ import {
CanvasElementItem,
CanvasElementOptions,
canvasElementRegistry,
Placement,
Anchor,
} from 'app/features/canvas';
import { DimensionContext } from 'app/features/dimensions';
import { notFoundItem } from 'app/features/canvas/elements/notFound';
import { GroupState } from './group';
import { LayerElement } from 'app/core/components/Layers/types';
import { Scene } from './scene';
import { HorizontalConstraint, Placement, VerticalConstraint } from '../types';
let counter = 0;
@@ -28,23 +27,24 @@ export class ElementState implements LayerElement {
div?: HTMLDivElement;
// Calculated
width = 100;
height = 100;
data?: any; // depends on the type
// From options, but always set and always valid
anchor: Anchor;
placement: Placement;
constructor(public item: CanvasElementItem, public options: CanvasElementOptions, public parent?: GroupState) {
const fallbackName = `Element ${Date.now()}`;
if (!options) {
this.options = { type: item.id, name: fallbackName };
}
this.anchor = options.anchor ?? {};
this.placement = options.placement ?? {};
options.anchor = this.anchor;
options.placement = this.placement;
options.constraint = options.constraint ?? {
vertical: VerticalConstraint.Top,
horizontal: HorizontalConstraint.Left,
};
options.placement = options.placement ?? { width: 100, height: 100, top: 0, left: 0 };
this.validatePlacement();
this.sizeStyle = {
...options.placement,
position: 'absolute',
};
const scene = this.getScene();
if (!options.name) {
@@ -72,71 +72,106 @@ export class ElementState implements LayerElement {
}
validatePlacement() {
const { anchor, placement } = this;
if (!(anchor.left || anchor.right)) {
anchor.left = true;
}
if (!(anchor.top || anchor.bottom)) {
anchor.top = true;
const { constraint, placement } = this.options;
const { vertical, horizontal } = constraint ?? {};
const updatedPlacement = placement ?? ({} as Placement);
switch (vertical) {
case VerticalConstraint.Top:
updatedPlacement.top = updatedPlacement.top ?? 0;
updatedPlacement.height = updatedPlacement.height ?? 100;
delete updatedPlacement.bottom;
break;
case VerticalConstraint.Bottom:
updatedPlacement.bottom = updatedPlacement.bottom ?? 0;
updatedPlacement.height = updatedPlacement.height ?? 100;
delete updatedPlacement.top;
break;
case VerticalConstraint.TopBottom:
updatedPlacement.top = updatedPlacement.top ?? 0;
updatedPlacement.bottom = updatedPlacement.bottom ?? 0;
delete updatedPlacement.height;
break;
}
const w = placement.width ?? 100; // this.div ? this.div.clientWidth : this.width;
const h = placement.height ?? 100; // this.div ? this.div.clientHeight : this.height;
if (anchor.top) {
if (!placement.top) {
placement.top = 0;
}
if (anchor.bottom) {
delete placement.height;
} else {
placement.height = h;
delete placement.bottom;
}
} else if (anchor.bottom) {
if (!placement.bottom) {
placement.bottom = 0;
}
placement.height = h;
delete placement.top;
switch (horizontal) {
case HorizontalConstraint.Left:
updatedPlacement.left = updatedPlacement.left ?? 0;
updatedPlacement.width = updatedPlacement.width ?? 100;
delete updatedPlacement.right;
break;
case HorizontalConstraint.Right:
updatedPlacement.right = updatedPlacement.right ?? 0;
updatedPlacement.width = updatedPlacement.width ?? 100;
delete updatedPlacement.left;
break;
case HorizontalConstraint.LeftRight:
updatedPlacement.left = updatedPlacement.left ?? 0;
updatedPlacement.right = updatedPlacement.right ?? 0;
delete updatedPlacement.width;
break;
}
if (anchor.left) {
if (!placement.left) {
placement.left = 0;
}
if (anchor.right) {
delete placement.width;
} else {
placement.width = w;
delete placement.right;
}
} else if (anchor.right) {
if (!placement.right) {
placement.right = 0;
}
placement.width = w;
delete placement.left;
this.options.placement = updatedPlacement;
}
this.width = w;
this.height = h;
setPlacementFromConstraint() {
const { constraint } = this.options;
const { vertical, horizontal } = constraint ?? {};
this.options.anchor = this.anchor;
this.options.placement = this.placement;
const elementContainer = this.div && this.div.getBoundingClientRect();
const parentContainer = this.div && this.div.parentElement?.getBoundingClientRect();
const relativeTop =
elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.top - parentContainer.top)) : 0;
const relativeBottom =
elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.bottom - parentContainer.bottom)) : 0;
const relativeLeft =
elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.left - parentContainer.left)) : 0;
const relativeRight =
elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.right - parentContainer.right)) : 0;
const placement = {} as Placement;
const width = elementContainer?.width ?? 100;
const height = elementContainer?.height ?? 100;
switch (vertical) {
case VerticalConstraint.Top:
placement.top = relativeTop;
placement.height = height;
break;
case VerticalConstraint.Bottom:
placement.bottom = relativeBottom;
placement.height = height;
break;
case VerticalConstraint.TopBottom:
placement.top = relativeTop;
placement.bottom = relativeBottom;
break;
}
// The parent size, need to set our own size based on offsets
updateSize(width: number, height: number) {
this.width = width;
this.height = height;
this.validatePlacement();
switch (horizontal) {
case HorizontalConstraint.Left:
placement.left = relativeLeft;
placement.width = width;
break;
case HorizontalConstraint.Right:
placement.right = relativeRight;
placement.width = width;
break;
case HorizontalConstraint.LeftRight:
placement.left = relativeLeft;
placement.right = relativeRight;
break;
}
// Update the CSS position
this.options.placement = placement;
this.sizeStyle = {
...this.options.placement,
position: 'absolute',
};
this.revId++;
}
updateData(ctx: DimensionContext) {
@@ -239,34 +274,55 @@ export class ElementState implements LayerElement {
};
applyDrag = (event: OnDrag) => {
const { placement, anchor } = this;
const { options } = this;
const { placement, constraint } = options;
const { vertical, horizontal } = constraint ?? {};
const deltaX = event.delta[0];
const deltaY = event.delta[1];
const style = event.target.style;
if (anchor.top) {
placement.top! += deltaY;
style.top = `${placement.top}px`;
const isConstrainedTop = vertical === VerticalConstraint.Top || vertical === VerticalConstraint.TopBottom;
const isConstrainedBottom = vertical === VerticalConstraint.Bottom || vertical === VerticalConstraint.TopBottom;
const isConstrainedLeft = horizontal === HorizontalConstraint.Left || horizontal === HorizontalConstraint.LeftRight;
const isConstrainedRight =
horizontal === HorizontalConstraint.Right || horizontal === HorizontalConstraint.LeftRight;
if (isConstrainedTop) {
placement!.top! += deltaY;
style.top = `${placement!.top}px`;
}
if (anchor.bottom) {
placement.bottom! -= deltaY;
style.bottom = `${placement.bottom}px`;
if (isConstrainedBottom) {
placement!.bottom! -= deltaY;
style.bottom = `${placement!.bottom}px`;
}
if (anchor.left) {
placement.left! += deltaX;
style.left = `${placement.left}px`;
if (isConstrainedLeft) {
placement!.left! += deltaX;
style.left = `${placement!.left}px`;
}
if (anchor.right) {
placement.right! -= deltaX;
style.right = `${placement.right}px`;
if (isConstrainedRight) {
placement!.right! -= deltaX;
style.right = `${placement!.right}px`;
}
// TODO: Center + Scale
};
// kinda like:
// https://github.com/grafana/grafana-edge-app/blob/main/src/panels/draw/WrapItem.tsx#L44
applyResize = (event: OnResize) => {
const { placement, anchor } = this;
const { options } = this;
const { placement, constraint } = options;
const { vertical, horizontal } = constraint ?? {};
const top = vertical === VerticalConstraint.Top || vertical === VerticalConstraint.TopBottom;
const bottom = vertical === VerticalConstraint.Bottom || vertical === VerticalConstraint.TopBottom;
const left = horizontal === HorizontalConstraint.Left || horizontal === HorizontalConstraint.LeftRight;
const right = horizontal === HorizontalConstraint.Right || horizontal === HorizontalConstraint.LeftRight;
const style = event.target.style;
const deltaX = event.delta[0];
@@ -275,69 +331,62 @@ export class ElementState implements LayerElement {
const dirTB = event.direction[1];
if (dirLR === 1) {
// RIGHT
if (anchor.right) {
placement.right! -= deltaX;
style.right = `${placement.right}px`;
if (!anchor.left) {
placement.width = event.width;
style.width = `${placement.width}px`;
if (right) {
placement!.right! -= deltaX;
style.right = `${placement!.right}px`;
if (!left) {
placement!.width = event.width;
style.width = `${placement!.width}px`;
}
} else {
placement.width! = event.width;
style.width = `${placement.width}px`;
placement!.width! = event.width;
style.width = `${placement!.width}px`;
}
} else if (dirLR === -1) {
// LEFT
if (anchor.left) {
placement.left! -= deltaX;
placement.width! = event.width;
style.left = `${placement.left}px`;
style.width = `${placement.width}px`;
if (left) {
placement!.left! -= deltaX;
placement!.width! = event.width;
style.left = `${placement!.left}px`;
style.width = `${placement!.width}px`;
} else {
placement.width! += deltaX;
style.width = `${placement.width}px`;
placement!.width! += deltaX;
style.width = `${placement!.width}px`;
}
}
if (dirTB === -1) {
// TOP
if (anchor.top) {
placement.top! -= deltaY;
placement.height = event.height;
style.top = `${placement.top}px`;
style.height = `${placement.height}px`;
if (top) {
placement!.top! -= deltaY;
placement!.height = event.height;
style.top = `${placement!.top}px`;
style.height = `${placement!.height}px`;
} else {
placement.height = event.height;
style.height = `${placement.height}px`;
placement!.height = event.height;
style.height = `${placement!.height}px`;
}
} else if (dirTB === 1) {
// BOTTOM
if (anchor.bottom) {
placement.bottom! -= deltaY;
placement.height! = event.height;
style.bottom = `${placement.bottom}px`;
style.height = `${placement.height}px`;
if (bottom) {
placement!.bottom! -= deltaY;
placement!.height! = event.height;
style.bottom = `${placement!.bottom}px`;
style.height = `${placement!.height}px`;
} else {
placement.height! = event.height;
style.height = `${placement.height}px`;
placement!.height! = event.height;
style.height = `${placement!.height}px`;
}
}
this.width = event.width;
this.height = event.height;
// TODO: Center + Scale
};
render() {
const { item } = this;
return (
<div key={`${this.UID}`} style={{ ...this.sizeStyle, ...this.dataStyle }} ref={this.initElement}>
<item.display
key={`${this.UID}/${this.revId}`}
config={this.options.config}
width={this.width}
height={this.height}
data={this.data}
/>
<item.display key={`${this.UID}/${this.revId}`} config={this.options.config} data={this.data} />
</div>
);
}

View File

@@ -8,6 +8,7 @@ import { LayerActionID } from 'app/plugins/panel/canvas/types';
import { cloneDeep } from 'lodash';
import { Scene } from './scene';
import { RootElement } from './root';
import { HorizontalConstraint, Placement, VerticalConstraint } from '../types';
export const groupItemDummy: CanvasElementItem = {
id: 'group',
@@ -53,27 +54,6 @@ export class GroupState extends ElementState {
return false;
}
// The parent size, need to set our own size based on offsets
updateSize(width: number, height: number) {
super.updateSize(width, height);
if (!this.parent) {
this.width = width;
this.height = height;
this.sizeStyle.width = width;
this.sizeStyle.height = height;
}
// Update children with calculated size
for (const elem of this.elements) {
elem.updateSize(this.width, this.height);
}
// The group forced to full width (for now)
this.sizeStyle.width = width;
this.sizeStyle.height = height;
this.sizeStyle.position = 'absolute';
}
updateData(ctx: DimensionContext) {
super.updateData(ctx);
for (const elem of this.elements) {
@@ -113,21 +93,50 @@ export class GroupState extends ElementState {
return;
}
const opts = cloneDeep(element.options);
if (element.anchor.top) {
opts.placement!.top! += 10;
const { constraint, placement: oldPlacement } = element.options;
const { vertical, horizontal } = constraint ?? {};
const placement = oldPlacement ?? ({} as Placement);
switch (vertical) {
case VerticalConstraint.Top:
case VerticalConstraint.TopBottom:
if (placement.top == null) {
placement.top = 25;
} else {
placement.top += 10;
}
if (element.anchor.left) {
opts.placement!.left! += 10;
break;
case VerticalConstraint.Bottom:
if (placement.bottom == null) {
placement.bottom = 100;
} else {
placement.bottom -= 10;
}
if (element.anchor.bottom) {
opts.placement!.bottom! += 10;
}
if (element.anchor.right) {
opts.placement!.right! += 10;
break;
}
switch (horizontal) {
case HorizontalConstraint.Left:
case HorizontalConstraint.LeftRight:
if (placement.left == null) {
placement.left = 50;
} else {
placement.left += 10;
}
break;
case HorizontalConstraint.Right:
if (placement.right == null) {
placement.right = 50;
} else {
placement.right -= 10;
}
break;
}
opts.placement = placement;
const copy = new ElementState(element.item, opts, this);
copy.updateSize(element.width, element.height);
copy.updateData(this.scene.context);
if (updateName) {
copy.options.name = this.scene.getNextElementName();

View File

@@ -5,21 +5,17 @@ import { Scene } from './scene';
export class RootElement extends GroupState {
constructor(public options: CanvasGroupOptions, public scene: Scene, private changeCallback: () => void) {
super(options, scene);
this.sizeStyle = {
height: '100%',
width: '100%',
};
}
isRoot(): this is RootElement {
return true;
}
// The parent size is always fullsize
updateSize(width: number, height: number) {
super.updateSize(width, height);
this.width = width;
this.height = height;
this.sizeStyle.width = width;
this.sizeStyle.height = height;
}
// root type can not change
onChange(options: CanvasElementOptions) {
this.revId++;
@@ -28,10 +24,10 @@ export class RootElement extends GroupState {
}
getSaveModel(): CanvasGroupOptions {
const { placement, anchor, ...rest } = this.options;
const { placement, constraint, ...rest } = this.options;
return {
...rest, // everything except placement & anchor
...rest, // everything except placement & constraint
elements: this.elements.map((v) => v.getSaveModel()),
};
}

View File

@@ -8,7 +8,7 @@ import Selecto from 'selecto';
import { config } from 'app/core/config';
import { GrafanaTheme2, PanelData } from '@grafana/data';
import { stylesFactory } from '@grafana/ui';
import { Anchor, CanvasGroupOptions, DEFAULT_CANVAS_ELEMENT_CONFIG, Placement } from 'app/features/canvas';
import { CanvasGroupOptions, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas';
import {
ColorDimensionConfig,
ResourceDimensionConfig,
@@ -112,7 +112,6 @@ export class Scene {
this.width = width;
this.height = height;
this.style = { width, height };
this.root.updateSize(width, height);
if (this.selecto?.getSelectedTargets().length) {
this.clearCurrentSelection();
@@ -157,44 +156,16 @@ export class Scene {
this.save();
}
toggleAnchor(element: ElementState, k: keyof Anchor) {
const { div } = element;
if (!div) {
console.log('Not ready');
return;
}
const w = element.parent?.width ?? 100;
const h = element.parent?.height ?? 100;
// Get computed position....
const info = div.getBoundingClientRect(); // getElementInfo(div, element.parent?.div);
console.log('DIV info', div);
const placement: Placement = {
top: info.top,
left: info.left,
width: info.width,
height: info.height,
bottom: h - info.bottom,
right: w - info.right,
};
console.log('PPP', placement);
// // TODO: needs to recalculate placement based on absolute values...
// element.anchor[k] = !Boolean(element.anchor[k]);
// element.placement = placement;
// element.validatePlacement();
// element.revId++;
// this.revId++;
// this.save();
this.moved.next(Date.now());
}
save = () => {
save = (updateMoveable = false) => {
this.onSave(this.root.getSaveModel());
if (updateMoveable) {
setTimeout(() => {
if (this.div) {
this.initMoveable(true);
}
}, 100);
}
};
private findElementByTarget = (target: HTMLElement | SVGElement): ElementState | undefined => {
@@ -262,8 +233,8 @@ export class Scene {
initMoveable = (destroySelecto = false, allowChanges = true) => {
const targetElements = this.generateTargetElements(this.root.elements);
if (destroySelecto) {
this.selecto?.destroy();
if (destroySelecto && this.selecto) {
this.selecto.destroy();
}
this.selecto = new Selecto({
@@ -295,9 +266,8 @@ export class Scene {
.on('dragEnd', (event) => {
const targetedElement = this.findElementByTarget(event.target);
if (targetedElement && targetedElement.parent) {
const parent = targetedElement.parent;
targetedElement.updateSize(parent.width, parent.height);
if (targetedElement) {
targetedElement?.setPlacementFromConstraint();
}
})
.on('resize', (event) => {
@@ -311,6 +281,13 @@ export class Scene {
targetedElement!.applyResize(event);
});
this.moved.next(Date.now()); // TODO only on end
})
.on('resizeEnd', (event) => {
const targetedElement = this.findElementByTarget(event.target);
if (targetedElement) {
targetedElement?.setPlacementFromConstraint();
}
});
let targets: Array<HTMLElement | SVGElement> = [];

View File

@@ -10,11 +10,25 @@ export interface Placement {
height?: number;
}
export interface Anchor {
top?: boolean;
left?: boolean;
right?: boolean;
bottom?: boolean;
export interface Constraint {
horizontal?: HorizontalConstraint;
vertical?: VerticalConstraint;
}
export enum HorizontalConstraint {
Left = 'left',
Right = 'right',
LeftRight = 'leftright',
Center = 'center',
Scale = 'scale',
}
export enum VerticalConstraint {
Top = 'top',
Bottom = 'bottom',
TopBottom = 'topbottom',
Center = 'center',
Scale = 'scale',
}
export enum BackgroundImageSize {

View File

@@ -38,7 +38,6 @@ export class LayerElementListEditor extends PureComponent<Props> {
const newElementOptions = item.getNewOptions() as CanvasElementOptions;
newElementOptions.type = item.id;
const newElement = new ElementState(item, newElementOptions, layer);
newElement.updateSize(newElement.width, newElement.height);
newElement.updateData(layer.scene.context);
layer.elements.push(newElement);
layer.scene.save();

View File

@@ -1,18 +1,32 @@
import React, { FC } from 'react';
import { Button, Field, HorizontalGroup, InlineField, InlineFieldRow } from '@grafana/ui';
import { StandardEditorProps } from '@grafana/data';
import { PanelOptions } from '../models.gen';
import { useObservable } from 'react-use';
import { Subject } from 'rxjs';
import { Field, InlineField, InlineFieldRow, Select, VerticalGroup } from '@grafana/ui';
import { SelectableValue, StandardEditorProps } from '@grafana/data';
import { PanelOptions } from '../models.gen';
import { CanvasEditorOptions } from './elementEditor';
import { Anchor, Placement } from 'app/features/canvas';
import { HorizontalConstraint, Placement, VerticalConstraint } from 'app/features/canvas';
import { NumberInput } from 'app/features/dimensions/editors/NumberInput';
const anchors: Array<keyof Anchor> = ['top', 'left', 'bottom', 'right'];
const places: Array<keyof Placement> = ['top', 'left', 'bottom', 'right', 'width', 'height'];
export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({ item }) => {
const horizontalOptions: Array<SelectableValue<HorizontalConstraint>> = [
{ label: 'Left', value: HorizontalConstraint.Left },
{ label: 'Right', value: HorizontalConstraint.Right },
{ label: 'Left and right', value: HorizontalConstraint.LeftRight },
];
const verticalOptions: Array<SelectableValue<VerticalConstraint>> = [
{ label: 'Top', value: VerticalConstraint.Top },
{ label: 'Bottom', value: VerticalConstraint.Bottom },
{ label: 'Top and bottom', value: VerticalConstraint.TopBottom },
];
export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({
item,
onChange,
}) => {
const settings = item.settings;
// Will force a rerender whenever the subject changes
@@ -26,28 +40,39 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
if (!element) {
return <div>???</div>;
}
const { placement } = element;
const { options } = element;
const { placement, constraint: layout } = options;
const onHorizontalConstraintChange = (h: SelectableValue<HorizontalConstraint>) => {
element.options.constraint!.horizontal = h.value;
element.setPlacementFromConstraint();
settings.scene.revId++;
settings.scene.save(true);
};
const onVerticalConstraintChange = (v: SelectableValue<VerticalConstraint>) => {
element.options.constraint!.vertical = v.value;
element.setPlacementFromConstraint();
settings.scene.revId++;
settings.scene.save(true);
};
return (
<div>
<HorizontalGroup>
{anchors.map((a) => (
<Button
key={a}
size="sm"
variant={element.anchor[a] ? 'primary' : 'secondary'}
onClick={() => settings.scene.toggleAnchor(element, a)}
>
{a}
</Button>
))}
</HorizontalGroup>
<VerticalGroup>
<Select options={verticalOptions} onChange={onVerticalConstraintChange} value={layout?.vertical} />
<Select
options={horizontalOptions}
onChange={onHorizontalConstraintChange}
value={options.constraint?.horizontal}
/>
</VerticalGroup>
<br />
<Field label="Position">
<>
{places.map((p) => {
const v = placement[p];
const v = placement![p];
if (v == null) {
return null;
}

View File

@@ -84,7 +84,7 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<
category: ['Layout'],
id: 'content',
path: '__', // not used
name: 'Anchor',
name: 'Constraints',
editor: PlacementEditor,
settings: opts,
});

View File

@@ -81,7 +81,7 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions<LayerEdi
category: ['Layout'],
id: 'content',
path: '__', // not used
name: 'Anchor',
name: 'Constraints',
editor: PlacementEditor,
settings: {
scene: opts.scene,

View File

@@ -16,6 +16,7 @@ import {
ScaleDimensionConfig,
TextDimensionConfig,
} from 'app/features/dimensions';
import { HorizontalConstraint, VerticalConstraint } from 'app/features/canvas';
interface Props extends PanelProps<PanelOptions> {}
@@ -36,17 +37,16 @@ export class IconPanel extends Component<Props> {
updateSize = (props: Props) => {
const { width, height } = props;
this.element.anchor = {
top: true,
left: true,
this.element.options.constraint = {
vertical: VerticalConstraint.Top,
horizontal: HorizontalConstraint.Left,
};
this.element.placement = {
this.element.options.placement = {
left: 0,
top: 0,
width,
height,
};
this.element.updateSize(width, height);
};
dims: DimensionContext = {