@jupytergis/base 0.14.0 → 0.15.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 (62) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +1 -1
  2. package/lib/commands/BaseCommandIDs.js +1 -1
  3. package/lib/commands/index.js +28 -34
  4. package/lib/constants.js +1 -0
  5. package/lib/dialogs/symbology/classificationModes.js +12 -16
  6. package/lib/dialogs/symbology/colorRampUtils.d.ts +47 -3
  7. package/lib/dialogs/symbology/colorRampUtils.js +112 -13
  8. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +6 -14
  9. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +2 -2
  10. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.js +3 -11
  11. package/lib/dialogs/symbology/components/color_ramp/RgbaColorPicker.d.ts +13 -0
  12. package/lib/dialogs/symbology/components/color_ramp/RgbaColorPicker.js +98 -0
  13. package/lib/dialogs/symbology/components/color_stops/StopContainer.js +3 -1
  14. package/lib/dialogs/symbology/components/color_stops/StopRow.d.ts +1 -1
  15. package/lib/dialogs/symbology/components/color_stops/StopRow.js +12 -7
  16. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -1
  17. package/lib/dialogs/symbology/symbologyUtils.d.ts +2 -2
  18. package/lib/dialogs/symbology/symbologyUtils.js +58 -40
  19. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +14 -2
  20. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +6 -5
  21. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +3 -1
  22. package/lib/dialogs/symbology/vector_layer/types/Canonical.js +70 -5
  23. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +81 -34
  24. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +155 -43
  25. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +31 -16
  26. package/lib/formbuilder/formselectors.js +4 -1
  27. package/lib/formbuilder/objectform/components/WmsTileSourceUrlInput.d.ts +3 -0
  28. package/lib/formbuilder/objectform/components/WmsTileSourceUrlInput.js +84 -0
  29. package/lib/formbuilder/objectform/source/index.d.ts +1 -0
  30. package/lib/formbuilder/objectform/source/index.js +1 -0
  31. package/lib/formbuilder/objectform/source/wmsTileSource.d.ts +4 -0
  32. package/lib/formbuilder/objectform/source/wmsTileSource.js +78 -0
  33. package/lib/formbuilder/objectform/useSchemaFormState.d.ts +1 -1
  34. package/lib/mainview/mainView.d.ts +3 -1
  35. package/lib/mainview/mainView.js +170 -23
  36. package/lib/menus.js +4 -0
  37. package/lib/panelview/components/layers.js +19 -2
  38. package/lib/panelview/components/legendItem.js +14 -4
  39. package/lib/panelview/filter-panel/Filter.d.ts +3 -0
  40. package/lib/panelview/filter-panel/Filter.js +9 -9
  41. package/lib/panelview/leftpanel.js +0 -7
  42. package/lib/panelview/story-maps/SpectaPanel.js +2 -2
  43. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +1 -2
  44. package/lib/panelview/story-maps/StoryViewerPanel.js +1 -1
  45. package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +2 -1
  46. package/lib/panelview/story-maps/components/SpectaDesktopView.js +4 -4
  47. package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +1 -0
  48. package/lib/panelview/story-maps/hooks/useStoryMap.js +3 -0
  49. package/lib/stacBrowser/components/filter-extension/QueryableComboBox.js +61 -20
  50. package/lib/stacBrowser/hooks/useStacFilterExtension.d.ts +1 -1
  51. package/lib/stacBrowser/hooks/useStacFilterExtension.js +195 -111
  52. package/lib/stacBrowser/hooks/useStacSearch.d.ts +1 -0
  53. package/lib/stacBrowser/hooks/useStacSearch.js +18 -10
  54. package/lib/tools.d.ts +1 -1
  55. package/lib/tools.js +3 -3
  56. package/lib/types.d.ts +7 -1
  57. package/package.json +5 -2
  58. package/style/shared/button.css +2 -5
  59. package/style/shared/input.css +2 -2
  60. package/style/shared/tabs.css +2 -2
  61. package/style/storyPanel.css +7 -0
  62. package/style/symbologyDialog.css +45 -1
@@ -9,11 +9,11 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { JupyterGISModel, } from '@jupytergis/schema';
12
+ import { JupyterGISModel, DEFAULT_PROJECTION, } from '@jupytergis/schema';
13
13
  import { showErrorMessage } from '@jupyterlab/apputils';
14
14
  import { CommandRegistry } from '@lumino/commands';
15
15
  import { UUID } from '@lumino/coreutils';
16
- import { ContextMenu } from '@lumino/widgets';
16
+ import { ContextMenu, Menu } from '@lumino/widgets';
17
17
  import { Collection, Map as OlMap, View, getUid, } from 'ol';
18
18
  import Feature from 'ol/Feature';
19
19
  import { FullScreen, ScaleLine, Zoom } from 'ol/control';
@@ -27,7 +27,7 @@ import TileLayer from 'ol/layer/Tile';
27
27
  import { fromLonLat, get as getProjection, toLonLat, transformExtent, } from 'ol/proj';
