diff --git a/packages/base/src/stacBrowser/components/StacPanel.tsx b/packages/base/src/stacBrowser/components/StacPanel.tsx index db89cb31a..21832cb20 100644 --- a/packages/base/src/stacBrowser/components/StacPanel.tsx +++ b/packages/base/src/stacBrowser/components/StacPanel.tsx @@ -1,12 +1,16 @@ import { IJupyterGISModel } from '@jupytergis/schema'; +import { Dialog } from '@jupyterlab/apputils'; +import { Widget } from '@lumino/widgets'; import React from 'react'; +import { Button } from '@/src/shared/components/Button'; import { Tabs, TabsContent, TabsList, TabsTrigger, } from '@/src/shared/components/Tabs'; +import useStacIndex from '@/src/stacBrowser/hooks/useStacIndex'; import useStacSearch from '@/src/stacBrowser/hooks/useStacSearch'; import StacPanelFilters from './StacPanelFilters'; import StacPanelResults from './StacPanelResults'; @@ -15,6 +19,11 @@ interface IStacViewProps { model?: IJupyterGISModel; } const StacPanel = ({ model }: IStacViewProps) => { + const inputRef = React.useRef(null); + const [selectedCatalog, setSelectedCatalog] = React.useState(''); + + const { catalogs, isLoading: isIndexLoading, error } = useStacIndex(model); + const { filterState, filterSetters, @@ -38,42 +47,151 @@ const StacPanel = ({ model }: IStacViewProps) => { return null; } + if (isIndexLoading) { + return ( +
+

Loading...

+
+ ); + } + + if (error) { + return ( +
+

Error loading catalogs.

+
+ ); + } + + const handleOpenDialog = async () => { + const widget = new URLInputWidget(catalogs); + inputRef.current = widget.getInput(); + + const dialog = new Dialog({ + title: 'Add Catalog', + body: widget, + buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Select' })], + }); + + const result = await dialog.launch(); + if (result.button.accept && inputRef.current) { + const url = inputRef.current.value; + //console.log('Catalog URL added:', url); + setSelectedCatalog(url); + } + }; + return ( - - - - Filters - - {`Results (${totalResults})`} - - - - - - - - +
+ + + {selectedCatalog && ( +
+ Selected Catalog: {selectedCatalog} +
+ )} + + + + + Filters + + {`Results (${totalResults})`} + + + + + + + + +
); }; export default StacPanel; + +class URLInputWidget extends Widget { + private input: HTMLInputElement; + + constructor(catalogs: any[] = []) { + const node = document.createElement('div'); + node.style.padding = '10px'; + + // First section: Manual URL entry + const label = document.createElement('label'); + label.textContent = 'Enter Catalog URL:'; + label.style.display = 'block'; + label.style.marginBottom = '8px'; + label.style.fontWeight = 'bold'; + + const input = document.createElement('input'); + input.type = 'url'; + input.placeholder = 'https://example.com'; + input.className = 'jgis-stac-url-input'; + + // Second section: Select from catalog dropdown + const catalogLabel = document.createElement('label'); + catalogLabel.textContent = 'Or select a catalog:'; + catalogLabel.style.display = 'block'; + catalogLabel.style.marginBottom = '8px'; + catalogLabel.style.fontWeight = 'bold'; + + const dropdown = document.createElement('select'); + dropdown.style.width = '100%'; + dropdown.style.padding = '8px'; + dropdown.style.boxSizing = 'border-box'; + dropdown.style.border = '1px solid #ccc'; + dropdown.style.borderRadius = '4px'; + dropdown.style.marginBottom = '10px'; + + const defaultOption = document.createElement('option'); + defaultOption.value = ''; + defaultOption.textContent = 'Select a catalog...'; + dropdown.appendChild(defaultOption); + + catalogs.forEach((catalog: any) => { + const option = document.createElement('option'); + option.value = catalog.url; + option.textContent = catalog.title; + dropdown.appendChild(option); + }); + + dropdown.addEventListener('change', e => { + const selectedUrl = (e.target as HTMLSelectElement).value; + input.value = selectedUrl; + }); + + node.appendChild(label); + node.appendChild(input); + node.appendChild(catalogLabel); + node.appendChild(dropdown); + + super({ node }); + this.input = input; + } + + getInput(): HTMLInputElement { + return this.input; + } +} diff --git a/packages/base/src/stacBrowser/hooks/useStacIndex.ts b/packages/base/src/stacBrowser/hooks/useStacIndex.ts new file mode 100644 index 000000000..ed2ae8758 --- /dev/null +++ b/packages/base/src/stacBrowser/hooks/useStacIndex.ts @@ -0,0 +1,77 @@ +import { IJupyterGISModel } from '@jupytergis/schema'; +import { useEffect, useState } from 'react'; + +import { fetchWithProxies } from '@/src/tools'; + +export interface IStacIndexCatalog { + id: number; + url: string; + slug: string; + title: string; + summary: string; + access: string; + created: string; + updated: string; + isPrivate: boolean; + isApi: boolean; + accessInfo: string | null; +} + +export type IStacIndexCatalogs = IStacIndexCatalog[]; + +interface IUseStacIndexReturn { + catalogs: IStacIndexCatalogs; + isLoading: boolean; + error: string | null; +} + +/** + * Custom hook for fetching STAC catalogs from stacindex.org API + * @param model - JupyterGIS model for proxy configuration + * @returns Object containing catalogs list, loading state, and error + */ +const useStacIndex = ( + model: IJupyterGISModel | undefined, +): IUseStacIndexReturn => { + const [isLoading, setIsLoading] = useState(true); + const [catalogs, setCatalogs] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchCatalogs = async () => { + if (!model) { + setIsLoading(false); + return; + } + + try { + setIsLoading(true); + setError(null); + + const data = (await fetchWithProxies( + 'https://stacindex.org/api/catalogs', + model, + async response => await response.json(), + undefined, + 'internal', + )) as IStacIndexCatalogs; + + setCatalogs(data || []); + } catch (error) { + console.error('Error fetching STAC catalogs:', error); + setError( + error instanceof Error ? error.message : 'Failed to fetch catalogs', + ); + setCatalogs([]); + } finally { + setIsLoading(false); + } + }; + + fetchCatalogs(); + }, [model]); + + return { catalogs, isLoading, error }; +}; + +export default useStacIndex; diff --git a/packages/base/style/tabPanel.css b/packages/base/style/tabPanel.css index 5be5249f0..19f2c8fe5 100644 --- a/packages/base/style/tabPanel.css +++ b/packages/base/style/tabPanel.css @@ -89,3 +89,28 @@ transparent 20% ) !important; } + +.jgis-stac-catalog-input-label { + font-weight: normal; + font-size: var(--jp-ui-font-size1); + color: var(--jp-ui-font-color1); +} + +.jgis-stac-catalog-input { + width: 100%; + padding: 8px; + box-sizing: border-box; + border: 1px solid var(--jp-border-color1); + border-radius: 4px; + background-color: var(--jp-input-background); + color: var(--jp-ui-font-color0); +} + +.jgis-stac-url-input { + width: 100%; + padding: 8px; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 20px; +} diff --git a/tsconfigbase.json b/tsconfigbase.json index 604b2ff68..e056fcdfb 100644 --- a/tsconfigbase.json +++ b/tsconfigbase.json @@ -20,7 +20,5 @@ "types": ["node", "webpack-env"], "skipLibCheck": true, "lib": ["ES2019", "WebWorker", "DOM"] - }, - "esModuleInterop": true, - "resolveJsonModule": true + } }