mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
|
import React, { useCallback, useMemo, useState } from 'react';
|
||
|
import { useAsync } from 'react-use';
|
||
|
|
||
|
import { LoadingBar } from '@grafana/ui';
|
||
|
import { listFolders, PAGE_SIZE } from 'app/features/browse-dashboards/api/services';
|
||
|
import { createFlatTree } from 'app/features/browse-dashboards/state';
|
||
|
import { DashboardViewItemCollection } from 'app/features/browse-dashboards/types';
|
||
|
import { DashboardViewItem } from 'app/features/search/types';
|
||
|
|
||
|
import { NestedFolderList } from './NestedFolderList';
|
||
|
import { FolderChange, FolderUID } from './types';
|
||
|
|
||
|
async function fetchRootFolders() {
|
||
|
return await listFolders(undefined, undefined, 1, PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
interface NestedFolderPickerProps {
|
||
|
value?: FolderUID | undefined;
|
||
|
// TODO: think properly (and pragmatically) about how to communicate moving to general folder,
|
||
|
// vs removing selection (if possible?)
|
||
|
onChange?: (folderUID: FolderChange) => void;
|
||
|
}
|
||
|
|
||
|
export function NestedFolderPicker({ value, onChange }: NestedFolderPickerProps) {
|
||
|
// const [search, setSearch] = useState('');
|
||
|
|
||
|
const [folderOpenState, setFolderOpenState] = useState<Record<string, boolean>>({});
|
||
|
const [childrenForUID, setChildrenForUID] = useState<Record<string, DashboardViewItem[]>>({});
|
||
|
const state = useAsync(fetchRootFolders);
|
||
|
|
||
|
const handleFolderClick = useCallback(async (uid: string, newOpenState: boolean) => {
|
||
|
setFolderOpenState((old) => ({ ...old, [uid]: newOpenState }));
|
||
|
|
||
|
if (newOpenState) {
|
||
|
const folders = await listFolders(uid, undefined, 1, PAGE_SIZE);
|
||
|
setChildrenForUID((old) => ({ ...old, [uid]: folders }));
|
||
|
}
|
||
|
}, []);
|
||
|
|
||
|
const flatTree = useMemo(() => {
|
||
|
const rootCollection: DashboardViewItemCollection = {
|
||
|
isFullyLoaded: !state.loading,
|
||
|
lastKindHasMoreItems: false,
|
||
|
lastFetchedKind: 'folder',
|
||
|
lastFetchedPage: 1,
|
||
|
items: state.value ?? [],
|
||
|
};
|
||
|
|
||
|
const childrenCollections: Record<string, DashboardViewItemCollection | undefined> = {};
|
||
|
|
||
|
for (const parentUID in childrenForUID) {
|
||
|
const children = childrenForUID[parentUID];
|
||
|
childrenCollections[parentUID] = {
|
||
|
isFullyLoaded: !!children,
|
||
|
lastKindHasMoreItems: false,
|
||
|
lastFetchedKind: 'folder',
|
||
|
lastFetchedPage: 1,
|
||
|
items: children,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const result = createFlatTree(undefined, rootCollection, childrenCollections, folderOpenState, 0, false);
|
||
|
result.unshift({
|
||
|
isOpen: false,
|
||
|
level: 0,
|
||
|
item: {
|
||
|
kind: 'folder',
|
||
|
title: 'Dashboards',
|
||
|
uid: '',
|
||
|
},
|
||
|
});
|
||
|
|
||
|
return result;
|
||
|
}, [childrenForUID, folderOpenState, state.loading, state.value]);
|
||
|
|
||
|
const handleSelectionChange = useCallback(
|
||
|
(event: React.FormEvent<HTMLInputElement>, item: DashboardViewItem) => {
|
||
|
console.log('selected', item);
|
||
|
if (onChange) {
|
||
|
onChange({ title: item.title, uid: item.uid });
|
||
|
}
|
||
|
},
|
||
|
[onChange]
|
||
|
);
|
||
|
|
||
|
return (
|
||
|
<fieldset>
|
||
|
{/* <FilterInput placeholder="Search folder" value={search} escapeRegex={false} onChange={(val) => setSearch(val)} /> */}
|
||
|
|
||
|
{state.loading && <LoadingBar width={300} />}
|
||
|
{state.error && <p>{state.error.message}</p>}
|
||
|
{state.value && (
|
||
|
<NestedFolderList
|
||
|
items={flatTree}
|
||
|
selectedFolder={value}
|
||
|
onFolderClick={handleFolderClick}
|
||
|
onSelectionChange={handleSelectionChange}
|
||
|
/>
|
||
|
)}
|
||
|
</fieldset>
|
||
|
);
|
||
|
}
|