@jupytergis/base 0.2.1 → 0.4.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.
Files changed (95) hide show
  1. package/lib/annotations/components/Annotation.js +2 -2
  2. package/lib/annotations/model.d.ts +6 -7
  3. package/lib/annotations/model.js +15 -15
  4. package/lib/commands.d.ts +2 -3
  5. package/lib/commands.js +146 -62
  6. package/lib/constants.d.ts +3 -0
  7. package/lib/constants.js +5 -1
  8. package/lib/dialogs/formdialog.d.ts +5 -0
  9. package/lib/dialogs/formdialog.js +2 -2
  10. package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
  11. package/lib/dialogs/layerBrowserDialog.js +9 -9
  12. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
  13. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +26 -0
  14. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +64 -0
  15. package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
  16. package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -9
  17. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
  18. package/lib/dialogs/symbology/symbologyDialog.js +10 -9
  19. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  20. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +16 -3
  21. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
  22. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
  23. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
  24. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +85 -0
  25. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -20
  26. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +25 -65
  27. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
  28. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
  29. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
  30. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
  31. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
  32. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
  33. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
  34. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
  35. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
  36. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
  37. package/lib/formbuilder/creationform.d.ts +6 -2
  38. package/lib/formbuilder/creationform.js +6 -6
  39. package/lib/formbuilder/editform.d.ts +2 -2
  40. package/lib/formbuilder/editform.js +14 -9
  41. package/lib/formbuilder/formselectors.js +11 -1
  42. package/lib/formbuilder/objectform/baseform.d.ts +12 -3
  43. package/lib/formbuilder/objectform/baseform.js +39 -0
  44. package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
  45. package/lib/formbuilder/objectform/fileselectorwidget.js +88 -0
  46. package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
  47. package/lib/formbuilder/objectform/geojsonsource.js +8 -24
  48. package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
  49. package/lib/formbuilder/objectform/geotiffsource.js +64 -18
  50. package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
  51. package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
  52. package/lib/formbuilder/objectform/layerform.d.ts +2 -0
  53. package/lib/formbuilder/objectform/layerform.js +6 -0
  54. package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
  55. package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
  56. package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
  57. package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
  58. package/lib/icons.d.ts +1 -0
  59. package/lib/icons.js +5 -0
  60. package/lib/keybindings.json +62 -0
  61. package/lib/mainview/TemporalSlider.d.ts +8 -0
  62. package/lib/mainview/TemporalSlider.js +303 -0
  63. package/lib/mainview/mainView.d.ts +46 -8
  64. package/lib/mainview/mainView.js +431 -144
  65. package/lib/mainview/mainviewmodel.d.ts +4 -0
  66. package/lib/mainview/mainviewmodel.js +4 -0
  67. package/lib/mainview/mainviewwidget.d.ts +0 -2
  68. package/lib/mainview/mainviewwidget.js +0 -2
  69. package/lib/panelview/annotationPanel.js +5 -5
  70. package/lib/panelview/components/filter-panel/Filter.js +8 -24
  71. package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
  72. package/lib/panelview/components/layers.js +2 -2
  73. package/lib/panelview/components/sources.js +1 -1
  74. package/lib/panelview/leftpanel.d.ts +3 -0
  75. package/lib/panelview/leftpanel.js +5 -1
  76. package/lib/panelview/model.js +8 -8
  77. package/lib/panelview/objectproperties.js +10 -10
  78. package/lib/panelview/rightpanel.d.ts +1 -1
  79. package/lib/panelview/rightpanel.js +10 -10
  80. package/lib/statusbar/StatusBar.d.ts +13 -0
  81. package/lib/statusbar/StatusBar.js +52 -0
  82. package/lib/toolbar/widget.d.ts +1 -1
  83. package/lib/toolbar/widget.js +44 -37
  84. package/lib/tools.d.ts +50 -7
  85. package/lib/tools.js +394 -12
  86. package/lib/types.d.ts +2 -0
  87. package/lib/widget.d.ts +29 -5
  88. package/lib/widget.js +41 -7
  89. package/package.json +17 -5
  90. package/style/base.css +11 -0
  91. package/style/icons/logo_mini_qgz.svg +31 -0
  92. package/style/leftPanel.css +8 -0
  93. package/style/statusBar.css +16 -0
  94. package/style/symbologyDialog.css +7 -1
  95. package/style/temporalSlider.css +47 -0
