ResourcePickerPopover.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { css } from '@emotion/css';
  2. import { useDialog } from '@react-aria/dialog';
  3. import { FocusScope } from '@react-aria/focus';
  4. import { useOverlay } from '@react-aria/overlays';
  5. import React, { createRef, useState } from 'react';
  6. import { GrafanaTheme2 } from '@grafana/data';
  7. import { getBackendSrv } from '@grafana/runtime';
  8. import { Button, ButtonGroup, useStyles2 } from '@grafana/ui';
  9. import { config } from 'app/core/config';
  10. import { MediaType, PickerTabType, ResourceFolderName } from '../types';
  11. import { FileUploader } from './FileUploader';
  12. import { FolderPickerTab } from './FolderPickerTab';
  13. import { URLPickerTab } from './URLPickerTab';
  14. interface Props {
  15. value?: string; //img/icons/unicons/0-plus.svg
  16. onChange: (value?: string) => void;
  17. mediaType: MediaType;
  18. folderName: ResourceFolderName;
  19. }
  20. interface ErrorResponse {
  21. message: string;
  22. }
  23. export const ResourcePickerPopover = (props: Props) => {
  24. const { value, onChange, mediaType, folderName } = props;
  25. const styles = useStyles2(getStyles);
  26. const onClose = () => {
  27. onChange(value);
  28. };
  29. const ref = createRef<HTMLElement>();
  30. const { dialogProps } = useDialog({}, ref);
  31. const { overlayProps } = useOverlay({ onClose, isDismissable: true, isOpen: true }, ref);
  32. const [newValue, setNewValue] = useState<string>(value ?? '');
  33. const [activePicker, setActivePicker] = useState<PickerTabType>(PickerTabType.Folder);
  34. const [formData, setFormData] = useState<FormData>(new FormData());
  35. const [upload, setUpload] = useState<boolean>(false);
  36. const [error, setError] = useState<ErrorResponse>({ message: '' });
  37. const getTabClassName = (tabName: PickerTabType) => {
  38. return `${styles.resourcePickerPopoverTab} ${activePicker === tabName && styles.resourcePickerPopoverActiveTab}`;
  39. };
  40. const renderFolderPicker = () => (
  41. <FolderPickerTab
  42. value={value}
  43. mediaType={mediaType}
  44. folderName={folderName}
  45. newValue={newValue}
  46. setNewValue={setNewValue}
  47. />
  48. );
  49. const renderURLPicker = () => <URLPickerTab newValue={newValue} setNewValue={setNewValue} mediaType={mediaType} />;
  50. const renderUploader = () => (
  51. <FileUploader
  52. mediaType={mediaType}
  53. setFormData={setFormData}
  54. setUpload={setUpload}
  55. newValue={newValue}
  56. error={error}
  57. />
  58. );
  59. const renderPicker = () => {
  60. switch (activePicker) {
  61. case PickerTabType.Folder:
  62. return renderFolderPicker();
  63. case PickerTabType.URL:
  64. return renderURLPicker();
  65. case PickerTabType.Upload:
  66. return renderUploader();
  67. default:
  68. return renderFolderPicker();
  69. }
  70. };
  71. return (
  72. <FocusScope contain autoFocus restoreFocus>
  73. <section ref={ref} {...overlayProps} {...dialogProps}>
  74. <div className={styles.resourcePickerPopover}>
  75. <div className={styles.resourcePickerPopoverTabs}>
  76. <button
  77. className={getTabClassName(PickerTabType.Folder)}
  78. onClick={() => setActivePicker(PickerTabType.Folder)}
  79. >
  80. Folder
  81. </button>
  82. <button className={getTabClassName(PickerTabType.URL)} onClick={() => setActivePicker(PickerTabType.URL)}>
  83. URL
  84. </button>
  85. {config.featureToggles['storageLocalUpload'] ? (
  86. <button
  87. className={getTabClassName(PickerTabType.Upload)}
  88. onClick={() => setActivePicker(PickerTabType.Upload)}
  89. >
  90. Upload
  91. </button>
  92. ) : (
  93. ''
  94. )}
  95. </div>
  96. <div className={styles.resourcePickerPopoverContent}>
  97. {renderPicker()}
  98. <ButtonGroup className={styles.buttonGroup}>
  99. <Button className={styles.button} variant={'secondary'} onClick={() => onClose()}>
  100. Cancel
  101. </Button>
  102. <Button
  103. className={styles.button}
  104. variant={newValue && newValue !== value ? 'primary' : 'secondary'}
  105. onClick={() => {
  106. if (upload) {
  107. fetch('/api/storage/upload', {
  108. method: 'POST',
  109. body: formData,
  110. })
  111. .then((res) => {
  112. if (res.status >= 400) {
  113. res.json().then((data) => setError(data));
  114. return;
  115. } else {
  116. return res.json();
  117. }
  118. })
  119. .then((data) => {
  120. getBackendSrv()
  121. .get(`api/storage/read/${data.path}`)
  122. .then(() => setNewValue(`${config.appUrl}api/storage/read/${data.path}`))
  123. .then(() => onChange(`${config.appUrl}api/storage/read/${data.path}`));
  124. })
  125. .catch((err) => console.error(err));
  126. } else {
  127. onChange(newValue);
  128. }
  129. }}
  130. >
  131. Select
  132. </Button>
  133. </ButtonGroup>
  134. </div>
  135. </div>
  136. </section>
  137. </FocusScope>
  138. );
  139. };
  140. const getStyles = (theme: GrafanaTheme2) => ({
  141. resourcePickerPopover: css`
  142. border-radius: ${theme.shape.borderRadius()};
  143. box-shadow: ${theme.shadows.z3};
  144. background: ${theme.colors.background.primary};
  145. border: 1px solid ${theme.colors.border.medium};
  146. `,
  147. resourcePickerPopoverTab: css`
  148. width: 50%;
  149. text-align: center;
  150. padding: ${theme.spacing(1, 0)};
  151. background: ${theme.colors.background.secondary};
  152. color: ${theme.colors.text.secondary};
  153. font-size: ${theme.typography.bodySmall.fontSize};
  154. cursor: pointer;
  155. border: none;
  156. &:focus:not(:focus-visible) {
  157. outline: none;
  158. box-shadow: none;
  159. }
  160. :focus-visible {
  161. position: relative;
  162. }
  163. `,
  164. resourcePickerPopoverActiveTab: css`
  165. color: ${theme.colors.text.primary};
  166. font-weight: ${theme.typography.fontWeightMedium};
  167. background: ${theme.colors.background.primary};
  168. `,
  169. resourcePickerPopoverContent: css`
  170. width: 315px;
  171. font-size: ${theme.typography.bodySmall.fontSize};
  172. min-height: 184px;
  173. padding: ${theme.spacing(1)};
  174. display: flex;
  175. flex-direction: column;
  176. `,
  177. resourcePickerPopoverTabs: css`
  178. display: flex;
  179. width: 100%;
  180. border-radius: ${theme.shape.borderRadius()} ${theme.shape.borderRadius()} 0 0;
  181. `,
  182. buttonGroup: css`
  183. align-self: center;
  184. flex-direction: row;
  185. `,
  186. button: css`
  187. margin: 12px 20px 5px;
  188. `,
  189. });