@jupytergis/base 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/BaseCommandIDs.d.ts +1 -0
- package/lib/commands/BaseCommandIDs.js +1 -0
- package/lib/commands/index.js +52 -0
- package/lib/constants.js +3 -0
- package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +0 -6
- package/lib/dialogs/symbology/hooks/useGetBandInfo.js +2 -2
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +4 -4
- package/lib/formbuilder/objectform/StoryEditorForm.d.ts +3 -2
- package/lib/formbuilder/objectform/StoryEditorForm.js +7 -1
- package/lib/mainview/mainView.d.ts +18 -0
- package/lib/mainview/mainView.js +243 -18
- package/lib/panelview/{components/filter-panel → filter-panel}/Filter.js +1 -1
- package/lib/panelview/leftpanel.js +4 -4
- package/lib/panelview/rightpanel.d.ts +2 -0
- package/lib/panelview/rightpanel.js +21 -14
- package/lib/panelview/{components/story-maps → story-maps}/PreviewModeSwitch.js +3 -2
- package/lib/panelview/story-maps/StoryEditorPanel.d.ts +9 -0
- package/lib/panelview/story-maps/StoryEditorPanel.js +34 -0
- package/lib/panelview/{components/story-maps → story-maps}/StoryNavBar.d.ts +2 -1
- package/lib/panelview/{components/story-maps → story-maps}/StoryNavBar.js +3 -3
- package/lib/panelview/story-maps/StoryViewerPanel.d.ts +13 -0
- package/lib/panelview/{components/story-maps → story-maps}/StoryViewerPanel.js +37 -24
- package/lib/panelview/story-maps/components/StoryContentSection.d.ts +6 -0
- package/lib/panelview/story-maps/components/StoryContentSection.js +10 -0
- package/lib/panelview/story-maps/components/StoryImageSection.d.ts +15 -0
- package/lib/panelview/story-maps/components/StoryImageSection.js +13 -0
- package/lib/panelview/story-maps/components/StorySubtitleSection.d.ts +11 -0
- package/lib/panelview/story-maps/components/StorySubtitleSection.js +9 -0
- package/lib/panelview/story-maps/components/StoryTitleSection.d.ts +12 -0
- package/lib/panelview/story-maps/components/StoryTitleSection.js +8 -0
- package/lib/shared/components/Combobox.d.ts +21 -0
- package/lib/shared/components/Combobox.js +32 -0
- package/lib/shared/components/Command.js +10 -10
- package/lib/shared/components/Input.d.ts +3 -0
- package/lib/shared/components/Input.js +18 -0
- package/lib/shared/components/Pagination.js +3 -2
- package/lib/shared/components/Select.d.ts +19 -0
- package/lib/shared/components/Select.js +28 -0
- package/lib/shared/components/SingleDatePicker.d.ts +11 -0
- package/lib/shared/components/SingleDatePicker.js +16 -0
- package/lib/stacBrowser/components/StacPanel.d.ts +9 -1
- package/lib/stacBrowser/components/StacPanel.js +53 -9
- package/lib/stacBrowser/components/filter-extension/QueryableComboBox.d.ts +9 -0
- package/lib/stacBrowser/components/filter-extension/QueryableComboBox.js +179 -0
- package/lib/stacBrowser/components/filter-extension/QueryableRow.d.ts +16 -0
- package/lib/stacBrowser/components/filter-extension/QueryableRow.js +16 -0
- package/lib/stacBrowser/components/filter-extension/StacFilterExtensionPanel.d.ts +7 -0
- package/lib/stacBrowser/components/filter-extension/StacFilterExtensionPanel.js +49 -0
- package/lib/stacBrowser/components/filter-extension/StacQueryableFilters.d.ts +11 -0
- package/lib/stacBrowser/components/filter-extension/StacQueryableFilters.js +19 -0
- package/lib/stacBrowser/components/{StacFilterSection.d.ts → geodes/StacFilterSection.d.ts} +1 -1
- package/lib/stacBrowser/components/{StacFilterSection.js → geodes/StacFilterSection.js} +3 -3
- package/lib/stacBrowser/components/geodes/StacGeodesFilterPanel.d.ts +7 -0
- package/lib/stacBrowser/components/geodes/StacGeodesFilterPanel.js +69 -0
- package/lib/stacBrowser/components/shared/StacPanelResults.d.ts +3 -0
- package/lib/stacBrowser/components/shared/StacPanelResults.js +68 -0
- package/lib/stacBrowser/components/shared/StacSpatialExtent.d.ts +8 -0
- package/lib/stacBrowser/components/shared/StacSpatialExtent.js +10 -0
- package/lib/stacBrowser/components/shared/StacTemporalExtent.d.ts +9 -0
- package/lib/stacBrowser/components/shared/StacTemporalExtent.js +9 -0
- package/lib/stacBrowser/context/StacResultsContext.d.ts +33 -0
- package/lib/stacBrowser/context/StacResultsContext.js +269 -0
- package/lib/stacBrowser/hooks/useGeodesSearch.d.ts +24 -0
- package/lib/stacBrowser/hooks/useGeodesSearch.js +178 -0
- package/lib/stacBrowser/hooks/useStacFilterExtension.d.ts +30 -0
- package/lib/stacBrowser/hooks/useStacFilterExtension.js +262 -0
- package/lib/stacBrowser/hooks/useStacSearch.d.ts +5 -16
- package/lib/stacBrowser/hooks/useStacSearch.js +30 -184
- package/lib/stacBrowser/types/types.d.ts +86 -3
- package/lib/toolbar/widget.d.ts +5 -0
- package/lib/toolbar/widget.js +23 -2
- package/lib/tools.d.ts +0 -7
- package/lib/tools.js +55 -14
- package/package.json +2 -2
- package/style/base.css +38 -3
- package/style/shared/button.css +5 -4
- package/style/shared/calendar.css +7 -1
- package/style/shared/combobox.css +75 -0
- package/style/shared/command.css +178 -0
- package/style/shared/input.css +59 -0
- package/style/shared/pagination.css +1 -1
- package/style/shared/popover.css +1 -0
- package/style/shared/tabs.css +1 -1
- package/style/shared/toggle.css +1 -1
- package/style/stacBrowser.css +169 -16
- package/style/statusBar.css +1 -0
- package/style/storyPanel.css +120 -3
- package/style/tabPanel.css +0 -86
- package/lib/panelview/components/story-maps/StoryEditorPanel.d.ts +0 -7
- package/lib/panelview/components/story-maps/StoryEditorPanel.js +0 -29
- package/lib/panelview/components/story-maps/StoryViewerPanel.d.ts +0 -7
- package/lib/stacBrowser/components/StacPanelFilters.d.ts +0 -14
- package/lib/stacBrowser/components/StacPanelFilters.js +0 -81
- package/lib/stacBrowser/components/StacPanelResults.d.ts +0 -13
- package/lib/stacBrowser/components/StacPanelResults.js +0 -48
- /package/lib/panelview/{components/filter-panel → filter-panel}/Filter.d.ts +0 -0
- /package/lib/panelview/{components/filter-panel → filter-panel}/FilterRow.d.ts +0 -0
- /package/lib/panelview/{components/filter-panel → filter-panel}/FilterRow.js +0 -0
- /package/lib/panelview/{components/identify-panel → identify-panel}/IdentifyPanel.d.ts +0 -0
- /package/lib/panelview/{components/identify-panel → identify-panel}/IdentifyPanel.js +0 -0
- /package/lib/panelview/{components/story-maps → story-maps}/PreviewModeSwitch.d.ts +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ChevronsUpDownIcon } from 'lucide-react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "./Command";
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger, } from "./Popover";
|
|
6
|
+
import { cn } from './utils';
|
|
7
|
+
export function Select({ items, buttonText, emptyText = 'No option found.', className, buttonClassName, open: controlledOpen, onOpenChange: controlledOnOpenChange, showSearch = false, searchPlaceholder = 'Search...', }) {
|
|
8
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
9
|
+
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
10
|
+
const setOpen = controlledOnOpenChange || setInternalOpen;
|
|
11
|
+
const handleSelect = (item) => {
|
|
12
|
+
setOpen(false);
|
|
13
|
+
if (item.onSelect) {
|
|
14
|
+
item.onSelect();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
return (React.createElement(Popover, { open: open, onOpenChange: setOpen },
|
|
18
|
+
React.createElement(PopoverTrigger, { asChild: true },
|
|
19
|
+
React.createElement(Button, { variant: "outline", role: "combobox", "aria-expanded": open, className: cn('jgis-combobox-button', buttonClassName) },
|
|
20
|
+
React.createElement("span", { className: "jgis-combobox-button-text" }, buttonText),
|
|
21
|
+
React.createElement(ChevronsUpDownIcon, { className: "jgis-combobox-icon" }))),
|
|
22
|
+
React.createElement(PopoverContent, { className: cn('jgis-select-popover', className) },
|
|
23
|
+
React.createElement(Command, null,
|
|
24
|
+
showSearch && React.createElement(CommandInput, { placeholder: searchPlaceholder }),
|
|
25
|
+
React.createElement(CommandList, null,
|
|
26
|
+
React.createElement(CommandEmpty, null, emptyText),
|
|
27
|
+
React.createElement(CommandGroup, null, items.map(item => (React.createElement(CommandItem, { key: item.value, value: item.label, onSelect: () => handleSelect(item) }, item.label)))))))));
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ISingleDatePickerProps {
|
|
3
|
+
date: Date | undefined;
|
|
4
|
+
onDateChange: (date: Date | undefined) => void;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
dateFormat?: string;
|
|
8
|
+
showIcon?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function SingleDatePicker({ date, onDateChange, placeholder, className, dateFormat, showIcon, }: ISingleDatePickerProps): React.JSX.Element;
|
|
11
|
+
export default SingleDatePicker;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { format } from 'date-fns';
|
|
2
|
+
import { CalendarIcon } from 'lucide-react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
import { Calendar } from "./Calendar";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger, } from "./Popover";
|
|
7
|
+
function SingleDatePicker({ date, onDateChange, placeholder = 'Select date', className, dateFormat = 'PPP', showIcon = true, }) {
|
|
8
|
+
return (React.createElement(Popover, null,
|
|
9
|
+
React.createElement(PopoverTrigger, { asChild: true },
|
|
10
|
+
React.createElement(Button, { className: className, variant: "outline" },
|
|
11
|
+
showIcon && React.createElement(CalendarIcon, { className: "jgis-stac-datepicker-icon" }),
|
|
12
|
+
date ? format(date, dateFormat) : React.createElement("span", null, placeholder))),
|
|
13
|
+
React.createElement(PopoverContent, null,
|
|
14
|
+
React.createElement(Calendar, { mode: "single", selected: date, onSelect: onDateChange, autoFocus: true }))));
|
|
15
|
+
}
|
|
16
|
+
export default SingleDatePicker;
|
|
@@ -3,5 +3,13 @@ import React from 'react';
|
|
|
3
3
|
interface IStacViewProps {
|
|
4
4
|
model?: IJupyterGISModel;
|
|
5
5
|
}
|
|
6
|
-
declare const StacPanel:
|
|
6
|
+
declare const StacPanel: {
|
|
7
|
+
({ model }: IStacViewProps): React.JSX.Element;
|
|
8
|
+
ProviderSelect: typeof ProviderSelect;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Provider selector component for choosing STAC providers.
|
|
12
|
+
* Uses context to manage selected provider URL.
|
|
13
|
+
*/
|
|
14
|
+
declare function ProviderSelect(): React.JSX.Element;
|
|
7
15
|
export default StacPanel;
|
|
@@ -1,20 +1,64 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Select } from "../../shared/components/Select";
|
|
2
3
|
import { Tabs, TabsContent, TabsList, TabsTrigger, } from "../../shared/components/Tabs";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import StacPanelResults from
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import StacFilterExtensionPanel from "./filter-extension/StacFilterExtensionPanel";
|
|
5
|
+
import StacGeodesFilterPanel from "./geodes/StacGeodesFilterPanel";
|
|
6
|
+
import StacPanelResults from "./shared/StacPanelResults";
|
|
7
|
+
import { StacResultsProvider, useStacResultsContext, } from "../context/StacResultsContext";
|
|
8
|
+
const GEODES_URL = 'https://geodes-portal.cnes.fr/api/stac/';
|
|
9
|
+
const COPERNICUS_URL = 'https://stac.dataspace.copernicus.eu/v1/';
|
|
10
|
+
const WORLDPOP_URL = 'https://api.stac.worldpop.org/';
|
|
11
|
+
const PROVIDERS = [
|
|
12
|
+
{ url: COPERNICUS_URL, name: 'Copernicus' },
|
|
13
|
+
{ url: GEODES_URL, name: 'GEODES' },
|
|
14
|
+
{ url: WORLDPOP_URL, name: 'WorldPop' },
|
|
15
|
+
];
|
|
16
|
+
// URL to panel component mapping for extensibility
|
|
17
|
+
// Add new entries here to support additional STAC providers
|
|
18
|
+
const URL_TO_PANEL_MAP = {
|
|
19
|
+
[GEODES_URL]: StacGeodesFilterPanel,
|
|
20
|
+
};
|
|
21
|
+
// Inner component that uses the context
|
|
22
|
+
const StacPanelContent = ({ model }) => {
|
|
23
|
+
var _a;
|
|
24
|
+
const { totalResults, selectedUrl } = useStacResultsContext();
|
|
8
25
|
if (!model) {
|
|
9
26
|
return null;
|
|
10
27
|
}
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
const ProviderPanel = (_a = URL_TO_PANEL_MAP[selectedUrl]) !== null && _a !== void 0 ? _a : StacFilterExtensionPanel;
|
|
29
|
+
return (React.createElement(Tabs, { defaultValue: "filters", className: "jgis-panel-tabs", style: { boxShadow: 'none' } },
|
|
30
|
+
React.createElement(TabsList, { className: "jgis-stac-panel-tabs-list" },
|
|
13
31
|
React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: "filters" }, "Filters"),
|
|
14
32
|
React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: "results" }, `Results (${totalResults})`)),
|
|
15
33
|
React.createElement(TabsContent, { value: "filters" },
|
|
16
|
-
React.createElement(
|
|
34
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-panel" },
|
|
35
|
+
React.createElement(StacPanel.ProviderSelect, null),
|
|
36
|
+
selectedUrl ? (React.createElement(ProviderPanel, { model: model })) : (React.createElement("div", { className: "jgis-stac-panel-placeholder" }, "Please select a provider above")))),
|
|
17
37
|
React.createElement(TabsContent, { value: "results" },
|
|
18
|
-
React.createElement(StacPanelResults,
|
|
38
|
+
React.createElement(StacPanelResults, null))));
|
|
39
|
+
};
|
|
40
|
+
// Outer component that provides the context
|
|
41
|
+
const StacPanel = ({ model }) => {
|
|
42
|
+
return (React.createElement(StacResultsProvider, { model: model },
|
|
43
|
+
React.createElement(StacPanelContent, { model: model })));
|
|
19
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Provider selector component for choosing STAC providers.
|
|
47
|
+
* Uses context to manage selected provider URL.
|
|
48
|
+
*/
|
|
49
|
+
function ProviderSelect() {
|
|
50
|
+
const { selectedUrl, setSelectedUrl } = useStacResultsContext();
|
|
51
|
+
const selectedProvider = PROVIDERS.find(provider => provider.url === selectedUrl);
|
|
52
|
+
const buttonText = (selectedProvider === null || selectedProvider === void 0 ? void 0 : selectedProvider.name) || 'Select a provider...';
|
|
53
|
+
const items = PROVIDERS.map(provider => ({
|
|
54
|
+
value: provider.url,
|
|
55
|
+
label: provider.name,
|
|
56
|
+
onSelect: () => setSelectedUrl(provider.url),
|
|
57
|
+
}));
|
|
58
|
+
return (React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
59
|
+
React.createElement("label", { className: "jgis-stac-filter-extension-label" }, "Provider"),
|
|
60
|
+
React.createElement(Select, { items: items, buttonText: buttonText, emptyText: "No provider found.", buttonClassName: "jgis-stac-filter-extension-select" })));
|
|
61
|
+
}
|
|
62
|
+
// Attach ProviderSelect as a composable sub-component
|
|
63
|
+
StacPanel.ProviderSelect = ProviderSelect;
|
|
20
64
|
export default StacPanel;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { IQueryableFilter, IStacQueryables, UpdateSelectedQueryables } from "../../types/types";
|
|
3
|
+
interface IQueryableComboProps {
|
|
4
|
+
queryables: IStacQueryables;
|
|
5
|
+
selectedQueryables: Record<string, IQueryableFilter>;
|
|
6
|
+
updateSelectedQueryables: UpdateSelectedQueryables;
|
|
7
|
+
}
|
|
8
|
+
export declare function QueryableComboBox({ queryables, selectedQueryables, updateSelectedQueryables, }: IQueryableComboProps): React.JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Combobox } from "../../../shared/components/Combobox";
|
|
3
|
+
import { Input } from "../../../shared/components/Input";
|
|
4
|
+
import { Select } from "../../../shared/components/Select";
|
|
5
|
+
import QueryableRow from "./QueryableRow";
|
|
6
|
+
import SingleDatePicker from '../../../shared/components/SingleDatePicker';
|
|
7
|
+
export function QueryableComboBox({ queryables, selectedQueryables, updateSelectedQueryables, }) {
|
|
8
|
+
// Derive selected items from selectedQueryables
|
|
9
|
+
const selectedItems = useMemo(() => {
|
|
10
|
+
return queryables.filter(([key]) => key in selectedQueryables);
|
|
11
|
+
}, [queryables, selectedQueryables]);
|
|
12
|
+
const handleSelect = (key, val) => {
|
|
13
|
+
var _a;
|
|
14
|
+
const isCurrentlySelected = key in selectedQueryables;
|
|
15
|
+
if (isCurrentlySelected) {
|
|
16
|
+
// Remove if already selected - pass null to explicitly remove
|
|
17
|
+
updateSelectedQueryables(key, null);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// Add if not selected - initialize with default filter
|
|
21
|
+
const operators = getOperatorsForType(val.type, val.format);
|
|
22
|
+
let initialInputValue = undefined;
|
|
23
|
+
// For enum types, set the first option since the UI looks like there's a selection
|
|
24
|
+
if (val.enum && val.enum.length > 0) {
|
|
25
|
+
initialInputValue =
|
|
26
|
+
typeof val.enum[0] === 'number' ? val.enum[0] : val.enum[0];
|
|
27
|
+
}
|
|
28
|
+
else if (val.type === 'string' && val.format === 'date-time') {
|
|
29
|
+
// For datetime types, set to current UTC time
|
|
30
|
+
initialInputValue = new Date().toISOString();
|
|
31
|
+
}
|
|
32
|
+
updateSelectedQueryables(key, {
|
|
33
|
+
operator: ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || '=',
|
|
34
|
+
inputValue: initialInputValue,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const getOperatorsForType = (type, format) => {
|
|
39
|
+
if (format === 'date-time') {
|
|
40
|
+
return [
|
|
41
|
+
{ value: '<', label: '<' },
|
|
42
|
+
{ value: '<=', label: '≤' },
|
|
43
|
+
{ value: '>', label: '>' },
|
|
44
|
+
{ value: '>=', label: '≥' },
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
switch (type) {
|
|
48
|
+
case 'string':
|
|
49
|
+
return [
|
|
50
|
+
{ value: '=', label: '=' },
|
|
51
|
+
{ value: '!=', label: '≠' },
|
|
52
|
+
];
|
|
53
|
+
case 'number':
|
|
54
|
+
case 'integer':
|
|
55
|
+
return [
|
|
56
|
+
{ value: '=', label: '=' },
|
|
57
|
+
{ value: '!=', label: '≠' },
|
|
58
|
+
{ value: '<', label: '<' },
|
|
59
|
+
{ value: '<=', label: '≤' },
|
|
60
|
+
{ value: '>', label: '>' },
|
|
61
|
+
{ value: '>=', label: '≥' },
|
|
62
|
+
];
|
|
63
|
+
default:
|
|
64
|
+
return [
|
|
65
|
+
{ value: '=', label: '=' },
|
|
66
|
+
{ value: '!=', label: '≠' },
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const getInputBasedOnType = (val, currentValue, onChange) => {
|
|
71
|
+
switch (val.type) {
|
|
72
|
+
case 'string':
|
|
73
|
+
if (val.enum) {
|
|
74
|
+
const selectedOption = val.enum.find(opt => String(opt) === String(currentValue));
|
|
75
|
+
const buttonText = selectedOption
|
|
76
|
+
? String(selectedOption)
|
|
77
|
+
: 'Select option...';
|
|
78
|
+
return (React.createElement(Select, { items: val.enum.map(option => ({
|
|
79
|
+
value: String(option),
|
|
80
|
+
label: String(option),
|
|
81
|
+
onSelect: () => onChange(String(option)),
|
|
82
|
+
})), buttonText: buttonText, emptyText: "No option found.", buttonClassName: "jgis-queryable-combo-input" }));
|
|
83
|
+
}
|
|
84
|
+
if (val.format === 'date-time') {
|
|
85
|
+
// Convert UTC ISO string to Date object for SingleDatePicker
|
|
86
|
+
const parseDate = (isoString) => {
|
|
87
|
+
if (!isoString) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return new Date(isoString);
|
|
92
|
+
}
|
|
93
|
+
catch (_a) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Convert Date object back to ISO string for storage
|
|
98
|
+
const handleDateChange = (date) => {
|
|
99
|
+
if (date) {
|
|
100
|
+
onChange(date.toISOString());
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
onChange('');
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return (React.createElement(SingleDatePicker, { date: parseDate(currentValue), onDateChange: handleDateChange, dateFormat: "P", showIcon: true, placeholder: "Select date", className: "jgis-queryable-combo-input jgis-queryable-combo-input-date-picker" }));
|
|
107
|
+
}
|
|
108
|
+
return (React.createElement(Input, { type: "text", className: "jgis-queryable-combo-input",
|
|
109
|
+
// style={{borderRadius: 0}}
|
|
110
|
+
value: currentValue || '', onChange: e => onChange(e.target.value) }));
|
|
111
|
+
case 'number':
|
|
112
|
+
case 'integer':
|
|
113
|
+
if (val.enum) {
|
|
114
|
+
const selectedOption = val.enum.find(opt => Number(opt) === Number(currentValue));
|
|
115
|
+
const buttonText = selectedOption
|
|
116
|
+
? String(selectedOption)
|
|
117
|
+
: 'Select option...';
|
|
118
|
+
return (React.createElement(Select, { items: val.enum.map(option => ({
|
|
119
|
+
value: String(option),
|
|
120
|
+
label: String(option),
|
|
121
|
+
onSelect: () => onChange(Number(option)),
|
|
122
|
+
})), buttonText: buttonText, emptyText: "No option found.", buttonClassName: "jgis-queryable-combo-input" }));
|
|
123
|
+
}
|
|
124
|
+
return (React.createElement(Input, { type: "number", className: "jgis-queryable-combo-input", min: val.minimum !== undefined ? val.minimum : undefined, max: val.maximum !== undefined ? val.maximum : undefined, value: currentValue || '', onChange: e => onChange(Number(e.target.value)) }));
|
|
125
|
+
default:
|
|
126
|
+
return (React.createElement(Input, { type: "text", className: "jgis-queryable-combo-input", value: currentValue || '', onChange: e => onChange(e.target.value) }));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const getButtonText = () => {
|
|
130
|
+
if (selectedItems.length === 0) {
|
|
131
|
+
return 'Select queryable...';
|
|
132
|
+
}
|
|
133
|
+
if (selectedItems.length === 1) {
|
|
134
|
+
return selectedItems[0][1].title || selectedItems[0][0] || 'Queryable';
|
|
135
|
+
}
|
|
136
|
+
return `${selectedItems.length} selected`;
|
|
137
|
+
};
|
|
138
|
+
const isSelected = (key) => {
|
|
139
|
+
return key in selectedQueryables;
|
|
140
|
+
};
|
|
141
|
+
const items = queryables.map(([key, val]) => ({
|
|
142
|
+
value: key,
|
|
143
|
+
label: val.title || key,
|
|
144
|
+
selected: isSelected(key),
|
|
145
|
+
showCheckIcon: true,
|
|
146
|
+
onSelect: () => handleSelect(key, val),
|
|
147
|
+
}));
|
|
148
|
+
return (React.createElement("div", { className: "jgis-queryable-combo-container" },
|
|
149
|
+
React.createElement(Combobox, { items: items, buttonText: getButtonText(), searchPlaceholder: "Search queryable...", emptyText: "No queryable found.", buttonClassName: "jgis-queryable-combo-button jgis-combobox-button--full-width" }),
|
|
150
|
+
React.createElement("div", { className: "jgis-queryable-rows-container" }, selectedItems.map(([key, val]) => {
|
|
151
|
+
var _a, _b;
|
|
152
|
+
const operators = getOperatorsForType(val.type, val.format);
|
|
153
|
+
const currentFilter = (_a = selectedQueryables[key]) !== null && _a !== void 0 ? _a : {
|
|
154
|
+
operator: ((_b = operators[0]) === null || _b === void 0 ? void 0 : _b.value) || '=',
|
|
155
|
+
inputValue: undefined,
|
|
156
|
+
};
|
|
157
|
+
const handleInputChange = (value) => {
|
|
158
|
+
// For datetime values, convert local time to UTC ISO string
|
|
159
|
+
let valueToStore = value;
|
|
160
|
+
if (val.type === 'string' &&
|
|
161
|
+
val.format === 'date-time' &&
|
|
162
|
+
typeof value === 'string') {
|
|
163
|
+
try {
|
|
164
|
+
// Parse local time and convert to UTC ISO string
|
|
165
|
+
const localDate = new Date(value);
|
|
166
|
+
valueToStore = localDate.toISOString();
|
|
167
|
+
}
|
|
168
|
+
catch (_a) {
|
|
169
|
+
valueToStore = value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
updateSelectedQueryables(key, Object.assign(Object.assign({}, currentFilter), { inputValue: valueToStore }));
|
|
173
|
+
};
|
|
174
|
+
const handleOperatorChange = (operator) => {
|
|
175
|
+
updateSelectedQueryables(key, Object.assign(Object.assign({}, currentFilter), { operator }));
|
|
176
|
+
};
|
|
177
|
+
return (React.createElement(QueryableRow, { key: key, qKey: key, qVal: val, operators: operators, currentFilter: currentFilter, inputComponent: getInputBasedOnType(val, currentFilter.inputValue, handleInputChange), onOperatorChange: handleOperatorChange }));
|
|
178
|
+
}))));
|
|
179
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { IQueryableFilter, IStacQueryableSchema, Operator } from "../../types/types";
|
|
3
|
+
interface IOperatorOption {
|
|
4
|
+
value: Operator;
|
|
5
|
+
label: string | React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
interface IQueryableRowProps {
|
|
8
|
+
qKey: string;
|
|
9
|
+
qVal: IStacQueryableSchema;
|
|
10
|
+
operators: IOperatorOption[];
|
|
11
|
+
currentFilter: IQueryableFilter;
|
|
12
|
+
inputComponent: React.ReactNode;
|
|
13
|
+
onOperatorChange: (operator: Operator) => void;
|
|
14
|
+
}
|
|
15
|
+
declare function QueryableRow({ qKey, qVal, operators, currentFilter, inputComponent, onOperatorChange, }: IQueryableRowProps): React.JSX.Element;
|
|
16
|
+
export default QueryableRow;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Select } from "../../../shared/components/Select";
|
|
3
|
+
function QueryableRow({ qKey, qVal, operators, currentFilter, inputComponent, onOperatorChange, }) {
|
|
4
|
+
const currentOperator = operators.find(op => op.value === currentFilter.operator);
|
|
5
|
+
const buttonText = (currentOperator === null || currentOperator === void 0 ? void 0 : currentOperator.label) || 'Select operator...';
|
|
6
|
+
const items = operators.map(operator => ({
|
|
7
|
+
value: String(operator.value),
|
|
8
|
+
label: String(operator.label),
|
|
9
|
+
onSelect: () => onOperatorChange(operator.value),
|
|
10
|
+
}));
|
|
11
|
+
return (React.createElement("div", { className: "jgis-queryable-row" },
|
|
12
|
+
React.createElement("span", null, qVal.title || qKey),
|
|
13
|
+
React.createElement(Select, { items: items, buttonText: String(buttonText), emptyText: "No operator found.", buttonClassName: "jgis-queryable-combo-operator" }),
|
|
14
|
+
inputComponent));
|
|
15
|
+
}
|
|
16
|
+
export default QueryableRow;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IJupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
interface IStacFilterExtensionPanelProps {
|
|
4
|
+
model?: IJupyterGISModel;
|
|
5
|
+
}
|
|
6
|
+
declare function StacFilterExtensionPanel({ model }: IStacFilterExtensionPanelProps): React.JSX.Element | undefined;
|
|
7
|
+
export default StacFilterExtensionPanel;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Input } from "../../../shared/components/Input";
|
|
3
|
+
import { Select } from "../../../shared/components/Select";
|
|
4
|
+
import StacQueryableFilters from "./StacQueryableFilters";
|
|
5
|
+
import StacSpatialExtent from "../shared/StacSpatialExtent";
|
|
6
|
+
import StacTemporalExtent from "../shared/StacTemporalExtent";
|
|
7
|
+
import { useStacResultsContext } from "../../context/StacResultsContext";
|
|
8
|
+
import { useStacFilterExtension } from "../../hooks/useStacFilterExtension";
|
|
9
|
+
function StacFilterExtensionPanel({ model }) {
|
|
10
|
+
var _a;
|
|
11
|
+
const { selectedUrl } = useStacResultsContext();
|
|
12
|
+
const [limit, setLimit] = useState(12);
|
|
13
|
+
const { queryableFields, collections, selectedCollection, setSelectedCollection, startTime, endTime, setStartTime, setEndTime, useWorldBBox, setUseWorldBBox, selectedQueryables, updateSelectedQueryables, filterOperator, setFilterOperator, } = useStacFilterExtension({
|
|
14
|
+
model,
|
|
15
|
+
baseUrl: selectedUrl,
|
|
16
|
+
limit,
|
|
17
|
+
});
|
|
18
|
+
if (!model) {
|
|
19
|
+
console.warn('JupyterGIS model not found');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
return (React.createElement(React.Fragment, null,
|
|
23
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
24
|
+
React.createElement(StacTemporalExtent, { startTime: startTime, endTime: endTime, setStartTime: setStartTime, setEndTime: setEndTime })),
|
|
25
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
26
|
+
React.createElement(StacSpatialExtent, { checked: useWorldBBox, onCheckedChange: setUseWorldBBox, label: "Use entire world" })),
|
|
27
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
28
|
+
React.createElement("label", { className: "jgis-stac-filter-extension-label" }, "Collection"),
|
|
29
|
+
React.createElement(Select, { items: collections.map((option) => ({
|
|
30
|
+
value: option.id,
|
|
31
|
+
label: option.title || option.id,
|
|
32
|
+
onSelect: () => setSelectedCollection(option.id),
|
|
33
|
+
})), buttonText: selectedCollection
|
|
34
|
+
? ((_a = collections.find(c => c.id === selectedCollection)) === null || _a === void 0 ? void 0 : _a.title) ||
|
|
35
|
+
'Select a collection...'
|
|
36
|
+
: 'Select a collection...', emptyText: "No collection found.", buttonClassName: "jgis-stac-filter-extension-select", showSearch: true, searchPlaceholder: "Search collections..." })),
|
|
37
|
+
queryableFields && (React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
38
|
+
React.createElement("label", { className: "jgis-stac-filter-extension-label" }, "Additional Filters"),
|
|
39
|
+
React.createElement(StacQueryableFilters, { queryableFields: queryableFields, selectedQueryables: selectedQueryables, updateSelectedQueryables: updateSelectedQueryables, filterOperator: filterOperator, setFilterOperator: setFilterOperator }))),
|
|
40
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
41
|
+
React.createElement("label", { className: "jgis-stac-filter-extension-label" }, "Items per page"),
|
|
42
|
+
React.createElement(Input, { type: "number", min: "1", max: "1000", value: limit, onChange: e => {
|
|
43
|
+
const value = parseInt(e.target.value, 10);
|
|
44
|
+
if (!isNaN(value) && value > 0) {
|
|
45
|
+
setLimit(value);
|
|
46
|
+
}
|
|
47
|
+
}, className: "jgis-stac-filter-extension-input" }))));
|
|
48
|
+
}
|
|
49
|
+
export default StacFilterExtensionPanel;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FilterOperator, IQueryableFilter, IStacQueryables, UpdateSelectedQueryables } from "../../types/types";
|
|
3
|
+
interface IStacQueryableFilterListProps {
|
|
4
|
+
queryableFields: IStacQueryables;
|
|
5
|
+
selectedQueryables: Record<string, IQueryableFilter>;
|
|
6
|
+
updateSelectedQueryables: UpdateSelectedQueryables;
|
|
7
|
+
filterOperator: FilterOperator;
|
|
8
|
+
setFilterOperator: (operator: FilterOperator) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const StacQueryableFilters: React.FC<IStacQueryableFilterListProps>;
|
|
11
|
+
export default StacQueryableFilters;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RadioGroup, RadioGroupItem } from "../../../shared/components/RadioGroup";
|
|
3
|
+
import { QueryableComboBox } from "./QueryableComboBox";
|
|
4
|
+
const StacQueryableFilters = ({ queryableFields, selectedQueryables, updateSelectedQueryables, filterOperator, setFilterOperator, }) => {
|
|
5
|
+
return (React.createElement("div", { className: "jgis-stac-queryable-filters" },
|
|
6
|
+
React.createElement(RadioGroup, { className: "jgis-stac-queryable-filters-radio-group", value: filterOperator, onValueChange: (value) => {
|
|
7
|
+
if (value === 'and' || value === 'or') {
|
|
8
|
+
setFilterOperator(value);
|
|
9
|
+
}
|
|
10
|
+
} },
|
|
11
|
+
React.createElement("div", { className: "jgis-stac-queryable-filters-radio-item" },
|
|
12
|
+
React.createElement(RadioGroupItem, { value: "and", id: "filter-operator-and" }),
|
|
13
|
+
React.createElement("label", { htmlFor: "filter-operator-and" }, "Match all filters (and)")),
|
|
14
|
+
React.createElement("div", { className: "jgis-stac-queryable-filters-radio-item" },
|
|
15
|
+
React.createElement(RadioGroupItem, { value: "or", id: "filter-operator-or" }),
|
|
16
|
+
React.createElement("label", { htmlFor: "filter-operator-or" }, "Match any filters (or)"))),
|
|
17
|
+
React.createElement(QueryableComboBox, { queryables: queryableFields, selectedQueryables: selectedQueryables, updateSelectedQueryables: updateSelectedQueryables })));
|
|
18
|
+
};
|
|
19
|
+
export default StacQueryableFilters;
|
|
@@ -2,9 +2,9 @@ import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
|
2
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
3
3
|
import { ChevronRight } from 'lucide-react';
|
|
4
4
|
import React, { useMemo } from 'react';
|
|
5
|
-
import Badge from "
|
|
6
|
-
import { Button } from "
|
|
7
|
-
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "
|
|
5
|
+
import Badge from "../../../shared/components/Badge";
|
|
6
|
+
import { Button } from "../../../shared/components/Button";
|
|
7
|
+
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "../../../shared/components/DropdownMenu";
|
|
8
8
|
const StacFilterSection = ({ section, data, selectedCollections, selectedData, handleCheckedChange, }) => {
|
|
9
9
|
const items = useMemo(() => {
|
|
10
10
|
if (section === 'Collection') {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IJupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
interface IStacGeodesFilterPanelProps {
|
|
4
|
+
model?: IJupyterGISModel;
|
|
5
|
+
}
|
|
6
|
+
declare const StacGeodesFilterPanel: ({ model }: IStacGeodesFilterPanelProps) => React.JSX.Element;
|
|
7
|
+
export default StacGeodesFilterPanel;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import StacFilterSection from "./StacFilterSection";
|
|
3
|
+
import StacSpatialExtent from "../shared/StacSpatialExtent";
|
|
4
|
+
import StacTemporalExtent from "../shared/StacTemporalExtent";
|
|
5
|
+
import { datasets as datasetsList, platforms as platformsList, products as productsList, } from "../../constants";
|
|
6
|
+
import useGeodesSearch from "../../hooks/useGeodesSearch";
|
|
7
|
+
const StacGeodesFilterPanel = ({ model }) => {
|
|
8
|
+
const { filterState, filterSetters, startTime, setStartTime, endTime, setEndTime, useWorldBBox, setUseWorldBBox, } = useGeodesSearch({
|
|
9
|
+
model,
|
|
10
|
+
});
|
|
11
|
+
const handleDatasetSelection = (dataset, collection) => {
|
|
12
|
+
const collections = new Set(filterState.collections);
|
|
13
|
+
const datasets = new Set(filterState.datasets);
|
|
14
|
+
if (datasets.has(dataset)) {
|
|
15
|
+
datasets.delete(dataset);
|
|
16
|
+
// Remove the collection if no datasets remain for it
|
|
17
|
+
const datasetsForCollection = Array.from(datasets).filter(d => {
|
|
18
|
+
return datasetsList.some(entry => entry.collection === collection && entry.datasets.includes(d));
|
|
19
|
+
});
|
|
20
|
+
if (datasetsForCollection.length === 0) {
|
|
21
|
+
collections.delete(collection);
|
|
22
|
+
const platforms = new Set(filterState.platforms);
|
|
23
|
+
const products = new Set(filterState.products);
|
|
24
|
+
// Remove platforms belonging to this collection
|
|
25
|
+
if (platformsList[collection]) {
|
|
26
|
+
platformsList[collection].forEach(platform => {
|
|
27
|
+
platforms.delete(platform);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// Remove products belonging to this collection
|
|
31
|
+
productsList
|
|
32
|
+
.filter(product => product.collections.includes(collection))
|
|
33
|
+
.forEach(product => {
|
|
34
|
+
products.delete(product.productCode);
|
|
35
|
+
});
|
|
36
|
+
filterSetters.platforms(platforms);
|
|
37
|
+
filterSetters.products(products);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
datasets.add(dataset);
|
|
42
|
+
collections.add(collection);
|
|
43
|
+
}
|
|
44
|
+
filterSetters.collections(collections);
|
|
45
|
+
filterSetters.datasets(datasets);
|
|
46
|
+
};
|
|
47
|
+
const handleToggle = (key, value) => {
|
|
48
|
+
const updated = new Set(filterState[key]);
|
|
49
|
+
if (updated.has(value)) {
|
|
50
|
+
updated.delete(value);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
updated.add(value);
|
|
54
|
+
}
|
|
55
|
+
filterSetters[key](updated);
|
|
56
|
+
};
|
|
57
|
+
return (React.createElement(React.Fragment, null,
|
|
58
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
59
|
+
React.createElement(StacSpatialExtent, { checked: useWorldBBox, onCheckedChange: setUseWorldBBox, label: "Use whole world as bounding box" })),
|
|
60
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
61
|
+
React.createElement(StacTemporalExtent, { startTime: startTime, setStartTime: setStartTime, endTime: endTime, setEndTime: setEndTime })),
|
|
62
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
63
|
+
React.createElement(StacFilterSection, { section: "Collection", data: datasetsList, selectedCollections: Array.from(filterState.collections), selectedData: Array.from(filterState.datasets), handleCheckedChange: handleDatasetSelection })),
|
|
64
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
65
|
+
React.createElement(StacFilterSection, { section: "Platform", data: platformsList, selectedCollections: Array.from(filterState.collections), selectedData: Array.from(filterState.platforms), handleCheckedChange: platform => handleToggle('platforms', platform) })),
|
|
66
|
+
React.createElement("div", { className: "jgis-stac-filter-extension-section" },
|
|
67
|
+
React.createElement(StacFilterSection, { section: "Data / Product", data: productsList, selectedCollections: Array.from(filterState.collections), selectedData: Array.from(filterState.products), handleCheckedChange: product => handleToggle('products', product) }))));
|
|
68
|
+
};
|
|
69
|
+
export default StacGeodesFilterPanel;
|