@@ -0,0 +1,98 @@
1
+ import { showErrorMessage } from '@jupyterlab/apputils';
2
+ import { BaseForm } from './baseform';
3
+ import { loadFile } from '../../tools';
4
+ import { FileSelectorWidget } from './fileselectorwidget';
5
+ /**
6
+ * The form to modify a PathBasedSource source.
7
+ */
8
+ export class PathBasedSourcePropertiesForm extends BaseForm {
9
+ constructor(props) {
10
+ var _a, _b;
11
+ super(props);
12
+ if (this.props.sourceType !== 'GeoJSONSource') {
13
+ this._validatePath((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : '');
14
+ }
15
+ }
16
+ processSchema(data, schema, uiSchema) {
17
+ var _a;
18
+ super.processSchema(data, schema, uiSchema);
19
+ if (!schema.properties || !data) {
20
+ return;
21
+ }
22
+ // Customize the widget for path field
23
+ if (schema.properties && schema.properties.path) {
24
+ const docManager = (_a = this.props.formChangedSignal) === null || _a === void 0 ? void 0 : _a.sender.props.formSchemaRegistry.getDocManager();
25
+ uiSchema.path = {
26
+ 'ui:widget': FileSelectorWidget,
27
+ 'ui:options': {
28
+ docManager,
29
+ formOptions: this.props
30
+ }
31
+ };
32
+ }
33
+ // This is not user-editable
34
+ delete schema.properties.valid;
35
+ }
36
+ onFormBlur(id, value) {
37
+ // Is there a better way to spot the path text entry?
38
+ if (!id.endsWith('_path')) {
39
+ return;
40
+ }
41
+ this._validatePath(value);
42
+ }
43
+ // we need to use `onFormChange` instead of `onFormBlur` because it's no longer a text field
44
+ onFormChange(e) {
45
+ var _a;
46
+ super.onFormChange(e);
47
+ if (((_a = e.formData) === null || _a === void 0 ? void 0 : _a.path) !== undefined) {
48
+ this._validatePath(e.formData.path);
49
+ }
50
+ }
51
+ onFormSubmit(e) {
52
+ var _a, _b, _c;
53
+ if (((_c = (_b = (_a = this.state.extraErrors) === null || _a === void 0 ? void 0 : _a.path) === null || _b === void 0 ? void 0 : _b.__errors) === null || _c === void 0 ? void 0 : _c.length) >= 1) {
54
+ showErrorMessage('Invalid file', this.state.extraErrors.path.__errors[0]);
55
+ return;
56
+ }
57
+ super.onFormSubmit(e);
58
+ }
59
+ /**
60
+ * Validate the path, to avoid invalid path.
61
+ *
62
+ * @param path - the path to validate.
63
+ */
64
+ async _validatePath(path) {
65
+ const extraErrors = this.state.extraErrors;
66
+ let error = '';
67
+ let valid = true;
68
+ if (!path) {
69
+ valid = false;
70
+ error = 'Path is required';
71
+ }
72
+ else {
73
+ try {
74
+ await loadFile({
75
+ filepath: path,
76
+ type: this.props.sourceType,
77
+ model: this.props.model
78
+ });
79
+ }
80
+ catch (e) {
81
+ valid = false;
82
+ error = `"${path}" is not a valid ${this.props.sourceType} file.`;
83
+ }
84
+ }
85
+ if (!valid) {
86
+ extraErrors.path = {
87
+ __errors: [error]
88
+ };
89
+ this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors })));
90
+ }
91
+ else {
92
+ this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors: Object.assign(Object.assign({}, extraErrors), { path: { __errors: [] } }) })));
93
+ }
94
+ if (this.props.formErrorSignal) {
95
+ this.props.formErrorSignal.emit(!valid);
96
+ }
97
+ }
98
+ }
@@ -6,10 +6,8 @@ import { ILayerProps, LayerPropertiesForm } from './layerform';
6
6
  */
