VisualizationSelectPane.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import { css } from '@emotion/css';
  2. import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
  3. import { useDispatch, useSelector } from 'react-redux';
  4. import { useLocalStorage } from 'react-use';
  5. import { GrafanaTheme, PanelData, SelectableValue } from '@grafana/data';
  6. import { selectors } from '@grafana/e2e-selectors';
  7. import { Button, CustomScrollbar, FilterInput, RadioButtonGroup, useStyles } from '@grafana/ui';
  8. import { Field } from '@grafana/ui/src/components/Forms/Field';
  9. import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants';
  10. import { PanelLibraryOptionsGroup } from 'app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup';
  11. import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions';
  12. import { VizTypeChangeDetails } from 'app/features/panel/components/VizTypePicker/types';
  13. import { VizTypePicker } from '../../../panel/components/VizTypePicker/VizTypePicker';
  14. import { changePanelPlugin } from '../../../panel/state/actions';
  15. import { PanelModel } from '../../state/PanelModel';
  16. import { getPanelPluginWithFallback } from '../../state/selectors';
  17. import { toggleVizPicker } from './state/reducers';
  18. import { VisualizationSelectPaneTab } from './types';
  19. interface Props {
  20. panel: PanelModel;
  21. data?: PanelData;
  22. }
  23. export const VisualizationSelectPane: FC<Props> = ({ panel, data }) => {
  24. const plugin = useSelector(getPanelPluginWithFallback(panel.type));
  25. const [searchQuery, setSearchQuery] = useState('');
  26. const [listMode, setListMode] = useLocalStorage(
  27. LS_VISUALIZATION_SELECT_TAB_KEY,
  28. VisualizationSelectPaneTab.Visualizations
  29. );
  30. const dispatch = useDispatch();
  31. const styles = useStyles(getStyles);
  32. const searchRef = useRef<HTMLInputElement | null>(null);
  33. const onVizChange = useCallback(
  34. (pluginChange: VizTypeChangeDetails) => {
  35. dispatch(changePanelPlugin({ panel: panel, ...pluginChange }));
  36. // close viz picker unless a mod key is pressed while clicking
  37. if (!pluginChange.withModKey) {
  38. dispatch(toggleVizPicker(false));
  39. }
  40. },
  41. [dispatch, panel]
  42. );
  43. // Give Search input focus when using radio button switch list mode
  44. useEffect(() => {
  45. if (searchRef.current) {
  46. searchRef.current.focus();
  47. }
  48. }, [listMode]);
  49. const onCloseVizPicker = () => {
  50. dispatch(toggleVizPicker(false));
  51. };
  52. if (!plugin) {
  53. return null;
  54. }
  55. const radioOptions: Array<SelectableValue<VisualizationSelectPaneTab>> = [
  56. { label: 'Visualizations', value: VisualizationSelectPaneTab.Visualizations },
  57. { label: 'Suggestions', value: VisualizationSelectPaneTab.Suggestions },
  58. {
  59. label: 'Library panels',
  60. value: VisualizationSelectPaneTab.LibraryPanels,
  61. description: 'Reusable panels you can share between multiple dashboards.',
  62. },
  63. ];
  64. return (
  65. <div className={styles.openWrapper}>
  66. <div className={styles.formBox}>
  67. <div className={styles.searchRow}>
  68. <FilterInput
  69. value={searchQuery}
  70. onChange={setSearchQuery}
  71. ref={searchRef}
  72. autoFocus={true}
  73. placeholder="Search for..."
  74. />
  75. <Button
  76. title="Close"
  77. variant="secondary"
  78. icon="angle-up"
  79. className={styles.closeButton}
  80. aria-label={selectors.components.PanelEditor.toggleVizPicker}
  81. onClick={onCloseVizPicker}
  82. />
  83. </div>
  84. <Field className={styles.customFieldMargin}>
  85. <RadioButtonGroup options={radioOptions} value={listMode} onChange={setListMode} fullWidth />
  86. </Field>
  87. </div>
  88. <div className={styles.scrollWrapper}>
  89. <CustomScrollbar autoHeightMin="100%">
  90. <div className={styles.scrollContent}>
  91. {listMode === VisualizationSelectPaneTab.Visualizations && (
  92. <VizTypePicker
  93. current={plugin.meta}
  94. onChange={onVizChange}
  95. searchQuery={searchQuery}
  96. data={data}
  97. onClose={() => {}}
  98. />
  99. )}
  100. {listMode === VisualizationSelectPaneTab.Suggestions && (
  101. <VisualizationSuggestions
  102. current={plugin.meta}
  103. onChange={onVizChange}
  104. searchQuery={searchQuery}
  105. panel={panel}
  106. data={data}
  107. onClose={() => {}}
  108. />
  109. )}
  110. {listMode === VisualizationSelectPaneTab.LibraryPanels && (
  111. <PanelLibraryOptionsGroup searchQuery={searchQuery} panel={panel} key="Panel Library" />
  112. )}
  113. </div>
  114. </CustomScrollbar>
  115. </div>
  116. </div>
  117. );
  118. };
  119. VisualizationSelectPane.displayName = 'VisualizationSelectPane';
  120. const getStyles = (theme: GrafanaTheme) => {
  121. return {
  122. icon: css`
  123. color: ${theme.palette.gray33};
  124. `,
  125. wrapper: css`
  126. display: flex;
  127. flex-direction: column;
  128. flex: 1 1 0;
  129. height: 100%;
  130. `,
  131. vizButton: css`
  132. text-align: left;
  133. `,
  134. scrollWrapper: css`
  135. flex-grow: 1;
  136. min-height: 0;
  137. `,
  138. scrollContent: css`
  139. padding: ${theme.spacing.sm};
  140. `,
  141. openWrapper: css`
  142. display: flex;
  143. flex-direction: column;
  144. flex: 1 1 100%;
  145. height: 100%;
  146. background: ${theme.colors.bg1};
  147. border: 1px solid ${theme.colors.border1};
  148. `,
  149. searchRow: css`
  150. display: flex;
  151. margin-bottom: ${theme.spacing.sm};
  152. `,
  153. closeButton: css`
  154. margin-left: ${theme.spacing.sm};
  155. `,
  156. customFieldMargin: css`
  157. margin-bottom: ${theme.spacing.sm};
  158. `,
  159. formBox: css`
  160. padding: ${theme.spacing.sm};
  161. padding-bottom: 0;
  162. `,
  163. };
  164. };