mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 20:42:10 +08:00
Canvas: Support scale + center constraints (#48085)
This commit is contained in:
@ -42,12 +42,6 @@ export class ElementState implements LayerElement {
|
|||||||
horizontal: HorizontalConstraint.Left,
|
horizontal: HorizontalConstraint.Left,
|
||||||
};
|
};
|
||||||
options.placement = options.placement ?? { width: 100, height: 100, top: 0, left: 0 };
|
options.placement = options.placement ?? { width: 100, height: 100, top: 0, left: 0 };
|
||||||
this.validatePlacement();
|
|
||||||
this.sizeStyle = {
|
|
||||||
...options.placement,
|
|
||||||
position: 'absolute',
|
|
||||||
};
|
|
||||||
|
|
||||||
const scene = this.getScene();
|
const scene = this.getScene();
|
||||||
if (!options.name) {
|
if (!options.name) {
|
||||||
const newName = scene?.getNextElementName();
|
const newName = scene?.getNextElementName();
|
||||||
@ -61,7 +55,6 @@ export class ElementState implements LayerElement {
|
|||||||
while (trav) {
|
while (trav) {
|
||||||
if (trav.isRoot()) {
|
if (trav.isRoot()) {
|
||||||
return trav.scene;
|
return trav.scene;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
trav = trav.parent;
|
trav = trav.parent;
|
||||||
}
|
}
|
||||||
@ -73,48 +66,108 @@ export class ElementState implements LayerElement {
|
|||||||
return this.options.name;
|
return this.options.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
validatePlacement() {
|
/** Use the configured options to update CSS style properties directly on the wrapper div **/
|
||||||
const { constraint, placement } = this.options;
|
applyLayoutStylesToDiv() {
|
||||||
|
const { constraint } = this.options;
|
||||||
const { vertical, horizontal } = constraint ?? {};
|
const { vertical, horizontal } = constraint ?? {};
|
||||||
const updatedPlacement = placement ?? ({} as Placement);
|
const placement = this.options.placement ?? ({} as Placement);
|
||||||
|
|
||||||
|
const style: React.CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
};
|
||||||
|
|
||||||
|
const translate = ['0px', '0px'];
|
||||||
|
|
||||||
switch (vertical) {
|
switch (vertical) {
|
||||||
case VerticalConstraint.Top:
|
case VerticalConstraint.Top:
|
||||||
updatedPlacement.top = updatedPlacement.top ?? 0;
|
placement.top = placement.top ?? 0;
|
||||||
updatedPlacement.height = updatedPlacement.height ?? 100;
|
placement.height = placement.height ?? 100;
|
||||||
delete updatedPlacement.bottom;
|
style.top = `${placement.top}px`;
|
||||||
|
style.height = `${placement.height}px`;
|
||||||
|
delete placement.bottom;
|
||||||
break;
|
break;
|
||||||
case VerticalConstraint.Bottom:
|
case VerticalConstraint.Bottom:
|
||||||
updatedPlacement.bottom = updatedPlacement.bottom ?? 0;
|
placement.bottom = placement.bottom ?? 0;
|
||||||
updatedPlacement.height = updatedPlacement.height ?? 100;
|
placement.height = placement.height ?? 100;
|
||||||
delete updatedPlacement.top;
|
style.bottom = `${placement.bottom}px`;
|
||||||
|
style.height = `${placement.height}px`;
|
||||||
|
delete placement.top;
|
||||||
break;
|
break;
|
||||||
case VerticalConstraint.TopBottom:
|
case VerticalConstraint.TopBottom:
|
||||||
updatedPlacement.top = updatedPlacement.top ?? 0;
|
placement.top = placement.top ?? 0;
|
||||||
updatedPlacement.bottom = updatedPlacement.bottom ?? 0;
|
placement.bottom = placement.bottom ?? 0;
|
||||||
delete updatedPlacement.height;
|
style.top = `${placement.top}px`;
|
||||||
|
style.bottom = `${placement.bottom}px`;
|
||||||
|
delete placement.height;
|
||||||
|
break;
|
||||||
|
case VerticalConstraint.Center:
|
||||||
|
placement.top = placement.top ?? 0;
|
||||||
|
placement.height = placement.height ?? 100;
|
||||||
|
translate[1] = '-50%';
|
||||||
|
style.top = `calc(50% - ${placement.top}px)`;
|
||||||
|
style.height = `${placement.height}px`;
|
||||||
|
delete placement.bottom;
|
||||||
|
break;
|
||||||
|
case VerticalConstraint.Scale:
|
||||||
|
placement.top = placement.top ?? 0;
|
||||||
|
placement.bottom = placement.bottom ?? 0;
|
||||||
|
style.top = `${placement.top}%`;
|
||||||
|
style.bottom = `${placement.bottom}%`;
|
||||||
|
delete placement.height;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (horizontal) {
|
switch (horizontal) {
|
||||||
case HorizontalConstraint.Left:
|
case HorizontalConstraint.Left:
|
||||||
updatedPlacement.left = updatedPlacement.left ?? 0;
|
placement.left = placement.left ?? 0;
|
||||||
updatedPlacement.width = updatedPlacement.width ?? 100;
|
placement.width = placement.width ?? 100;
|
||||||
delete updatedPlacement.right;
|
style.left = `${placement.left}px`;
|
||||||
|
style.width = `${placement.width}px`;
|
||||||
|
delete placement.right;
|
||||||
break;
|
break;
|
||||||
case HorizontalConstraint.Right:
|
case HorizontalConstraint.Right:
|
||||||
updatedPlacement.right = updatedPlacement.right ?? 0;
|
placement.right = placement.right ?? 0;
|
||||||
updatedPlacement.width = updatedPlacement.width ?? 100;
|
placement.width = placement.width ?? 100;
|
||||||
delete updatedPlacement.left;
|
style.right = `${placement.right}px`;
|
||||||
|
style.width = `${placement.width}px`;
|
||||||
|
delete placement.left;
|
||||||
break;
|
break;
|
||||||
case HorizontalConstraint.LeftRight:
|
case HorizontalConstraint.LeftRight:
|
||||||
updatedPlacement.left = updatedPlacement.left ?? 0;
|
placement.left = placement.left ?? 0;
|
||||||
updatedPlacement.right = updatedPlacement.right ?? 0;
|
placement.right = placement.right ?? 0;
|
||||||
delete updatedPlacement.width;
|
style.left = `${placement.left}px`;
|
||||||
|
style.right = `${placement.right}px`;
|
||||||
|
delete placement.width;
|
||||||
|
break;
|
||||||
|
case HorizontalConstraint.Center:
|
||||||
|
placement.left = placement.left ?? 0;
|
||||||
|
placement.width = placement.width ?? 100;
|
||||||
|
translate[0] = '-50%';
|
||||||
|
style.left = `calc(50% - ${placement.left}px)`;
|
||||||
|
style.width = `${placement.width}px`;
|
||||||
|
delete placement.right;
|
||||||
|
break;
|
||||||
|
case HorizontalConstraint.Scale:
|
||||||
|
placement.left = placement.left ?? 0;
|
||||||
|
placement.right = placement.right ?? 0;
|
||||||
|
style.left = `${placement.left}%`;
|
||||||
|
style.right = `${placement.right}%`;
|
||||||
|
delete placement.width;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options.placement = updatedPlacement;
|
style.transform = `translate(${translate[0]}, ${translate[1]})`;
|
||||||
|
this.options.placement = placement;
|
||||||
|
this.sizeStyle = style;
|
||||||
|
if (this.div) {
|
||||||
|
for (const key in this.sizeStyle) {
|
||||||
|
this.div.style[key as any] = (this.sizeStyle as any)[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in this.dataStyle) {
|
||||||
|
this.div.style[key as any] = (this.dataStyle as any)[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlacementFromConstraint() {
|
setPlacementFromConstraint() {
|
||||||
@ -151,6 +204,17 @@ export class ElementState implements LayerElement {
|
|||||||
placement.top = relativeTop;
|
placement.top = relativeTop;
|
||||||
placement.bottom = relativeBottom;
|
placement.bottom = relativeBottom;
|
||||||
break;
|
break;
|
||||||
|
case VerticalConstraint.Center:
|
||||||
|
const elementCenter = elementContainer ? relativeTop + height / 2 : 0;
|
||||||
|
const parentCenter = parentContainer ? parentContainer.height / 2 : 0;
|
||||||
|
const distanceFromCenter = parentCenter - elementCenter;
|
||||||
|
placement.top = distanceFromCenter;
|
||||||
|
placement.height = height;
|
||||||
|
break;
|
||||||
|
case VerticalConstraint.Scale:
|
||||||
|
placement.top = (relativeTop / (parentContainer?.height ?? height)) * 100;
|
||||||
|
placement.bottom = (relativeBottom / (parentContainer?.height ?? height)) * 100;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (horizontal) {
|
switch (horizontal) {
|
||||||
@ -166,13 +230,22 @@ export class ElementState implements LayerElement {
|
|||||||
placement.left = relativeLeft;
|
placement.left = relativeLeft;
|
||||||
placement.right = relativeRight;
|
placement.right = relativeRight;
|
||||||
break;
|
break;
|
||||||
|
case HorizontalConstraint.Center:
|
||||||
|
const elementCenter = elementContainer ? relativeLeft + width / 2 : 0;
|
||||||
|
const parentCenter = parentContainer ? parentContainer.width / 2 : 0;
|
||||||
|
const distanceFromCenter = parentCenter - elementCenter;
|
||||||
|
placement.left = distanceFromCenter;
|
||||||
|
placement.width = width;
|
||||||
|
break;
|
||||||
|
case HorizontalConstraint.Scale:
|
||||||
|
placement.left = (relativeLeft / (parentContainer?.width ?? width)) * 100;
|
||||||
|
placement.right = (relativeRight / (parentContainer?.width ?? width)) * 100;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options.placement = placement;
|
this.options.placement = placement;
|
||||||
this.sizeStyle = {
|
|
||||||
...this.options.placement,
|
this.applyLayoutStylesToDiv();
|
||||||
position: 'absolute',
|
|
||||||
};
|
|
||||||
this.revId++;
|
this.revId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,45 +346,11 @@ export class ElementState implements LayerElement {
|
|||||||
|
|
||||||
initElement = (target: HTMLDivElement) => {
|
initElement = (target: HTMLDivElement) => {
|
||||||
this.div = target;
|
this.div = target;
|
||||||
|
this.applyLayoutStylesToDiv();
|
||||||
};
|
};
|
||||||
|
|
||||||
applyDrag = (event: OnDrag) => {
|
applyDrag = (event: OnDrag) => {
|
||||||
const { options } = this;
|
event.target.style.transform = event.transform;
|
||||||
const { placement, constraint } = options;
|
|
||||||
const { vertical, horizontal } = constraint ?? {};
|
|
||||||
|
|
||||||
const deltaX = event.delta[0];
|
|
||||||
const deltaY = event.delta[1];
|
|
||||||
|
|
||||||
const style = event.target.style;
|
|
||||||
|
|
||||||
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 (isConstrainedBottom) {
|
|
||||||
placement!.bottom! -= deltaY;
|
|
||||||
style.bottom = `${placement!.bottom}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isConstrainedLeft) {
|
|
||||||
placement!.left! += deltaX;
|
|
||||||
style.left = `${placement!.left}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isConstrainedRight) {
|
|
||||||
placement!.right! -= deltaX;
|
|
||||||
style.right = `${placement!.right}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Center + Scale
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// kinda like:
|
// kinda like:
|
||||||
@ -380,14 +419,12 @@ export class ElementState implements LayerElement {
|
|||||||
style.height = `${placement!.height}px`;
|
style.height = `${placement!.height}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Center + Scale
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item } = this;
|
const { item } = this;
|
||||||
return (
|
return (
|
||||||
<div key={`${this.UID}`} style={{ ...this.sizeStyle, ...this.dataStyle }} ref={this.initElement}>
|
<div key={this.UID} ref={this.initElement}>
|
||||||
<item.display key={`${this.UID}/${this.revId}`} config={this.options.config} data={this.data} />
|
<item.display key={`${this.UID}/${this.revId}`} config={this.options.config} data={this.data} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -255,21 +255,20 @@ export class Scene {
|
|||||||
.on('drag', (event) => {
|
.on('drag', (event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
const targetedElement = this.findElementByTarget(event.target);
|
||||||
targetedElement!.applyDrag(event);
|
targetedElement!.applyDrag(event);
|
||||||
this.moved.next(Date.now()); // TODO only on end
|
|
||||||
})
|
})
|
||||||
.on('dragGroup', (e) => {
|
.on('dragGroup', (e) => {
|
||||||
e.events.forEach((event) => {
|
e.events.forEach((event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
const targetedElement = this.findElementByTarget(event.target);
|
||||||
targetedElement!.applyDrag(event);
|
targetedElement!.applyDrag(event);
|
||||||
});
|
});
|
||||||
this.moved.next(Date.now()); // TODO only on end
|
|
||||||
})
|
})
|
||||||
.on('dragEnd', (event) => {
|
.on('dragEnd', (event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
const targetedElement = this.findElementByTarget(event.target);
|
||||||
|
|
||||||
if (targetedElement) {
|
if (targetedElement) {
|
||||||
targetedElement?.setPlacementFromConstraint();
|
targetedElement?.setPlacementFromConstraint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.moved.next(Date.now());
|
||||||
})
|
})
|
||||||
.on('resize', (event) => {
|
.on('resize', (event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
const targetedElement = this.findElementByTarget(event.target);
|
||||||
|
@ -17,18 +17,19 @@ const horizontalOptions: Array<SelectableValue<HorizontalConstraint>> = [
|
|||||||
{ label: 'Left', value: HorizontalConstraint.Left },
|
{ label: 'Left', value: HorizontalConstraint.Left },
|
||||||
{ label: 'Right', value: HorizontalConstraint.Right },
|
{ label: 'Right', value: HorizontalConstraint.Right },
|
||||||
{ label: 'Left and right', value: HorizontalConstraint.LeftRight },
|
{ label: 'Left and right', value: HorizontalConstraint.LeftRight },
|
||||||
|
{ label: 'Center', value: HorizontalConstraint.Center },
|
||||||
|
{ label: 'Scale', value: HorizontalConstraint.Scale },
|
||||||
];
|
];
|
||||||
|
|
||||||
const verticalOptions: Array<SelectableValue<VerticalConstraint>> = [
|
const verticalOptions: Array<SelectableValue<VerticalConstraint>> = [
|
||||||
{ label: 'Top', value: VerticalConstraint.Top },
|
{ label: 'Top', value: VerticalConstraint.Top },
|
||||||
{ label: 'Bottom', value: VerticalConstraint.Bottom },
|
{ label: 'Bottom', value: VerticalConstraint.Bottom },
|
||||||
{ label: 'Top and bottom', value: VerticalConstraint.TopBottom },
|
{ label: 'Top and bottom', value: VerticalConstraint.TopBottom },
|
||||||
|
{ label: 'Center', value: VerticalConstraint.Center },
|
||||||
|
{ label: 'Scale', value: VerticalConstraint.Scale },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({
|
export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, PanelOptions>> = ({ item }) => {
|
||||||
item,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
const settings = item.settings;
|
const settings = item.settings;
|
||||||
|
|
||||||
// Will force a rerender whenever the subject changes
|
// Will force a rerender whenever the subject changes
|
||||||
@ -59,6 +60,12 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
|
|||||||
settings.scene.save(true);
|
settings.scene.save(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onPositionChange = (value: number | undefined, placement: keyof Placement) => {
|
||||||
|
element.options.placement![placement] = value ?? element.options.placement![placement];
|
||||||
|
element.applyLayoutStylesToDiv();
|
||||||
|
settings.scene.clearCurrentSelection();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<VerticalGroup>
|
<VerticalGroup>
|
||||||
@ -81,7 +88,7 @@ export const PlacementEditor: FC<StandardEditorProps<any, CanvasEditorOptions, P
|
|||||||
return (
|
return (
|
||||||
<InlineFieldRow key={p}>
|
<InlineFieldRow key={p}>
|
||||||
<InlineField label={p} labelWidth={8} grow={true}>
|
<InlineField label={p} labelWidth={8} grow={true}>
|
||||||
<NumberInput value={v} onChange={(v) => console.log('TODO, edit!!!', p, v)} />
|
<NumberInput value={v} onChange={(v) => onPositionChange(v, p)} />
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user