28
28
  import { register } from 'ol/proj/proj4.js';
29
29
  import RenderFeature, { toGeometry } from 'ol/render/Feature';
30
- import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource, Tile as TileSource, } from 'ol/source';
30
+ import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, TileWMS as TileWMSSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource, Tile as TileSource, } from 'ol/source';
31
31
  import Static from 'ol/source/ImageStatic';
32
32
  import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
33
33
  //@ts-expect-error no types for ol-pmtiles
@@ -161,11 +161,54 @@ export class MainView extends React.Component {
161
161
  });
162
162
  },
163
163
  });
164
+ this._commands.addCommand('Copy-Coordinates-Map-CRS', {
165
+ label: () => {
166
+ if (!this._Map || !this._clickCoords) {
167
+ return 'Map CRS';
168
+ }
169
+ const proj = this._Map.getView().getProjection().getCode();
170
+ const coord = this._clickCoords;
171
+ return `Map CRS — ${proj} (${coord[0].toFixed(0)}E, ${coord[1].toFixed(0)}N)`;
172
+ },
173
+ execute: async () => {
174
+ const coord = this._clickCoords;
175
+ const text = `${coord[0].toFixed(0)}, ${coord[1].toFixed(0)}`;
176
+ await navigator.clipboard.writeText(text);
177
+ },
178
+ });
179
+ this._commands.addCommand('Copy-Coordinates-LonLat', {
180
+ label: () => {
181
+ if (!this._Map || !this._clickCoords) {
182
+ return 'Latitude/Longitude';
183
+ }
184
+ const lonLat = toLonLat(this._clickCoords, this._Map.getView().getProjection());
185
+ return `Latitude/Longitude: (${lonLat[1].toFixed(6)}N, ${lonLat[0].toFixed(6)}E)`;
186
+ },
187
+ execute: async () => {
188
+ const lonLat = toLonLat(this._clickCoords, this._Map.getView().getProjection());
189
+ const text = `${lonLat[1].toFixed(6)}, ${lonLat[0].toFixed(6)}`;
190
+ await navigator.clipboard.writeText(text);
191
+ },
192
+ });
164
193
  this._contextMenu.addItem({
165
194
  command: CommandIDs.addAnnotation,
166
195
  selector: '.ol-viewport',
167
196
  rank: 1,
168
197
  });
198
+ const copyCoordinatesMenu = new Menu({ commands: this._commands });
199
+ copyCoordinatesMenu.title.label = 'Copy Coordinates';
200
+ copyCoordinatesMenu.addItem({
201
+ command: 'Copy-Coordinates-Map-CRS',
202
+ });
203
+ copyCoordinatesMenu.addItem({
204
+ command: 'Copy-Coordinates-LonLat',
205
+ });
206
+ this._contextMenu.addItem({
207
+ type: 'submenu',
208
+ submenu: copyCoordinatesMenu,
209
+ selector: '.ol-viewport',
210
+ rank: 2,
211
+ });
169
212
  };
170
213
  this.vectorLayerStyleRuleBuilder = (layer) => {
171
214
  var _a, _b;
@@ -213,6 +256,70 @@ export class MainView extends React.Component {
213
256
  }
214
257
  const newStyle = Object.assign(Object.assign({}, defaultStyle), layerParams.color);
215
258
  layerStyle.style = newStyle;
259
+ // When fallbackColor[3] === 0, add an OL filter so features that would be
260
+ // drawn with a transparent color are excluded from rendering entirely.
261
+ // alpha === 0 is the contract: the default TRANSPARENT [0,0,0,0] triggers
262
+ // this automatically, and a future dedicated picker option can set it.
263
+ const symbologyState = layerParams.symbologyState;
264
+ if (Array.isArray(symbologyState === null || symbologyState === void 0 ? void 0 : symbologyState.fallbackColor) &&
265
+ symbologyState.fallbackColor[3] === 0) {
266
+ let matchFilter;
267
+ if (symbologyState.renderType === 'Categorized') {
268
+ const fillExpr = layerParams.color['fill-color'];
269
+ if (Array.isArray(fillExpr) &&
270
+ fillExpr[0] === 'case' &&
271
+ fillExpr.length >= 4) {
272
+ // Extract conditions from ['case', cond, val, cond, val, ..., fallback],
273
+ // skipping any whose matched output color is also fully transparent.
274
+ const conditions = [];
275
+ for (let i = 1; i < fillExpr.length - 1; i += 2) {
276
+ const output = fillExpr[i + 1];
277
+ if (Array.isArray(output) && output[3] === 0) {
278
+ continue;
279
+ }
280
+ conditions.push(fillExpr[i]);
281
+ }
282
+ // If every stop is transparent, use a never-true filter to hide all.
283
+ matchFilter =
284
+ conditions.length === 0
285
+ ? ['==', 0, 1]
286
+ : conditions.length === 1
287
+ ? conditions[0]
288
+ : ['any', ...conditions];
289
+ }
290
+ }
291
+ else if (symbologyState.renderType === 'Graduated') {
292
+ const fillExpr = layerParams.color['fill-color'];
293
+ // Graduated fill is ['case', ['has', field], interpolateExpr, fallback].
294
+ // Features missing the attribute fall through to the fallback, so filter
295
+ // them out with the same ['has', field] condition.
296
+ if (Array.isArray(fillExpr) &&
297
+ fillExpr[0] === 'case' &&
298
+ Array.isArray(fillExpr[1]) &&
299
+ fillExpr[1][0] === 'has') {
300
+ matchFilter = fillExpr[1];
301
+ }
302
+ }
303
+ else if (symbologyState.renderType === 'Canonical') {
304
+ const fillExpr = layerParams.color['fill-color'];
305
+ // Canonical fill is ['coalesce', ['get', field], fallback].
306
+ // Features missing the attribute fall through to the fallback, so filter
307
+ // them out with ['has', field]. Note: features that have the field but
308
+ // with a non-color value also get the fallback — that gap is accepted.
309
+ if (Array.isArray(fillExpr) &&
310
+ fillExpr[0] === 'coalesce' &&
311
+ Array.isArray(fillExpr[1]) &&
312
+ fillExpr[1][0] === 'get') {
313
+ matchFilter = ['has', fillExpr[1][1]];
314
+ }
315
+ }
316
+ if (matchFilter) {
317
+ // Combine with any existing user-applied filter.
318
+ layerStyle.filter = layerStyle.filter
319
+ ? ['all', layerStyle.filter, matchFilter]
320
+ : matchFilter;
321
+ }
322
+ }
216
323
  return [layerStyle];
217
324
  };
