FolderPickerTab.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { css } from '@emotion/css';
  2. import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
  3. import { GrafanaTheme2, SelectableValue } from '@grafana/data';
  4. import { Field, FilterInput, Select, useStyles2 } from '@grafana/ui';
  5. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  6. import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
  7. import { MediaType, ResourceFolderName } from '../types';
  8. import { ResourceCards } from './ResourceCards';
  9. const getFolders = (mediaType: MediaType) => {
  10. if (mediaType === MediaType.Icon) {
  11. return [ResourceFolderName.Icon, ResourceFolderName.IOT, ResourceFolderName.Marker];
  12. } else {
  13. return [ResourceFolderName.BG];
  14. }
  15. };
  16. const getFolderIfExists = (folders: Array<SelectableValue<string>>, path: string) => {
  17. return folders.find((folder) => path.startsWith(folder.value!)) ?? folders[0];
  18. };
  19. export interface ResourceItem {
  20. label: string;
  21. value: string; // includes folder
  22. search: string;
  23. imgUrl: string;
  24. }
  25. interface Props {
  26. value?: string;
  27. mediaType: MediaType;
  28. folderName: ResourceFolderName;
  29. newValue: string;
  30. setNewValue: Dispatch<SetStateAction<string>>;
  31. }
  32. export const FolderPickerTab = (props: Props) => {
  33. const { value, mediaType, folderName, newValue, setNewValue } = props;
  34. const styles = useStyles2(getStyles);
  35. const folders = getFolders(mediaType).map((v) => ({
  36. label: v,
  37. value: v,
  38. }));
  39. const [searchQuery, setSearchQuery] = useState<string>();
  40. const [currentFolder, setCurrentFolder] = useState<SelectableValue<string>>(
  41. getFolderIfExists(folders, value?.length ? value : folderName)
  42. );
  43. const [directoryIndex, setDirectoryIndex] = useState<ResourceItem[]>([]);
  44. const [filteredIndex, setFilteredIndex] = useState<ResourceItem[]>([]);
  45. const onChangeSearch = (query: string) => {
  46. if (query) {
  47. query = query.toLowerCase();
  48. setFilteredIndex(directoryIndex.filter((card) => card.search.includes(query)));
  49. } else {
  50. setFilteredIndex(directoryIndex);
  51. }
  52. };
  53. useEffect(() => {
  54. // we don't want to load everything before picking a folder
  55. const folder = currentFolder?.value;
  56. if (folder) {
  57. const filter =
  58. mediaType === MediaType.Icon
  59. ? (item: FileElement) => item.name.endsWith('.svg')
  60. : (item: FileElement) => item.name.endsWith('.png') || item.name.endsWith('.gif');
  61. getDatasourceSrv()
  62. .get('-- Grafana --')
  63. .then((ds) => {
  64. (ds as GrafanaDatasource).listFiles(folder).subscribe({
  65. next: (frame) => {
  66. const cards: ResourceItem[] = [];
  67. frame.forEach((item) => {
  68. if (filter(item)) {
  69. const idx = item.name.lastIndexOf('.');
  70. cards.push({
  71. value: `${folder}/${item.name}`,
  72. label: item.name,
  73. search: (idx ? item.name.substring(0, idx) : item.name).toLowerCase(),
  74. imgUrl: `public/${folder}/${item.name}`,
  75. });
  76. }
  77. });
  78. setDirectoryIndex(cards);
  79. setFilteredIndex(cards);
  80. },
  81. });
  82. });
  83. }
  84. }, [mediaType, currentFolder]);
  85. return (
  86. <>
  87. <Field>
  88. <Select options={folders} onChange={setCurrentFolder} value={currentFolder} />
  89. </Field>
  90. <Field>
  91. <FilterInput
  92. value={searchQuery ?? ''}
  93. placeholder="Search"
  94. onChange={(v) => {
  95. onChangeSearch(v);
  96. setSearchQuery(v);
  97. }}
  98. />
  99. </Field>
  100. {filteredIndex && (
  101. <div className={styles.cardsWrapper}>
  102. <ResourceCards cards={filteredIndex} onChange={(v) => setNewValue(v)} value={newValue} />
  103. </div>
  104. )}
  105. </>
  106. );
  107. };
  108. const getStyles = (theme: GrafanaTheme2) => ({
  109. cardsWrapper: css`
  110. height: 30vh;
  111. min-height: 50px;
  112. margin-top: 5px;
  113. max-width: 680px;
  114. `,
  115. });