mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Co-authored-by: Drew Slobodnjak <drew08t@users.noreply.github.com>
(cherry picked from commit fc62f7ae23
)
Co-authored-by: Nathan Marrs <nathanielmarrs@gmail.com>
This commit is contained in:
parent
c0943153be
commit
f2f49abe03
210
public/app/features/canvas/elements/text.tsx
Normal file
210
public/app/features/canvas/elements/text.tsx
Normal file
@ -0,0 +1,210 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Input, usePanelContext, useStyles2 } from '@grafana/ui';
|
||||
import { DimensionContext } from 'app/features/dimensions/context';
|
||||
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
|
||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||
|
||||
import { CanvasElementItem, CanvasElementProps, defaultTextColor } from '../element';
|
||||
import { ElementState } from '../runtime/element';
|
||||
import { Align, TextConfig, TextData, VAlign } from '../types';
|
||||
|
||||
const TextDisplay = (props: CanvasElementProps<TextConfig, TextData>) => {
|
||||
const { data, isSelected } = props;
|
||||
const styles = useStyles2(getStyles(data));
|
||||
|
||||
const context = usePanelContext();
|
||||
const scene = context.instanceState?.scene;
|
||||
|
||||
const isEditMode = useObservable<boolean>(scene?.editModeEnabled ?? of(false));
|
||||
|
||||
if (isEditMode && isSelected) {
|
||||
return <TextEdit {...props} />;
|
||||
}
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<span className={styles.span}>{data?.text ? data.text : 'Double click to set text'}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TextEdit = (props: CanvasElementProps<TextConfig, TextData>) => {
|
||||
let { data, config } = props;
|
||||
const context = usePanelContext();
|
||||
let panelData: DataFrame[];
|
||||
panelData = context.instanceState?.scene?.data.series;
|
||||
|
||||
const onTextChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const { value: textValue } = event.currentTarget;
|
||||
saveText(textValue);
|
||||
};
|
||||
|
||||
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
const scene = context.instanceState?.scene;
|
||||
if (scene) {
|
||||
scene.editModeEnabled.next(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveText = useCallback(
|
||||
(textValue: string) => {
|
||||
let selectedElement: ElementState;
|
||||
selectedElement = context.instanceState?.selected[0];
|
||||
if (selectedElement) {
|
||||
const options = selectedElement.options;
|
||||
selectedElement.onChange({
|
||||
...options,
|
||||
config: {
|
||||
...options.config,
|
||||
text: { ...selectedElement.options.config.text, fixed: textValue },
|
||||
},
|
||||
});
|
||||
|
||||
// Force a re-render (update scene data after config update)
|
||||
const scene = context.instanceState?.scene;
|
||||
if (scene) {
|
||||
scene.updateData(scene.data);
|
||||
}
|
||||
}
|
||||
},
|
||||
[context.instanceState?.scene, context.instanceState?.selected]
|
||||
);
|
||||
|
||||
const styles = useStyles2(getStyles(data));
|
||||
return (
|
||||
<div className={styles.inlineEditorContainer}>
|
||||
{panelData && <Input value={config.text?.fixed ?? ''} onChange={onTextChange} onKeyDown={onKeyDown} autoFocus />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (data: TextData | undefined) => (theme: GrafanaTheme2) => ({
|
||||
container: css`
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: table;
|
||||
`,
|
||||
inlineEditorContainer: css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
`,
|
||||
span: css`
|
||||
display: table-cell;
|
||||
vertical-align: ${data?.valign};
|
||||
text-align: ${data?.align};
|
||||
font-size: ${data?.size}px;
|
||||
color: ${data?.color};
|
||||
`,
|
||||
});
|
||||
|
||||
export const textItem: CanvasElementItem<TextConfig, TextData> = {
|
||||
id: 'text',
|
||||
name: 'Text',
|
||||
description: 'Display text',
|
||||
|
||||
display: TextDisplay,
|
||||
|
||||
hasEditMode: true,
|
||||
|
||||
defaultSize: {
|
||||
width: 100,
|
||||
height: 50,
|
||||
},
|
||||
|
||||
getNewOptions: (options) => ({
|
||||
...options,
|
||||
config: {
|
||||
align: Align.Center,
|
||||
valign: VAlign.Middle,
|
||||
color: {
|
||||
fixed: defaultTextColor,
|
||||
},
|
||||
size: 16,
|
||||
},
|
||||
placement: {
|
||||
top: 100,
|
||||
left: 100,
|
||||
},
|
||||
}),
|
||||
|
||||
prepareData: (ctx: DimensionContext, cfg: TextConfig) => {
|
||||
const data: TextData = {
|
||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
||||
align: cfg.align ?? Align.Center,
|
||||
valign: cfg.valign ?? VAlign.Middle,
|
||||
size: cfg.size,
|
||||
};
|
||||
|
||||
if (cfg.color) {
|
||||
data.color = ctx.getColor(cfg.color).value();
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
registerOptionsUI: (builder) => {
|
||||
const category = ['Text'];
|
||||
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',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
@ -8,6 +8,7 @@ import { droneTopItem } from './elements/droneTop';
|
||||
import { iconItem } from './elements/icon';
|
||||
import { metricValueItem } from './elements/metricValue';
|
||||
import { rectangleItem } from './elements/rectangle';
|
||||
import { textItem } from './elements/text';
|
||||
import { windTurbineItem } from './elements/windTurbine';
|
||||
|
||||
export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
|
||||
@ -19,6 +20,7 @@ export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
|
||||
|
||||
export const defaultElementItems = [
|
||||
metricValueItem, // default for now
|
||||
textItem,
|
||||
rectangleItem,
|
||||
iconItem,
|
||||
];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, useCallback, useState } from 'react';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import {
|
||||
FieldNamePickerConfigSettings,
|
||||
@ -30,9 +30,6 @@ export const TextDimensionEditor: FC<StandardEditorProps<TextDimensionConfig, Te
|
||||
const { value, context, onChange } = props;
|
||||
const labelWidth = 9;
|
||||
|
||||
// force re-render on clear fixed text
|
||||
const [refresh, setRefresh] = useState(0);
|
||||
|
||||
const onModeChange = useCallback(
|
||||
(mode) => {
|
||||
onChange({
|
||||
@ -65,11 +62,9 @@ export const TextDimensionEditor: FC<StandardEditorProps<TextDimensionConfig, Te
|
||||
|
||||
const onClearFixed = () => {
|
||||
onFixedChange('');
|
||||
setRefresh(refresh + 1);
|
||||
};
|
||||
|
||||
const mode = value?.mode ?? TextDimensionMode.Fixed;
|
||||
|
||||
return (
|
||||
<>
|
||||
<InlineFieldRow>
|
||||
@ -90,7 +85,7 @@ export const TextDimensionEditor: FC<StandardEditorProps<TextDimensionConfig, Te
|
||||
</InlineFieldRow>
|
||||
)}
|
||||
{mode === TextDimensionMode.Fixed && (
|
||||
<InlineFieldRow key={refresh}>
|
||||
<InlineFieldRow key={value?.fixed}>
|
||||
<InlineField label={'Value'} labelWidth={labelWidth} grow={true}>
|
||||
<StringValueEditor
|
||||
context={context}
|
||||
|
Loading…
Reference in New Issue
Block a user