import { autorun } from "mobx"; import { observer } from "mobx-react"; import { FC, useEffect, useState } from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import CatalogMemberMixin from "../../../ModelMixins/CatalogMemberMixin"; import SearchableItemMixin from "../../../ModelMixins/SearchableItemMixin"; import ItemSearchProvider, { ItemSearchParameter, ItemSearchParameterType, ItemSearchResult } from "../../../Models/ItemSearchProviders/ItemSearchProvider"; import ViewState from "../../../ReactViewModels/ViewState"; import Box from "../../../Styled/Box"; import { Frame, Main } from "../ToolModal"; import BackButton from "./BackButton"; import ErrorComponent from "./ErrorComponent"; import Loading from "./Loading"; import SearchForm from "./SearchForm"; import SearchResults from "./SearchResults"; export interface PropsType extends WithTranslation { item: SearchableItemMixin.Instance; itemSearchProvider: ItemSearchProvider; viewState: ViewState; afterLoad?: () => void; } type State = | { is: "loadingParameters" } | { is: "error"; error: Error } | { is: "search" } | { is: "results"; results: ItemSearchResults }; export type ItemSearchQuery = Record< string, { type: ItemSearchParameterType; value: any } >; export type ItemSearchResults = ItemSearchResult[]; export type ActiveSelectionDisposer = () => void | undefined; const ItemSearchTool: FC = observer((props) => { const { viewState, item, itemSearchProvider, t } = props; const itemName = CatalogMemberMixin.isMixedInto(item) ? item.name : "Item"; const [state, setState] = useState({ is: "loadingParameters" }); const [parameters, setParameters] = useState([]); const [query, setQuery] = useState({}); useEffect( function loadParameters() { itemSearchProvider .initialize() .then(() => itemSearchProvider.describeParameters().then((parameters) => { setState({ is: "search" }); setParameters(parameters); setQuery({}); }) ) .catch((error: Error) => { console.warn(error); setState({ is: "error", error }); }) .finally(() => props.afterLoad?.()); }, /* eslint-disable-next-line react-hooks/exhaustive-deps */ [itemSearchProvider] ); useEffect( function closeSearchTool() { // Close item search tool if the parent item is disabled or removed from // the workbench const disposeListener = onItemDisabledOrRemovedFromWorkbench(item, () => viewState.closeTool() ); return disposeListener; }, /* eslint-disable-next-line react-hooks/exhaustive-deps */ [item] ); const setResults = (query: ItemSearchQuery, results: ItemSearchResults) => { setQuery(query); setState({ is: "results", results }); }; const searchAgain = () => setState({ is: "search" }); const loadIndexForParameter = itemSearchProvider.loadParameterHint?.bind(itemSearchProvider); return (
{state.is === "loadingParameters" && ( {t("itemSearchTool.loading")} )} {state.is === "error" && ( {t("itemSearchTool.loadError")} )} {state.is === "search" && parameters.length === 0 && ( {t("itemSearchTool.noParameters")} )} {state.is === "search" && parameters.length > 0 && ( )} {state.is === "results" && ( )} {state.is === "results" && ( {t("itemSearchTool.backBtnText")} )}
); }); /** * Callback when the given item is disabled or removed from the workbench. * * @param item The item to watch * @param callback The function to call when the event happens * @return A function to dispose the listener */ function onItemDisabledOrRemovedFromWorkbench( item: SearchableItemMixin.Instance, callback: () => void ): () => void { const disposer = autorun(() => { if (item.show === false || item.terria.workbench.contains(item) === false) callback(); }); return disposer; } export default withTranslation()(ItemSearchTool);