218
325
  /**
@@ -268,7 +375,7 @@ export class MainView extends React.Component {
268
375
  * to work with the temporal controller
269
376
  */
270
377
  this.handleTemporalController = (id, layer) => {
271
- var _a, _b, _c, _d, _e, _f;
378
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
272
379
  const selectedLayer = (_c = (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
273
380
  // Temporal Controller shouldn't be active if more than one layer is selected
274
381
  if (!selectedLayer || Object.keys(selectedLayer).length !== 1) {
@@ -286,13 +393,13 @@ export class MainView extends React.Component {
286
393
  const activeFilter = layer.filters.appliedFilters[0];
287
394
  // Save original features on first filter application
288
395
  if (!Object.keys(this._originalFeatures).includes(id)) {
289
- this._originalFeatures[id] = source.getFeatures();
396
+ this._originalFeatures[id] = (_e = source.getFeatures()) !== null && _e !== void 0 ? _e : [];
290
397
  }
291
398
  // clear current features
292
399
  source.clear();
293
- const startTime = (_e = activeFilter.betweenMin) !== null && _e !== void 0 ? _e : 0;
294
- const endTime = (_f = activeFilter.betweenMax) !== null && _f !== void 0 ? _f : 1000;
295
- const filteredFeatures = this._originalFeatures[id].filter(feature => {
400
+ const startTime = (_f = activeFilter.betweenMin) !== null && _f !== void 0 ? _f : 0;
401
+ const endTime = (_g = activeFilter.betweenMax) !== null && _g !== void 0 ? _g : 1000;
402
+ const filteredFeatures = ((_h = this._originalFeatures[id]) !== null && _h !== void 0 ? _h : []).filter(feature => {
296
403
  const featureTime = feature.get(activeFilter.feature);
297
404
  return featureTime >= startTime && featureTime <= endTime;
298
405
  });
@@ -302,7 +409,7 @@ export class MainView extends React.Component {
302
409
  }
303
410
  else {
304
411
  // Restore original features when no filters are applied
305
- source.addFeatures(this._originalFeatures[id]);
412
+ source.addFeatures((_j = this._originalFeatures[id]) !== null && _j !== void 0 ? _j : []);
306
413
  delete this._originalFeatures[id];
307
414
  }
308
415
  };
@@ -421,6 +528,11 @@ export class MainView extends React.Component {
421
528
  this._setupSpectaMode = () => {
422
529
  this._removeAllInteractions();
423
530
  this._setupStoryScrollListener();
531
+ // Ensure keybindings have a focused target in Specta mode.
532
+ window.requestAnimationFrame(() => {
533
+ var _a;
534
+ (_a = this.mainViewRef.current) === null || _a === void 0 ? void 0 : _a.focus();
535
+ });
424
536
  };
425
537
  this._removeAllInteractions = () => {
426
538
  // Remove all default interactions
@@ -555,6 +667,7 @@ export class MainView extends React.Component {
555
667
  });
556
668
  this.setState(old => (Object.assign(Object.assign({}, old), { annotations: newState })));
557
669
  };
670
+ this._lastPointerCoord = null;
558
671
  this._syncPointer = throttle((coordinates) => {
559
672
  const pointer = {
560
673
  coordinates: { x: coordinates[0], y: coordinates[1] },
@@ -597,6 +710,7 @@ export class MainView extends React.Component {
597
710
  };
598
711
  this._isPositionInitialized = false;
599
712
  this.divRef = React.createRef(); // Reference of render div
713
+ this.mainViewRef = React.createRef();
600
714
  this.controlsToolbarRef = React.createRef();
601
715
  this.spectaContainerRef = React.createRef();
602
716
  this.storyViewerPanelRef = React.createRef();
@@ -683,13 +797,15 @@ export class MainView extends React.Component {
683
797
  this._updateCenter = debounce(this.updateCenter, 100);
684
798
  }
685
799
  async componentDidMount() {
800
+ var _a;
686
801
  window.addEventListener('resize', this._handleWindowResize);
687
802
  const options = this._model.getOptions();
803
+ const projection = (_a = options.projection) !== null && _a !== void 0 ? _a : DEFAULT_PROJECTION;
688
804
  const center = options.longitude !== undefined && options.latitude !== undefined
689
- ? fromLonLat([options.longitude, options.latitude])
805
+ ? fromLonLat([options.longitude, options.latitude], projection)
690
806
  : [0, 0];
691
807
  const zoom = options.zoom !== undefined ? options.zoom : 1;
692
- await this.generateMap(center, zoom);
808
+ await this.generateMap(center, zoom, projection);
693
809
  this._mainViewModel.initSignal();
694
810
  if (window.jupytergisMaps !== undefined && this._documentPath) {
695
811
  window.jupytergisMaps[this._documentPath] = this._Map;
@@ -717,7 +833,7 @@ export class MainView extends React.Component {
717
833
  this._cleanupStoryScrollListener();
718
834
  this._mainViewModel.dispose();
719
835
  }
720
- async generateMap(center, zoom) {
836
+ async generateMap(center, zoom, projection = DEFAULT_PROJECTION) {
721
837
  const layers = this._model.getLayers();
722
838
  this._initialLayersCount = Object.values(layers).filter(layer => layer.type !== 'StorySegmentLayer').length;
723
839
  const scaleLine = new ScaleLine({
@@ -741,6 +857,7 @@ export class MainView extends React.Component {
741
857
  view: new View({
742
858
  center,
743
859
  zoom,
860
+ projection,
744
861
  }),
745
862
  controls,
746
863
  });
@@ -803,11 +920,12 @@ export class MainView extends React.Component {
803
920
  }
804
921
  });
805
922
  this._Map.on('moveend', () => {
923
+ var _a;
806
924
  const currentOptions = this._model.getOptions();
807
925
  const view = this._Map.getView();
808
926
  const center = view.getCenter() || [0, 0];
809
927
  const zoom = view.getZoom() || 0;
810
- const projection = view.getProjection();
928
+ const projection = (_a = getProjection(currentOptions.projection)) !== null && _a !== void 0 ? _a : view.getProjection();
811
929
  const latLng = toLonLat(center, projection);
812
930
  const bearing = view.getRotation();
813
931
  const resolution = view.getResolution();
@@ -842,14 +960,18 @@ export class MainView extends React.Component {
842
960
  this._Map.getViewport().addEventListener('contextmenu', event => {
843
961
  event.preventDefault();
844
962
  event.stopPropagation();
845
- const coordinate = this._Map.getEventCoordinate(event);
846
- this._clickCoords = coordinate;
963
+ if (this._lastPointerCoord) {
964
+ this._clickCoords = this._lastPointerCoord;
965
+ }
847
966
  this._contextMenu.open(event);
848
967
  });
849
- this.setState(old => (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
850
- code: view.getProjection().getCode(),
851
- units: view.getProjection().getUnits(),
852
- } })));
968
+ this.setState(old => {
969
+ var _a;
970
+ return (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
971
+ code: projection,
972
+ units: ((_a = getProjection(projection)) !== null && _a !== void 0 ? _a : view.getProjection()).getUnits(),
973
+ } }));
974
+ });
853
975
  }
854
976
  }
855
977
  /**
@@ -859,7 +981,7 @@ export class MainView extends React.Component {
859
981
  * @param source - the source object.
860
982
  */
861
983
  async addSource(id, source) {
862
- var _a, _b;
984
+ var _a, _b, _c;
863
985
  let newSource;
864
986
  switch (source.type) {
865
987
  case 'RasterSource': {
@@ -1074,6 +1196,21 @@ export class MainView extends React.Component {
1074
1196
  newSource = new VectorSource({
1075
1197
  features: [marker],
1076
1198
  });
1199
+ break;
1200
+ }
1201
+ case 'WmsTileSource': {
1202
+ const sourceParameters = source.parameters;
1203
+ const url = sourceParameters.url;
1204
+ const selectedLayer = (_c = sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.params) === null || _c === void 0 ? void 0 : _c.layers;
1205
+ newSource = new TileWMSSource({
1206
+ attributions: sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.attribution,
1207
+ url,
1208
+ params: {
1209
+ LAYERS: selectedLayer,
1210
+ TILED: true,
1211
+ },
1212
+ });
1213
+ break;
1077
1214
  }
1078
1215
  }
1079
1216
  newSource.set('id', id);
@@ -1580,6 +1717,9 @@ export class MainView extends React.Component {
1580
1717
  }
1581
1718
  }
1582
1719
  _onSharedOptionsChanged() {
1720
+ if (!this._Map) {
1721
+ return;
1722
+ }
1583
1723
  // ! would prefer a model ready signal or something, this feels hacky
1584
1724
  const enableSpectaPresentation = this._model.isSpectaMode();
1585
1725
  // Handle initialization based on specta presentation state
@@ -1632,6 +1772,12 @@ export class MainView extends React.Component {
1632
1772
  if (projection !== undefined && currentProjection !== projection) {
1633
1773
  const newProjection = getProjection(projection);
1634
1774
  if (newProjection) {
1775
+ this.setState(old => ({
1776
+ viewProjection: {
1777
+ code: newProjection.getCode(),
1778
+ units: newProjection.getUnits(),
1779
+ },
1780
+ }));
1635
1781
  view = new View({ projection: newProjection });
1636
1782
  }
1637
1783
  else {
@@ -1639,6 +1785,8 @@ export class MainView extends React.Component {
1639
1785
  return;
1640
1786
  }
1641
1787
  }
1788
+ view.setRotation(bearing || 0);
1789
+ this._Map.setView(view);
1642
1790
  // Use the extent only if explicitly requested (QGIS files).
1643
1791
  if (useExtent && extent) {
1644
1792
  view.fit(extent);
@@ -1652,8 +1800,6 @@ export class MainView extends React.Component {
1652
1800
  this._model.setOptions(options);
1653
1801
  }
1654
1802
  }
1655
- view.setRotation(bearing || 0);
1656
- this._Map.setView(view);
1657
1803
  }
1658
1804
  _onViewChanged(sender, change) {
1659
1805
  // TODO SOMETHING
@@ -1932,6 +2078,7 @@ export class MainView extends React.Component {
1932
2078
  _onPointerMove(e) {
1933
2079
  const pixel = this._Map.getEventPixel(e);
1934
2080
  const coordinates = this._Map.getCoordinateFromPixel(pixel);
2081
+ this._lastPointerCoord = coordinates;
1935
2082
  this._syncPointer(coordinates);
1936
2083
  }
1937
2084
  async _addMarker(e) {
@@ -2107,7 +2254,7 @@ export class MainView extends React.Component {
2107
2254
  }),
2108
2255
  React.createElement("div", { className: "jGIS-Mainview-Container" },
2109
2256
  this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
2110
- React.createElement("div", { className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
2257
+ React.createElement("div", { ref: this.mainViewRef, className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
2111
2258
  border: this.state.remoteUser
2112
2259
  ? `solid 3px ${this.state.remoteUser.color}`
2113
2260
  : 'unset',
package/lib/menus.js CHANGED
@@ -33,6 +33,10 @@ export const rasterSubMenu = (commands) => {
33
33
  type: 'command',
34
34
  command: CommandIDs.openNewRasterDialog,
35
35
  });
36
+ subMenu.addItem({
37
+ type: 'command',
38
+ command: CommandIDs.openNewWmsDialog,
39
+ });
36
40
  subMenu.addItem({
37
41
  type: 'command',
38
42
  command: CommandIDs.openNewHillshadeDialog,
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { DOMUtils } from '@jupyterlab/apputils';
2
13
  import { Button, LabIcon, caretDownIcon, caretRightIcon, } from '@jupyterlab/ui-components';
3
14
  import React, { useEffect, useState, } from 'react';
@@ -104,8 +115,14 @@ export const LayersBodyComponent = props => {
104
115
  };
105
116
  }
106
117
  else {
107
- // If types are the same add the selection - multi-selection
108
- newSelection = Object.assign(Object.assign({}, selectedValue), { [item]: { type } });
118
+ // If types are the same modify the selection (either add or remove to multi-selection)
119
+ if (item in selectedValue) {
120
+ const _a = selectedValue, _b = item, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
121
+ newSelection = rest;
122
+ }
123
+ else {
124
+ newSelection = Object.assign(Object.assign({}, selectedValue), { [item]: { type } });
125
+ }
109
126
  }
110
127
  }
111
128
  // Set the selection
@@ -1,16 +1,18 @@
1
1
  import React, { useEffect, useState } from 'react';
2
+ import { findExprNode } from "../../dialogs/symbology/colorRampUtils";
2
3
  import { useGetSymbology } from "../../dialogs/symbology/hooks/useGetSymbology";
3
4
  export const LegendItem = ({ layerId, model }) => {
4
5
  const { symbology, isLoading, error } = useGetSymbology({ layerId, model });
5
6
  const [content, setContent] = useState(null);
6
7
  const parseColorStops = (expr) => {
7
- if (!Array.isArray(expr) || expr[0] !== 'interpolate') {
8
+ const interpolate = findExprNode(expr, 'interpolate');
9
+ if (!interpolate) {
8
10
  return [];
9
11
  }
10
12
  const stops = [];
11
- for (let i = 3; i < expr.length; i += 2) {
12
- const value = expr[i];
13
- const rgba = expr[i + 1];
13
+ for (let i = 3; i < interpolate.length; i += 2) {
14
+ const value = interpolate[i];
15
+ const rgba = interpolate[i + 1];
14
16
  const color = Array.isArray(rgba)
15
17
  ? `rgba(${rgba[0]},${rgba[1]},${rgba[2]},${rgba[3]})`
16
18
  : String(rgba);
@@ -130,6 +132,14 @@ export const LegendItem = ({ layerId, model }) => {
130
132
  }))));
131
133
  return;
132
134
  }
135
+ // Canonical
136
+ if (renderType === 'Canonical') {
137
+ setContent(React.createElement("div", { style: { padding: 6 } },
138
+ property && (React.createElement("div", { style: { fontSize: '1em' } },
139
+ React.createElement("strong", null, property))),
140
+ React.createElement("div", { style: { fontSize: '0.8em', opacity: 0.7 } }, "hex color attribute")));
141
+ return;
142
+ }
133
143
  // Categorized
134
144
  if (renderType === 'Categorized') {
135
145
  const cats = parseCaseCategories(fill || stroke);
@@ -1,7 +1,10 @@
1
1
  import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import { PromiseDelegate } from '@lumino/coreutils';
3
+ import { Signal } from '@lumino/signaling';
2
4
  import React from 'react';
3
5
  interface IFilterComponentProps {
4
6
  model: IJupyterGISModel;
7
+ okSignalPromise: PromiseDelegate<Signal<any, null>>;
5
8
  }
6
9
  declare const FilterComponent: React.FC<IFilterComponentProps>;
7
10
  export default FilterComponent;
@@ -1,9 +1,10 @@
1
1
  import { Button } from '@jupyterlab/ui-components';
2
2
  import { cloneDeep } from 'lodash';
3
3
  import React, { useEffect, useRef, useState } from 'react';
4
+ import { useOkSignal } from "../../dialogs/symbology/hooks/useOkSignal";
4
5
  import { debounce, loadFile } from "../../tools";
5
6
  import FilterRow from './FilterRow';
6
- const FilterComponent = ({ model }) => {
7
+ const FilterComponent = ({ model, okSignalPromise, }) => {
7
8
  const featuresInLayerRef = useRef({});
8
9
  const [logicalOp, setLogicalOp] = useState('all');
9
10
  const [selectedLayer, setSelectedLayer] = useState('');
@@ -48,6 +49,9 @@ const FilterComponent = ({ model }) => {
48
49
  model === null || model === void 0 ? void 0 : model.sharedOptionsChanged.disconnect(handleSharedOptionsChanged);
49
50
  };
50
51
  }, [model]);
52
+ useOkSignal(okSignalPromise, () => {
53
+ updateLayerFilters(filterRows, logicalOp);
54
+ });
51
55
  useEffect(() => {
52
56
  var _a, _b, _c, _d;
53
57
  // Reset filter stuff for new layer
@@ -141,9 +145,6 @@ const FilterComponent = ({ model }) => {
141
145
  updateLayerFilters([]);
142
146
  setFilterRows([]);
143
147
  };
144
- const submitFilter = () => {
145
- updateLayerFilters(filterRows);
146
- };
147
148
  const updateLayerFilters = (filters, op) => {
148
149
  const layer = model === null || model === void 0 ? void 0 : model.getLayer(selectedLayer);
149
150
  if (!layer) {
@@ -158,17 +159,16 @@ const FilterComponent = ({ model }) => {
158
159
  return (React.createElement(React.Fragment, null,
159
160
  shouldDisplay && (React.createElement("div", { className: "jp-gis-filter-main" },
160
161
  React.createElement("div", { id: "filter-container", className: "jp-gis-filter-select-container" },
161
- React.createElement("select", { className: "jp-mod-styled rjsf jp-gis-logical-select", onChange: handleLogicalOpChange },
162
- React.createElement("option", { key: "all", value: "all", selected: logicalOp === 'all' }, "All"),
163
- React.createElement("option", { key: "any", value: "any", selected: logicalOp === 'any' }, "Any")),
162
+ React.createElement("select", { className: "jp-mod-styled rjsf jp-gis-logical-select", value: logicalOp, onChange: handleLogicalOpChange },
163
+ React.createElement("option", { value: "all" }, "All"),
164
+ React.createElement("option", { value: "any" }, "Any")),
164
165
  filterRows.map((row, index) => (React.createElement(FilterRow, { key: index, index: index, features: featuresInLayer, filterRows: filterRows, setFilterRows: setFilterRows, deleteRow: () => deleteRow(index) })))),
165
166
  React.createElement("div", { className: "jp-gis-filter-button-container" },
166
167
  React.createElement("div", { style: {
167
168
  justifyContent: 'flex-start',
168
169
  } },
169
170
  React.createElement(Button, { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: addFilterRow, "data-testid": "add-filter-button" }, "Add"),
170
- React.createElement(Button, { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: clearFilters }, "Clear")),
171
- React.createElement(Button, { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: submitFilter }, "Submit")))),
171
+ React.createElement(Button, { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: clearFilters }, "Clear"))))),
172
172
  !shouldDisplay && (React.createElement("div", { style: { textAlign: 'center' } }, "No layer selected"))));
173
173
  };
174
174
  export default FilterComponent;
@@ -2,7 +2,6 @@ import * as React from 'react';
2
2
  import Draggable from 'react-draggable';
3
3
  import { CommandIDs } from '../constants';
4
4
  import { LayersBodyComponent } from './components/layers';
5
- import FilterComponent from './filter-panel/Filter';
6
5
  import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/components/Tabs';
7
6
  import StacPanel from '../stacBrowser/components/StacPanel';
8
7
  export const LeftPanel = (props) => {
@@ -18,9 +17,6 @@ export const LeftPanel = (props) => {
18
17
  !props.settings.stacBrowserDisabled && !storyMapPresentationMode
19
18
  ? { name: 'stac', title: 'Stac Browser' }
20
19
  : false,
21
- !props.settings.filtersDisabled && !storyMapPresentationMode
22
- ? { name: 'filters', title: 'Filters' }
23
- : false,
24
20
  !props.settings.storyMapsDisabled
25
21
  ? { name: 'segments', title: 'Segments' }
26
22
  : false,
@@ -121,7 +117,6 @@ export const LeftPanel = (props) => {
121
117
  }, [storySegmentLayerTree]);
122
118
  const allLeftTabsDisabled = props.settings.layersDisabled &&
123
119
  props.settings.stacBrowserDisabled &&
124
- props.settings.filtersDisabled &&
125
120
  props.settings.storyMapsDisabled;
126
121
  const leftPanelVisible = !props.settings.leftPanelDisabled && !allLeftTabsDisabled;
127
122
  return (React.createElement(Draggable, { handle: ".jgis-tabs-list", cancel: ".jgis-tabs-trigger", bounds: ".jGIS-Mainview-Container" },
@@ -139,8 +134,6 @@ export const LeftPanel = (props) => {
139
134
  React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state, layerTree: filteredLayerTree }))),
140
135
  !props.settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content jgis-panel-tab-content-stac-panel" },
141
136
  React.createElement(StacPanel, { model: props.model }))),
142
- !props.settings.filtersDisabled && (React.createElement(TabsContent, { value: "filters", className: "jgis-panel-tab-content" },
143
- React.createElement(FilterComponent, { model: props.model }))),
144
137
  !props.settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "segments", className: "jgis-panel-tab-content" },
145
138
  React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state, layerTree: storySegmentLayerTree })))))));
146
139
  };
@@ -5,7 +5,7 @@ import { useStoryMap } from './hooks/useStoryMap';
5
5
  export function SpectaPanel({ model, isSpecta, isMobile, onSegmentTransitionEnd, containerRef, storyViewerPanelRef, addLayer, removeLayer, }) {
6
6
  const overrideLayerEntriesRef = useRef([]);
7
7
  const segmentContainerRef = useRef(null);
8
- const { storyData, currentIndex, setIndex, handlePrev, handleNext, hasPrev, hasNext, activeSlide, layerName, } = useStoryMap({
8
+ const { storyData, currentIndex, setIndex, handlePrev, handleNext, hasPrev, hasNext, activeSlide, layerName, showGradient, } = useStoryMap({
9
9
  model,
10
10
  overrideLayerEntriesRef,
11
11
  removeLayer,
@@ -31,5 +31,5 @@ export function SpectaPanel({ model, isSpecta, isMobile, onSegmentTransitionEnd,
31
31
  if (isMobile) {
32
32
  return (React.createElement(SpectaMobileView, { model: model, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }));
33
33
  }
34
- return (React.createElement(SpectaDesktopView, { model: model, isSpecta: isSpecta, containerRef: containerRef, storyViewerPanelRef: storyViewerPanelRef, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }));
34
+ return (React.createElement(SpectaDesktopView, { model: model, isSpecta: isSpecta, containerRef: containerRef, storyViewerPanelRef: storyViewerPanelRef, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, showGradient: showGradient, setIndex: setIndex }));
35
35
  }
@@ -5,7 +5,6 @@ interface IStoryViewerPanelProps {
5
5
  model: IJupyterGISModel;
6
6
  isSpecta: boolean;
7
7
  isMobile?: boolean;
8
- className?: string;
9
8
  /** Ref for the segment container (SpectaPanel uses it for animationend). */
10
9
  segmentContainerRef?: RefObject<HTMLDivElement>;
11
10
  storyData: IJGISStoryMap | null;
@@ -41,7 +40,7 @@ export type StoryNavPlacement = 'below-title' | 'over-image' | 'subtitle-specta'
41
40
  * Story viewer (presentational). Receives story state and callbacks from parent.
42
41
  * Desktop scroll/sentinel/imperative handle live in SpectaDesktopView.
43
42
  */
44
- declare function StoryViewerPanel({ model, isSpecta, isMobile, className, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }: IStoryViewerPanelProps): React.JSX.Element;
43
+ declare function StoryViewerPanel({ model, isSpecta, isMobile, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }: IStoryViewerPanelProps): React.JSX.Element;
45
44
  declare namespace StoryViewerPanel {
46
45
  var displayName: string;
47
46
  }
@@ -20,7 +20,7 @@ function getStoryNavPlacement(isSpecta, hasImage, storyType, isMobile) {
20
20
  * Story viewer (presentational). Receives story state and callbacks from parent.
21
21
  * Desktop scroll/sentinel/imperative handle live in SpectaDesktopView.
22
22
  */
23
- function StoryViewerPanel({ model, isSpecta, isMobile = false, className, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }) {
23
+ function StoryViewerPanel({ model, isSpecta, isMobile = false, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }) {
24
24
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
25
25
  const [imageLoaded, setImageLoaded] = useState(false);
26
26
  // Prefetch image when slide changes
@@ -15,7 +15,8 @@ interface ISpectaDesktopViewProps {
15
15
  handleNext: () => void;
16
16
  hasPrev: boolean;
17
17
  hasNext: boolean;
18
+ showGradient: boolean;
18
19
  setIndex: (index: number) => void;
19
20
  }
20
- export declare function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPanelRef, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }: ISpectaDesktopViewProps): JSX.Element;
21
+ export declare function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPanelRef, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, showGradient, setIndex, }: ISpectaDesktopViewProps): JSX.Element;
21
22
  export {};
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useImperativeHandle, useRef, } from 'react';
2
2
  import StoryViewerPanel from "../StoryViewerPanel";
3
3
  import SpectaPresentationProgressBar from "../../../statusbar/SpectaPresentationProgressBar";
4
- export function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPanelRef, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }) {
4
+ export function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPanelRef, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, showGradient, setIndex, }) {
5
5
  const scrollContainerRef = useRef(null);
6
6
  const topSentinelRef = useRef(null);
7
7
  const bottomSentinelRef = useRef(null);
@@ -39,11 +39,11 @@ export function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPa
39
39
  getScrollContainer: () => scrollContainerRef.current,
40
40
  }), [handlePrev, handleNext, isSpecta, hasPrev, hasNext]);
41
41
  return (React.createElement(React.Fragment, null,
42
- React.createElement("div", { className: "jgis-specta-right-panel-container-mod jgis-right-panel-container" },
42
+ React.createElement("div", { className: "jgis-specta-right-panel-container-mod jgis-right-panel-container", style: showGradient ? undefined : { width: '25%', borderRadius: 0 } },
43
43
  React.createElement("div", { ref: containerRef, className: "jgis-specta-story-panel-container" },
44
- React.createElement("div", { ref: scrollContainerRef, className: "jgis-story-viewer-panel-specta-mod", id: "jgis-story-segment-panel" },
44
+ React.createElement("div", { ref: scrollContainerRef, className: "jgis-story-viewer-panel-specta-mod", id: "jgis-story-segment-panel", style: showGradient ? undefined : { width: 'unset' } },
45
45
  React.createElement("div", { ref: topSentinelRef, "aria-hidden": true, "data-story-scroll-sentinel": "top", style: { height: 1, minHeight: 1, pointerEvents: 'none' } }),
46
- React.createElement(StoryViewerPanel, { model: model, isSpecta: isSpecta, className: "jgis-story-viewer-panel-specta-mod", segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }),
46
+ React.createElement(StoryViewerPanel, { model: model, isSpecta: isSpecta, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }),
47
47
  React.createElement("div", { ref: bottomSentinelRef, "aria-hidden": true, "data-story-scroll-sentinel": "bottom", style: { height: 1, minHeight: 1, pointerEvents: 'none' } })))),
48
48
  React.createElement(SpectaPresentationProgressBar, { model: model })));
49
49
  }
@@ -22,6 +22,7 @@ export declare function useStoryMap({ model, overrideLayerEntriesRef, removeLaye
22
22
  storyData: IJGISStoryMap | null;
23
23
  storySegments: IJGISLayer[];
24
24
  currentIndex: number;
25
+ showGradient: boolean;
25
26
  clearOverrideLayers: () => void;
26
27
  setIndex: (index: number) => void;
27
28
  handlePrev: () => void;