mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
235 lines
7.1 KiB
TypeScript
235 lines
7.1 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import React from 'react';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { config } from 'app/core/config';
|
|
import { DimensionContext } from 'app/features/dimensions';
|
|
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
|
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
|
|
|
import {
|
|
CanvasElementItem,
|
|
CanvasElementProps,
|
|
CanvasElementOptions,
|
|
defaultBgColor,
|
|
defaultTextColor,
|
|
} from '../element';
|
|
import { Align, CanvasElementConfig, CanvasElementData, VAlign } from '../types';
|
|
|
|
const Cloud = (props: CanvasElementProps<CanvasElementConfig, CanvasElementData>) => {
|
|
const { data } = props;
|
|
const styles = getStyles(config.theme2, data);
|
|
|
|
// uuid needed to avoid id conflicts when multiple elements are rendered
|
|
const uniqueId = uuidv4();
|
|
|
|
return (
|
|
<div className={styles.container}>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 110 70"
|
|
width="100%"
|
|
height="100%"
|
|
preserveAspectRatio="none"
|
|
>
|
|
<defs>
|
|
<pattern id={`image-${uniqueId}`} patternUnits="userSpaceOnUse" width="110" height="70">
|
|
<image xlinkHref={data?.backgroundImage} x="-50" y="-50" width="300" height="300"></image>
|
|
</pattern>
|
|
<clipPath id={`cloudClip-${uniqueId}`}>
|
|
<path d="M 23 13 C -1 13 -7 33 12.2 37 C -7 45.8 14.6 65 30.2 57 C 41 73 77 73 89 57 C 113 57 113 41 98 33 C 113 17 89 1 68 9 C 53 -3 29 -3 23 13 Z" />
|
|
</clipPath>
|
|
</defs>
|
|
{/* Apply background image within the clipping area */}
|
|
<rect
|
|
x="0"
|
|
y="0"
|
|
width="100%"
|
|
height="100%"
|
|
clipPath={`url(#cloudClip-${uniqueId})`}
|
|
style={{ fill: 'none' }}
|
|
/>
|
|
<path
|
|
d="M 23 13 C -1 13 -7 33 12.2 37 C -7 45.8 14.6 65 30.2 57 C 41 73 77 73 89 57 C 113 57 113 41 98 33 C 113 17 89 1 68 9 C 53 -3 29 -3 23 13 Z"
|
|
style={{ fill: data?.backgroundImage ? `url(#image-${uniqueId})` : data?.backgroundColor }}
|
|
/>
|
|
|
|
{/* Border */}
|
|
<path
|
|
d="M 23 13 C -1 13 -7 33 12.2 37 C -7 45.8 14.6 65 30.2 57 C 41 73 77 73 89 57 C 113 57 113 41 98 33 C 113 17 89 1 68 9 C 53 -3 29 -3 23 13 Z"
|
|
clipPath={`url(#cloudClip-${uniqueId})`}
|
|
className={styles.elementBorder}
|
|
/>
|
|
</svg>
|
|
<span className={styles.text}>{data?.text}</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const cloudItem: CanvasElementItem = {
|
|
id: 'cloud',
|
|
name: 'Cloud',
|
|
description: 'Cloud',
|
|
|
|
display: Cloud,
|
|
|
|
defaultSize: {
|
|
width: 110,
|
|
height: 70,
|
|
},
|
|
|
|
getNewOptions: (options) => ({
|
|
...options,
|
|
background: {
|
|
color: {
|
|
fixed: defaultBgColor,
|
|
},
|
|
},
|
|
config: {
|
|
align: Align.Center,
|
|
valign: VAlign.Middle,
|
|
color: {
|
|
fixed: defaultTextColor,
|
|
},
|
|
},
|
|
placement: {
|
|
width: options?.placement?.width ?? 110,
|
|
height: options?.placement?.height ?? 70,
|
|
top: options?.placement?.top,
|
|
left: options?.placement?.left,
|
|
rotation: options?.placement?.rotation ?? 0,
|
|
},
|
|
}),
|
|
|
|
// Called when data changes
|
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<CanvasElementConfig>) => {
|
|
const textConfig = elementOptions.config;
|
|
|
|
const data: CanvasElementData = {
|
|
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
|
align: textConfig?.align ?? Align.Center,
|
|
valign: textConfig?.valign ?? VAlign.Middle,
|
|
size: textConfig?.size,
|
|
};
|
|
|
|
if (textConfig?.color) {
|
|
data.color = dimensionContext.getColor(textConfig.color).value();
|
|
}
|
|
|
|
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
|
|
|
const { background, border } = elementOptions;
|
|
data.backgroundColor = background?.color ? dimensionContext.getColor(background.color).value() : defaultBgColor;
|
|
data.borderColor = border?.color ? dimensionContext.getColor(border.color).value() : defaultBgColor;
|
|
data.borderWidth = border?.width ?? 0;
|
|
|
|
data.backgroundImage = background?.image ? dimensionContext.getResource(background.image).value() : undefined;
|
|
|
|
return data;
|
|
},
|
|
|
|
registerOptionsUI: (builder) => {
|
|
const category = ['Cloud'];
|
|
builder
|
|
.addCustomEditor({
|
|
category,
|
|
id: 'textSelector',
|
|
path: 'config.text',
|
|
name: 'Text',
|
|
editor: TextDimensionEditor,
|
|
})
|
|
.addCustomEditor({
|
|
category,
|
|
id: 'config.color',
|
|
path: 'config.color',
|
|
name: 'Text color',
|
|
editor: ColorDimensionEditor,
|
|
settings: {},
|
|
defaultValue: {},
|
|
})
|
|
.addRadio({
|
|
category,
|
|
path: 'config.align',
|
|
name: 'Align text',
|
|
settings: {
|
|
options: [
|
|
{ value: Align.Left, label: 'Left' },
|
|
{ value: Align.Center, label: 'Center' },
|
|
{ value: Align.Right, label: 'Right' },
|
|
],
|
|
},
|
|
defaultValue: Align.Left,
|
|
})
|
|
.addRadio({
|
|
category,
|
|
path: 'config.valign',
|
|
name: 'Vertical align',
|
|
settings: {
|
|
options: [
|
|
{ value: VAlign.Top, label: 'Top' },
|
|
{ value: VAlign.Middle, label: 'Middle' },
|
|
{ value: VAlign.Bottom, label: 'Bottom' },
|
|
],
|
|
},
|
|
defaultValue: VAlign.Middle,
|
|
})
|
|
.addNumberInput({
|
|
category,
|
|
path: 'config.size',
|
|
name: 'Text size',
|
|
settings: {
|
|
placeholder: 'Auto',
|
|
},
|
|
});
|
|
},
|
|
|
|
customConnectionAnchors: [
|
|
{ x: -0.58, y: 0.63 }, // Top Left
|
|
{ x: -0.22, y: 0.99 }, // Top Middle
|
|
{ x: 0.235, y: 0.75 }, // Top Right
|
|
|
|
{ x: 0.8, y: 0.6 }, // Right Top
|
|
{ x: 0.785, y: 0.06 }, // Right Middle
|
|
{ x: 0.91, y: -0.51 }, // Right Bottom
|
|
|
|
{ x: 0.62, y: -0.635 }, // Bottom Right
|
|
{ x: 0.05, y: -0.98 }, // Bottom Middle
|
|
{ x: -0.45, y: -0.635 }, // Bottom Left
|
|
|
|
{ x: -0.8, y: -0.58 }, // Left Bottom
|
|
{ x: -0.78, y: -0.06 }, // Left Middle
|
|
{ x: -0.9, y: 0.48 }, // Left Top
|
|
],
|
|
};
|
|
|
|
const getStyles = (theme: GrafanaTheme2, data: CanvasElementData | undefined) => {
|
|
const textTop = data?.valign === VAlign.Middle ? '50%' : data?.valign === VAlign.Top ? '10%' : '90%';
|
|
const textLeft = data?.align === Align.Center ? '50%' : data?.align === Align.Left ? '10%' : '90%';
|
|
const textTransform = `translate(${data?.align === Align.Center ? '-50%' : data?.align === Align.Left ? '10%' : '-90%'}, ${
|
|
data?.valign === VAlign.Middle ? '-50%' : data?.valign === VAlign.Top ? '10%' : '-90%'
|
|
})`;
|
|
|
|
return {
|
|
container: css({
|
|
height: '100%',
|
|
width: '100%',
|
|
}),
|
|
text: css({
|
|
position: 'absolute',
|
|
top: textTop,
|
|
left: textLeft,
|
|
transform: textTransform,
|
|
fontSize: `${data?.size}px`,
|
|
color: data?.color,
|
|
}),
|
|
elementBorder: css({
|
|
fill: 'none',
|
|
stroke: data?.borderColor ?? 'none',
|
|
strokeWidth: data?.borderWidth ?? 0,
|
|
strokeLinejoin: 'round',
|
|
}),
|
|
};
|
|
};
|