7
7
  export declare class VectorLayerPropertiesForm extends LayerPropertiesForm {
8
8
  protected currentFormData: IVectorLayer;
9
- private sourceLayers;
10
9
  private currentSourceId;
11
10
  constructor(props: ILayerProps);
12
11
  protected onFormChange(e: IChangeEvent): void;
13
12
  protected processSchema(data: IVectorLayer | undefined, schema: IDict, uiSchema: IDict): void;
14
- private fetchSourceLayers;
15
13
  }
@@ -1,4 +1,3 @@
1
- import { getSourceLayerNames } from '../../tools';
2
1
  import { LayerPropertiesForm } from './layerform';
3
2
  /**
4
3
  * The form to modify a vector layer.
@@ -6,29 +5,6 @@ import { LayerPropertiesForm } from './layerform';
6
5
  export class VectorLayerPropertiesForm extends LayerPropertiesForm {
7
6
  constructor(props) {
8
7
  super(props);
9
- this.sourceLayers = [];
10
- this.fetchSourceLayers(this.props.sourceData);
11
- // If there is a source form attached, we listen to its changes
12
- if (this.sourceFormChangedSignal) {
13
- this.sourceFormChangedSignal.connect((sender, sourceData) => {
14
- if (this.props.sourceType === 'VectorTileSource') {
15
- this.fetchSourceLayers(this.currentFormData, sourceData);
16
- }
17
- });
18
- }
19
- props.model.clientStateChanged.connect(() => {
20
- var _a;
21
- if (!((_a = props.model.localState) === null || _a === void 0 ? void 0 : _a.selected.value)) {
22
- return;
23
- }
24
- const l = this.props.model.getLayer(Object.keys(props.model.localState.selected.value)[0]);
25
- const source = this.props.model.getSource(l === null || l === void 0 ? void 0 : l.parameters.source);
26
- if (!source || source.type !== 'VectorTileSource') {
27
- return;
28
- }
29
- const sourceData = source.parameters;
30
- this.fetchSourceLayers(this.currentFormData, sourceData);
31
- });
32
8
  }
33
9
  onFormChange(e) {
34
10
  super.onFormChange(e);
@@ -40,7 +16,6 @@ export class VectorLayerPropertiesForm extends LayerPropertiesForm {
40
16
  if (!source || source.type !== 'VectorTileSource') {
41
17
  return;
42
18
  }
43
- this.fetchSourceLayers(this.currentFormData, source.parameters);
44
19
  }
45
20
  processSchema(data, schema, uiSchema) {
46
21
  this.removeFormEntry('color', data, schema, uiSchema);
@@ -49,39 +24,5 @@ export class VectorLayerPropertiesForm extends LayerPropertiesForm {
49
24
  if (!data) {
50
25
  return;
51
26
  }
52
- // Show a dropdown for available sourceLayers if available
53
- // And automatically select one
54
- if (this.sourceLayers.length !== 0) {
55
- if (!data.sourceLayer || !this.sourceLayers.includes(data.sourceLayer)) {
56
- data.sourceLayer = this.sourceLayers[0];
57
- }
58
- schema.properties.sourceLayer.enum = this.sourceLayers;
59
- }
60
- }
61
- async fetchSourceLayers(data, sourceData) {
62
- if (data && data.source) {
63
- this.currentSourceId = data.source;
64
- if (!sourceData) {
65
- const currentSource = this.props.model.getSource(data.source);
66
- if (!currentSource || currentSource.type !== 'VectorTileSource') {
67
- this.sourceLayers = [];
68
- this.forceUpdate();
69
- return;
70
- }
71
- sourceData = currentSource.parameters;
72
- }
73
- try {
74
- this.sourceLayers = await getSourceLayerNames(sourceData.url, sourceData.urlParameters);
75
- this.forceUpdate();
76
- }
77
- catch (e) {
78
- console.error(e);
79
- }
80
- }
81
- else {
82
- this.currentSourceId = '';
83
- this.sourceLayers = [];
84
- this.forceUpdate();
85
- }
86
27
  }
87
28
  }
package/lib/icons.d.ts CHANGED
@@ -7,3 +7,4 @@ export declare const visibilityIcon: LabIcon;
7
7
  export declare const nonVisibilityIcon: LabIcon;
8
8
  export declare const geoJSONIcon: LabIcon;
9
9
  export declare const moundIcon: LabIcon;
10
+ export declare const logoMiniIconQGZ: LabIcon;
package/lib/icons.js CHANGED
@@ -12,6 +12,7 @@ import visibilitySvgStr from '../style/icons/visibility.svg';
12
12
  import nonVisibilitySvgStr from '../style/icons/nonvisibility.svg';
13
13
  import geoJsonSvgStr from '../style/icons/geojson.svg';
14
14
  import moundSvgStr from '../style/icons/mound.svg';
15
+ import logoMiniQGZ from '../style/icons/logo_mini_qgz.svg';
15
16
  export const logoIcon = new LabIcon({
16
17
  name: 'jupytergis::logo',
17
18
  svgstr: logoSvgStr
@@ -44,3 +45,7 @@ export const moundIcon = new LabIcon({
44
45
  name: 'jupytergis::mound',
45
46
  svgstr: moundSvgStr
46
47
  });
48
+ export const logoMiniIconQGZ = new LabIcon({
49
+ name: 'jupytergis::logoQGZ',
50
+ svgstr: logoMiniQGZ
51
+ });
@@ -0,0 +1,62 @@
1
+ [
2
+ {
3
+ "command": "jupytergis:undo",
4
+ "keys": ["Accel Z"],
5
+ "selector": ".data-jgis-keybinding"
6
+ },
7
+ {
8
+ "command": "jupytergis:redo",
9
+ "keys": ["Accel Shift Z"],
10
+ "selector": ".data-jgis-keybinding"
11
+ },
12
+ {
13
+ "command": "jupytergis:identify",
14
+ "keys": ["Escape"],
15
+ "selector": ".data-jgis-keybinding"
16
+ },
17
+ {
18
+ "command": "jupytergis:removeSource",
19
+ "keys": ["Delete"],
20
+ "selector": ".data-jgis-keybinding .jp-gis-source.jp-gis-sourceUnused"
21
+ },
22
+ {
23
+ "command": "jupytergis:renameSource",
24
+ "keys": ["F2"],
25
+ "selector": ".data-jgis-keybinding .jp-gis-source"
26
+ },
27
+ {
28
+ "command": "jupytergis:removeLayer",
29
+ "keys": ["Delete"],
30
+ "selector": ".data-jgis-keybinding .jp-gis-layerItem"
31
+ },
32
+ {
33
+ "command": "jupytergis:renameLayer",
34
+ "keys": ["F2"],
35
+ "selector": ".data-jgis-keybinding .jp-gis-layerItem"
36
+ },
37
+ {
38
+ "command": "jupytergis:removeGroup",
39
+ "keys": ["Delete"],
40
+ "selector": ".data-jgis-keybinding .jp-gis-layerGroupHeader"
41
+ },
42
+ {
43
+ "command": "jupytergis:renameGroup",
44
+ "keys": ["F2"],
45
+ "selector": ".data-jgis-keybinding .jp-gis-layerGroupHeader"
46
+ },
47
+ {
48
+ "command": "jupytergis:executeConsole",
49
+ "keys": ["Shift Enter"],
50
+ "selector": ".jpgis-console .jp-CodeConsole[data-jp-interaction-mode='notebook'] .jp-CodeConsole-promptCell"
51
+ },
52
+ {
53
+ "command": "jupytergis:invokeConsoleCompleter",
54
+ "keys": ["Tab"],
55
+ "selector": ".jpgis-console .jp-CodeConsole-promptCell .jp-mod-completer-enabled"
56
+ },
57
+ {
58
+ "command": "jupytergis:selectConsoleCompleter",
59
+ "keys": ["Enter"],
60
+ "selector": ".jpgis-console .jp-ConsolePanel .jp-mod-completer-active"
61
+ }
62
+ ]
@@ -0,0 +1,8 @@
1
+ import { IDict, IJGISFilterItem, IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface ITemporalSliderProps {
4
+ model: IJupyterGISModel;
5
+ filterStates: IDict<IJGISFilterItem | undefined>;
6
+ }
7
+ declare const TemporalSlider: ({ model, filterStates }: ITemporalSliderProps) => React.JSX.Element;
8
+ export default TemporalSlider;
@@ -0,0 +1,303 @@
1
+ import { faPause, faPlay } from '@fortawesome/free-solid-svg-icons';
2
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
+ import { Button, Slider } from '@jupyter/react-components';
4
+ import { format, isValid, parse } from 'date-fns';
5
+ import { daysInYear, millisecondsInDay, millisecondsInHour, millisecondsInMinute, millisecondsInSecond, millisecondsInWeek, minutesInMonth } from 'date-fns/constants';
6
+ import React, { useEffect, useRef, useState } from 'react';
7
+ import { useGetProperties } from '../dialogs/symbology/hooks/useGetProperties';
8
+ // List of common date formats to try
9
+ // TODO: Not even close to every valid format
10
+ const commonDateFormats = [
11
+ 'yyyy-MM-dd', // ISO format (e.g., 2023-10-05)
12
+ 'dd/MM/yyyy', // European format (e.g., 05/10/2023)
13
+ 'MM/dd/yyyy', // US format (e.g., 10/05/2023)
14
+ 'yyyyMMdd', // Compact format (e.g., 20231005)
15
+ 'dd-MM-yyyy', // European format with hyphens (e.g., 05-10-2023)
16
+ 'MM-dd-yyyy', // US format with hyphens (e.g., 10-05-2023)
17
+ 'yyyy/MM/dd', // ISO format with slashes (e.g., 2023/10/05)
18
+ 'dd.MM.yyyy', // European format with dots (e.g., 05.10.2023)
19
+ 'MM.dd.yyyy' // US format with dots (e.g., 10.05.2023)
20
+ ];
21
+ // Time steps in milliseconds
22
+ const stepMap = {
23
+ millisecond: 1,
24
+ second: millisecondsInSecond,
25
+ minute: millisecondsInMinute,
26
+ hour: millisecondsInHour,
27
+ day: millisecondsInDay,
28
+ week: millisecondsInWeek,
29
+ month: minutesInMonth * millisecondsInMinute,
30
+ year: millisecondsInDay * daysInYear
31
+ };
32
+ const TemporalSlider = ({ model, filterStates }) => {
33
+ const [layerId, setLayerId] = useState('');
34
+ const [selectedFeature, setSelectedFeature] = useState('');
35
+ const [range, setRange] = useState({ start: 0, end: 1 }); // min/max of current range being displayed
36
+ const [minMax, setMinMax] = useState({ min: 0, max: 1 }); // min/max of data values
37
+ const [validFeatures, setValidFeatures] = useState([]);
38
+ const [dateFormat, setDateFormat] = useState('yyyy-MM-dd');
39
+ const [step, setStep] = useState(stepMap.year);
40
+ const [currentValue, setCurrentValue] = useState(0);
41
+ const [fps, setFps] = useState(1);
42
+ const [validSteps, setValidSteps] = useState({});
43
+ const layerIdRef = useRef('');
44
+ const intervalRef = useRef(null);
45
+ const { featureProperties } = useGetProperties({ layerId, model });
46
+ useEffect(() => {
47
+ // This is for when the selected layer changes
48
+ const handleClientStateChanged = () => {
49
+ var _a, _b;
50
+ if (!((_b = (_a = model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value)) {
51
+ return;
52
+ }
53
+ const selectedLayerId = Object.keys(model.localState.selected.value)[0];
54
+ // reset
55
+ if (selectedLayerId !== layerIdRef.current) {
56
+ setLayerId(selectedLayerId);
57
+ setDateFormat('yyyy-MM-dd');
58
+ setFps(1);
59
+ }
60
+ };
61
+ // this is for when the layer itself changes
62
+ const handleLayerChange = (_, change) => {
63
+ var _a;
64
+ // Get the changes for the selected layer
65
+ const selectedLayer = (_a = change.layerChange) === null || _a === void 0 ? void 0 : _a.find(layer => layer.id === layerIdRef.current);
66
+ // Bail if there's no relevant change
67
+ if (!(selectedLayer === null || selectedLayer === void 0 ? void 0 : selectedLayer.newValue)) {
68
+ return;
69
+ }
70
+ const { newValue, oldValue } = selectedLayer;
71
+ // If layer was deleted (empty object) or the layer type changed, close the temporal controller
72
+ if (Object.keys(newValue).length === 0 ||
73
+ newValue.type !== oldValue.type) {
74
+ model.toggleTemporalController();
75
+ }
76
+ };
77
+ // Initial state
78
+ handleClientStateChanged();
79
+ model.clientStateChanged.connect(handleClientStateChanged);
80
+ model.sharedLayersChanged.connect(handleLayerChange);
81
+ return () => {
82
+ model.clientStateChanged.disconnect(handleClientStateChanged);
83
+ model.sharedLayersChanged.disconnect(handleLayerChange);
84
+ removeFilter();
85
+ if (intervalRef.current) {
86
+ clearInterval(intervalRef.current);
87
+ }
88
+ };
89
+ }, []);
90
+ useEffect(() => {
91
+ layerIdRef.current = layerId;
92
+ }, [layerId]);
93
+ useEffect(() => {
94
+ const results = [];
95
+ for (const [key, set] of Object.entries(featureProperties)) {
96
+ if (set.size === 0) {
97
+ continue;
98
+ }
99
+ const sampleValue = set.values().next().value;
100
+ // Validate value type
101
+ const isString = typeof sampleValue === 'string';
102
+ const isInteger = Number.isInteger(sampleValue);
103
+ if (!isString && !isInteger) {
104
+ continue;
105
+ }
106
+ // Date validation
107
+ if (isString) {
108
+ const dateFormatFromString = determineDateFormat(sampleValue);
109
+ if (!dateFormatFromString) {
110
+ continue;
111
+ }
112
+ setDateFormat(dateFormatFromString);
113
+ }
114
+ results.push(key);
115
+ }
116
+ // if we have state then remove the ms from the converted feature name
117
+ const currentState = filterStates[layerId];
118
+ const currentFeature = currentState === null || currentState === void 0 ? void 0 : currentState.feature.slice(0, -2);
119
+ setValidFeatures(results);
120
+ setSelectedFeature(currentFeature !== null && currentFeature !== void 0 ? currentFeature : results[0]);
121
+ }, [featureProperties]);
122
+ useEffect(() => {
123
+ var _a, _b, _c;
124
+ if (!selectedFeature || !featureProperties[selectedFeature]) {
125
+ return;
126
+ }
127
+ // Get and validate values
128
+ const valueSet = featureProperties[selectedFeature];
129
+ if (valueSet.size === 0) {
130
+ return;
131
+ }
132
+ const values = Array.from(valueSet);
133
+ const [firstValue] = values;
134
+ // Check the type of the first element
135
+ const isString = typeof firstValue === 'string';
136
+ let convertedValues;
137
+ if (isString) {
138
+ convertedValues = values.map(value => Date.parse(value)); // Convert all strings to milliseconds
139
+ }
140
+ else {
141
+ convertedValues = values; // Keep numbers as they are
142
+ }
143
+ // Calculate min and max
144
+ const min = Math.min(...convertedValues);
145
+ const max = Math.max(...convertedValues);
146
+ // Get valid step options
147
+ const filteredSteps = Object.fromEntries(Object.entries(stepMap).filter(([_, val]) => val < max - min));
148
+ //using filter item as a state object to restore prev values
149
+ const currentState = filterStates[layerId];
150
+ const step = (_a = Object.values(filteredSteps).slice(-1)[0]) !== null && _a !== void 0 ? _a : stepMap.millisecond;
151
+ setValidSteps(filteredSteps);
152
+ setStep(step);
153
+ setMinMax({ min, max });
154
+ setRange({
155
+ start: (_b = currentState === null || currentState === void 0 ? void 0 : currentState.betweenMin) !== null && _b !== void 0 ? _b : min,
156
+ end: (_c = currentState === null || currentState === void 0 ? void 0 : currentState.betweenMax) !== null && _c !== void 0 ? _c : min + step
157
+ });
158
+ model.addFeatureAsMs(layerId, selectedFeature);
159
+ }, [selectedFeature]);
160
+ // minMax needs to be set before current value so the slider displays correctly
161
+ useEffect(() => {
162
+ const currentState = filterStates[layerId];
163
+ setCurrentValue(typeof (currentState === null || currentState === void 0 ? void 0 : currentState.value) === 'number' ? currentState.value : minMax.min);
164
+ }, [minMax]);
165
+ // Guess the date format from a date string
166
+ const determineDateFormat = (dateString) => {
167
+ for (const format of commonDateFormats) {
168
+ const parsedDate = parse(dateString, format, new Date());
169
+ if (isValid(parsedDate)) {
170
+ return format; // Return the format if the date is valid
171
+ }
172
+ }
173
+ return null; // Return null if no format matches
174
+ };
175
+ // Convert milliseconds back to the original date string format
176
+ const millisecondsToDateString = (milliseconds, dateFormat) => {
177
+ const date = new Date(milliseconds); // Create a Date object from milliseconds
178
+ return format(date, dateFormat); // Format back to the original string format
179
+ };
180
+ const handleChange = (e) => {
181
+ setCurrentValue(+e.target.value);
182
+ setRange({ start: +e.target.value, end: +e.target.value + step });
183
+ applyFilter(+e.target.value);
184
+ };
185
+ const applyFilter = (value) => {
186
+ var _a, _b;
187
+ const newFilter = {
188
+ feature: `${selectedFeature}ms`,
189
+ operator: 'between',
190
+ value: value,
191
+ betweenMin: value,
192
+ betweenMax: value + step
193
+ };
194
+ const layer = model.getLayer(layerId);
195
+ if (!layer) {
196
+ return;
197
+ }
198
+ const appliedFilters = ((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.appliedFilters) || [];
199
+ const logicalOp = ((_b = layer.filters) === null || _b === void 0 ? void 0 : _b.logicalOp) || 'all';
200
+ // This is the only way to add a 'between' filter so
201
+ // find the index of the existing 'between' filter
202
+ const betweenFilterIndex = appliedFilters.findIndex(filter => filter.operator === 'between');
203
+ if (betweenFilterIndex !== -1) {
204
+ // If found, replace the existing filter
205
+ appliedFilters[betweenFilterIndex] = Object.assign({}, newFilter);
206
+ }
207
+ else {
208
+ // If not found, add the new filter
209
+ appliedFilters.push(newFilter);
210
+ }
211
+ // Apply the updated filters to the layer
212
+ layer.filters = { logicalOp, appliedFilters };
213
+ model.triggerLayerUpdate(layerId, layer);
214
+ };
215
+ const removeFilter = () => {
216
+ var _a, _b;
217
+ const layer = model.getLayer(layerIdRef.current);
218
+ if (!layer) {
219
+ return;
220
+ }
221
+ const appliedFilters = ((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.appliedFilters) || [];
222
+ const logicalOp = ((_b = layer.filters) === null || _b === void 0 ? void 0 : _b.logicalOp) || 'all';
223
+ // This is the only way to add a 'between' filter so
224
+ // find the index of the existing 'between' filter
225
+ const betweenFilterIndex = appliedFilters.findIndex(filter => filter.operator === 'between');
226
+ if (betweenFilterIndex !== -1) {
227
+ // If found, replace the existing filter
228
+ appliedFilters.splice(betweenFilterIndex, 1);
229
+ }
230
+ // Apply the updated filters to the layer
231
+ layer.filters = { logicalOp, appliedFilters };
232
+ model.triggerLayerUpdate(layerIdRef.current, layer);
233
+ };
234
+ const playAnimation = () => {
235
+ // Clear any existing interval first
236
+ if (intervalRef.current) {
237
+ clearInterval(intervalRef.current);
238
+ }
239
+ const incrementValue = () => {
240
+ setCurrentValue(prev => {
241
+ // Calculate next value with safety bounds
242
+ const nextValue = prev + step;
243
+ // Clear interval if we've reached the max
244
+ // step is subtracted to keep range values correct
245
+ if (nextValue >= minMax.max - step && intervalRef.current) {
246
+ clearInterval(intervalRef.current);
247
+ return minMax.max - step;
248
+ }
249
+ return nextValue;
250
+ });
251
+ };
252
+ // Start animation
253
+ intervalRef.current = setInterval(incrementValue, 1000 / fps);
254
+ };
255
+ const pauseAnimation = () => {
256
+ if (intervalRef.current) {
257
+ clearInterval(intervalRef.current);
258
+ }
259
+ };
260
+ return (React.createElement("div", { className: "jp-gis-temporal-slider-container" },
261
+ React.createElement("div", { className: "jp-gis-temporal-slider-row" },
262
+ React.createElement("div", null,
263
+ React.createElement("label", { htmlFor: "time-feature-select" }, "Feature: "),
264
+ React.createElement("select", { id: "time-feature-select", onChange: e => {
265
+ setSelectedFeature(e.target.value);
266
+ } }, validFeatures.map(feature => {
267
+ return (React.createElement("option", { value: feature, selected: selectedFeature === feature }, feature));
268
+ }))),
269
+ React.createElement("div", null,
270
+ React.createElement("span", null, "Current Frame:"),
271
+ ' ',
272
+ millisecondsToDateString(range.start, dateFormat),
273
+ " \u2264 ",
274
+ React.createElement("span", null, "t"),
275
+ " \u2264",
276
+ ' ',
277
+ millisecondsToDateString(range.end, dateFormat))),
278
+ React.createElement("div", { className: "jp-gis-temporal-slider-row" },
279
+ React.createElement("div", { className: "jp-gis-temporal-slider-controls" },
280
+ React.createElement("div", { className: "jp-gis-temporal-slider-sub-controls" },
281
+ React.createElement(Button, { appearance: "neutral", scale: "medium", onClick: pauseAnimation },
282
+ React.createElement(FontAwesomeIcon, { icon: faPause })),
283
+ React.createElement(Button, { appearance: "neutral", scale: "medium", onClick: playAnimation },
284
+ React.createElement(FontAwesomeIcon, { icon: faPlay }))),
285
+ React.createElement("div", { className: "jp-gis-temporal-slider-sub-controls", style: { minWidth: 0 } },
286
+ React.createElement("label", { htmlFor: "fps-number-input" }, "FPS:"),
287
+ React.createElement("input", { name: "fps-number-input", type: "number", value: fps, onChange: e => setFps(+e.target.value) }))),
288
+ React.createElement("div", null,
289
+ React.createElement(Slider, { min: minMax.min, max: minMax.max - step, value: currentValue, valueAsNumber: currentValue, step: step, onChange: handleChange, className: "jp-gis-temporal-slider" }))),
290
+ React.createElement("div", { className: "jp-gis-temporal-slider-row" },
291
+ React.createElement("div", null,
292
+ React.createElement("span", null, "Range: "),
293
+ millisecondsToDateString(minMax.min, dateFormat),
294
+ " ",
295
+ React.createElement("span", null, "to "),
296
+ millisecondsToDateString(minMax.max, dateFormat)),
297
+ React.createElement("div", null,
298
+ React.createElement("label", { htmlFor: "time-step-select" }, "Step: "),
299
+ React.createElement("select", { id: "time-step-select", onChange: e => {
300
+ setStep(+e.target.value);
301
+ } }, Object.entries(validSteps).map(([key, val]) => (React.createElement("option", { key: key, selected: val === step, value: val }, key))))))));
302
+ };
303
+ export default TemporalSlider;