@jupytergis/base 0.4.4 → 0.5.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 (38) hide show
  1. package/lib/annotations/components/AnnotationFloater.d.ts +1 -4
  2. package/lib/annotations/components/AnnotationFloater.js +11 -6
  3. package/lib/annotations/model.d.ts +1 -0
  4. package/lib/annotations/model.js +9 -0
  5. package/lib/commands.js +50 -221
  6. package/lib/constants.d.ts +2 -16
  7. package/lib/constants.js +9 -24
  8. package/lib/icons.d.ts +8 -0
  9. package/lib/icons.js +40 -0
  10. package/lib/index.d.ts +1 -0
  11. package/lib/index.js +1 -0
  12. package/lib/mainview/mainView.d.ts +6 -1
  13. package/lib/mainview/mainView.js +127 -11
  14. package/lib/menus.d.ts +4 -0
  15. package/lib/menus.js +45 -0
  16. package/lib/panelview/annotationPanel.js +2 -2
  17. package/lib/panelview/components/identify-panel/IdentifyPanel.js +29 -7
  18. package/lib/panelview/components/layers.js +1 -0
  19. package/lib/panelview/leftpanel.js +0 -8
  20. package/lib/panelview/rightpanel.js +1 -0
  21. package/lib/toolbar/widget.js +44 -57
  22. package/lib/tools.d.ts +1 -1
  23. package/lib/tools.js +15 -7
  24. package/lib/widget.d.ts +1 -0
  25. package/lib/widget.js +3 -0
  26. package/package.json +2 -2
  27. package/style/icons/book_open.svg +19 -0
  28. package/style/icons/clock-solid.svg +17 -0
  29. package/style/icons/geolocation.svg +15 -0
  30. package/style/icons/info-solid.svg +20 -0
  31. package/style/icons/raster.svg +3 -2
  32. package/style/icons/target_with_center.svg +9 -0
  33. package/style/icons/target_without_center.svg +17 -0
  34. package/style/icons/terminal_toolbar.svg +12 -0
  35. package/style/icons/vector_square.svg +21 -0
  36. package/style/leftPanel.css +0 -1
  37. package/lib/panelview/components/sources.d.ts +0 -10
  38. package/lib/panelview/components/sources.js +0 -147
@@ -6,6 +6,7 @@ import { ContextMenu } from '@lumino/widgets';
6
6
  import { Collection, Map as OlMap, View, getUid } from 'ol';
7
7
  //@ts-expect-error no types for ol-pmtiles
8
8
  import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
9
+ import Feature from 'ol/Feature';
9
10
  import { ScaleLine } from 'ol/control';
10
11
  import { singleClick } from 'ol/events/condition';
11
12
  import { GeoJSON, MVT } from 'ol/format';
@@ -31,6 +32,7 @@ import CollaboratorPointers from './CollaboratorPointers';
31
32
  import { FollowIndicator } from './FollowIndicator';
32
33
  import TemporalSlider from './TemporalSlider';
33
34
  import { Spinner } from './spinner';
35
+ import { Point } from 'ol/geom';
34
36
  export class MainView extends React.Component {
35
37
  constructor(props) {
36
38
  super(props);
@@ -114,7 +116,8 @@ export class MainView extends React.Component {
114
116
  zoom: (_a = this._Map.getView().getZoom()) !== null && _a !== void 0 ? _a : 0,
115
117
  label: 'New annotation',
116
118
  contents: [],
117
- parent: this._Map.getViewport().id
119
+ parent: this._Map.getViewport().id,
120
+ open: true
118
121
  });
119
122
  },
120
123
  label: 'Add annotation',
@@ -418,6 +421,17 @@ export class MainView extends React.Component {
418
421
  this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
419
422
  this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
420
423
  this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
424
+ this._model.geolocationChanged.connect(this._handleGeolocationChanged, this);
425
+ this._model.flyToGeometrySignal.connect(this.flyToGeometry, this);
426
+ this._model.highlightFeatureSignal.connect(this.highlightFeatureOnMap, this);
427
+ // Watch isIdentifying and clear the highlight when Identify Tool is turned off
428
+ this._model.sharedModel.awareness.on('change', () => {
429
+ var _a;
430
+ const isIdentifying = this._model.isIdentifying;
431
+ if (!isIdentifying && this._highlightLayer) {
432
+ (_a = this._highlightLayer.getSource()) === null || _a === void 0 ? void 0 : _a.clear();
433
+ }
434
+ });
421
435
  this.state = {
422
436
  id: this._mainViewModel.id,
423
437
  lightTheme: isLightTheme(),
@@ -439,7 +453,12 @@ export class MainView extends React.Component {
439
453
  }
440
454
  async componentDidMount() {
441
455
  window.addEventListener('resize', this._handleWindowResize);
442
- await this.generateScene();
456
+ const options = this._model.getOptions();
457
+ const center = options.longitude !== undefined && options.latitude !== undefined
458
+ ? fromLonLat([options.longitude, options.latitude])
459
+ : [0, 0];
460
+ const zoom = options.zoom !== undefined ? options.zoom : 1;
461
+ await this.generateScene(center, zoom);
443
462
  this.addContextMenu();
444
463
  this._mainViewModel.initSignal();
445
464
  if (window.jupytergisMaps !== undefined && this._documentPath) {
@@ -457,14 +476,14 @@ export class MainView extends React.Component {
457
476
  this._model.clientStateChanged.disconnect(this._onClientSharedStateChanged, this);
458
477
  this._mainViewModel.dispose();
459
478
  }
460
- async generateScene() {
479
+ async generateScene(center, zoom) {
461
480
  if (this.divRef.current) {
462
481
  this._Map = new OlMap({
463
482
  target: this.divRef.current,
464
483
  layers: [],
465
484
  view: new View({
466
- center: [0, 0],
467
- zoom: 1
485
+ center,
486
+ zoom
468
487
  }),
469
488
  controls: [new ScaleLine()]
470
489
  });
@@ -1064,6 +1083,86 @@ export class MainView extends React.Component {
1064
1083
  }
1065
1084
  }
1066
1085
  }
1086
+ flyToGeometry(sender, geometry) {
1087
+ if (!geometry || typeof geometry.getExtent !== 'function') {
1088
+ console.warn('Invalid geometry for flyToGeometry:', geometry);
1089
+ return;
1090
+ }
1091
+ const view = this._Map.getView();
1092
+ const extent = geometry.getExtent();
1093
+ view.fit(extent, {
1094
+ padding: [50, 50, 50, 50],
1095
+ duration: 1000,
1096
+ maxZoom: 16
1097
+ });
1098
+ }
1099
+ highlightFeatureOnMap(sender, featureOrGeometry) {
1100
+ const geometry = (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry.geometry) ||
1101
+ (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry._geometry) ||
1102
+ featureOrGeometry;
1103
+ if (!geometry) {
1104
+ console.warn('No geometry found in feature:', featureOrGeometry);
1105
+ return;
1106
+ }
1107
+ const isOlGeometry = typeof geometry.getCoordinates === 'function';
1108
+ const parsedGeometry = isOlGeometry
1109
+ ? geometry
1110
+ : new GeoJSON().readGeometry(geometry, {
1111
+ featureProjection: this._Map.getView().getProjection()
1112
+ });
1113
+ const olFeature = new Feature(Object.assign({ geometry: parsedGeometry }, (geometry !== featureOrGeometry ? featureOrGeometry : {})));
1114
+ if (!this._highlightLayer) {
1115
+ this._highlightLayer = new VectorLayer({
1116
+ source: new VectorSource(),
1117
+ style: feature => {
1118
+ var _a;
1119
+ const geomType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
1120
+ switch (geomType) {
1121
+ case 'Point':
1122
+ case 'MultiPoint':
1123
+ return new Style({
1124
+ image: new Circle({
1125
+ radius: 6,
1126
+ fill: new Fill({ color: 'rgba(255, 255, 0, 0.8)' }),
1127
+ stroke: new Stroke({ color: '#ff0', width: 2 })
1128
+ })
1129
+ });
1130
+ case 'LineString':
1131
+ case 'MultiLineString':
1132
+ return new Style({
1133
+ stroke: new Stroke({
1134
+ color: 'rgba(255, 255, 0, 0.8)',
1135
+ width: 3
1136
+ })
1137
+ });
1138
+ case 'Polygon':
1139
+ case 'MultiPolygon':
1140
+ return new Style({
1141
+ stroke: new Stroke({
1142
+ color: '#f00',
1143
+ width: 2
1144
+ }),
1145
+ fill: new Fill({
1146
+ color: 'rgba(255, 255, 0, 0.8)'
1147
+ })
1148
+ });
1149
+ default:
1150
+ return new Style({
1151
+ stroke: new Stroke({
1152
+ color: '#000',
1153
+ width: 2
1154
+ })
1155
+ });
1156
+ }
1157
+ },
1158
+ zIndex: 999
1159
+ });
1160
+ this._Map.addLayer(this._highlightLayer);
1161
+ }
1162
+ const source = this._highlightLayer.getSource();
1163
+ source === null || source === void 0 ? void 0 : source.clear();
1164
+ source === null || source === void 0 ? void 0 : source.addFeature(olFeature);
1165
+ }
1067
1166
  /**
1068
1167
  * Wait for all layers to be loaded.
1069
1168
  */
@@ -1302,7 +1401,7 @@ export class MainView extends React.Component {
1302
1401
  // Check if the id is an annotation
1303
1402
  const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
1304
1403
  if (annotation) {
1305
- this._moveToPosition(annotation.position, annotation.zoom);
1404
+ this._flyToPosition(annotation.position, annotation.zoom);
1306
1405
  return;
1307
1406
  }
1308
1407
  // The id is a layer
@@ -1334,15 +1433,18 @@ export class MainView extends React.Component {
1334
1433
  }
1335
1434
  _moveToPosition(center, zoom, duration = 1000) {
1336
1435
  const view = this._Map.getView();
1436
+ view.setZoom(zoom);
1437
+ view.setCenter([center.x, center.y]);
1337
1438
  // Zoom needs to be set before changing center
1338
1439
  if (!view.animate === undefined) {
1339
1440
  view.animate({ zoom, duration });
1340
1441
  view.animate({ center: [center.x, center.y], duration });
1341
1442
  }
1342
- else {
1343
- view.setZoom(zoom);
1344
- view.setCenter([center.x, center.y]);
1345
- }
1443
+ }
1444
+ _flyToPosition(center, zoom, duration = 1000) {
1445
+ const view = this._Map.getView();
1446
+ view.animate({ zoom, duration });
1447
+ view.animate({ center: [center.x, center.y], duration });
1346
1448
  }
1347
1449
  _onPointerMove(e) {
1348
1450
  const pixel = this._Map.getEventPixel(e);
@@ -1378,6 +1480,10 @@ export class MainView extends React.Component {
1378
1480
  // last element is alpha
1379
1481
  bandValues['Alpha'] = data[data.length - 1];
1380
1482
  this._model.syncIdentifiedFeatures([bandValues], this._mainViewModel.id);
1483
+ const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
1484
+ const point = new Point(coordinate);
1485
+ // trigger highlight via signal
1486
+ this._model.highlightFeatureSignal.emit(point);
1381
1487
  break;
1382
1488
  }
1383
1489
  }
@@ -1404,6 +1510,16 @@ export class MainView extends React.Component {
1404
1510
  feature.set(`${selectedFeature}ms`, parsedTime);
1405
1511
  });
1406
1512
  }
1513
+ _handleGeolocationChanged(sender, newPosition) {
1514
+ const view = this._Map.getView();
1515
+ const zoom = view.getZoom();
1516
+ if (zoom) {
1517
+ this._flyToPosition(newPosition, zoom);
1518
+ }
1519
+ else {
1520
+ throw new Error('Could not move to geolocation, because current zoom is not defined.');
1521
+ }
1522
+ }
1407
1523
  render() {
1408
1524
  return (React.createElement(React.Fragment, null,
1409
1525
  Object.entries(this.state.annotations).map(([key, annotation]) => {
@@ -1415,7 +1531,7 @@ export class MainView extends React.Component {
1415
1531
  left: screenPosition.x,
1416
1532
  top: screenPosition.y
1417
1533
  }, className: 'jGIS-Popup-Wrapper' },
1418
- React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel, open: false }))));
1534
+ React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel }))));
1419
1535
  }),
1420
1536
  React.createElement("div", { className: "jGIS-Mainview-Container" },
1421
1537
  this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
package/lib/menus.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { Menu } from '@lumino/widgets';
2
+ import { CommandRegistry } from '@lumino/commands';
3
+ export declare const vectorSubMenu: (commands: CommandRegistry) => Menu;
4
+ export declare const rasterSubMenu: (commands: CommandRegistry) => Menu;
package/lib/menus.js ADDED
@@ -0,0 +1,45 @@
1
+ import { Menu } from '@lumino/widgets';
2
+ import { CommandIDs } from './constants';
3
+ import { rasterIcon } from './icons';
4
+ export const vectorSubMenu = (commands) => {
5
+ const subMenu = new Menu({ commands });
6
+ subMenu.title.label = 'Add Vector Layer';
7
+ subMenu.title.iconClass = 'fa fa-vector-square';
8
+ subMenu.id = 'jp-gis-toolbar-vector-menu';
9
+ subMenu.addItem({
10
+ type: 'command',
11
+ command: CommandIDs.newVectorTileEntry
12
+ });
13
+ subMenu.addItem({
14
+ type: 'command',
15
+ command: CommandIDs.newGeoJSONEntry
16
+ });
17
+ subMenu.addItem({
18
+ type: 'command',
19
+ command: CommandIDs.newShapefileEntry
20
+ });
21
+ return subMenu;
22
+ };
23
+ export const rasterSubMenu = (commands) => {
24
+ const subMenu = new Menu({ commands });
25
+ subMenu.title.label = 'Add Raster Layer';
26
+ subMenu.title.icon = rasterIcon;
27
+ subMenu.id = 'jp-gis-toolbar-raster-menu';
28
+ subMenu.addItem({
29
+ type: 'command',
30
+ command: CommandIDs.newRasterEntry
31
+ });
32
+ subMenu.addItem({
33
+ type: 'command',
34
+ command: CommandIDs.newHillshadeEntry
35
+ });
36
+ subMenu.addItem({
37
+ type: 'command',
38
+ command: CommandIDs.newImageEntry
39
+ });
40
+ subMenu.addItem({
41
+ type: 'command',
42
+ command: CommandIDs.newGeoTiffEntry
43
+ });
44
+ return subMenu;
45
+ };
@@ -29,14 +29,14 @@ export class AnnotationsPanel extends Component {
29
29
  React.createElement(Annotation, { rightPanelModel: this._rightPanelModel, annotationModel: this._annotationModel, itemId: id }),
30
30
  React.createElement("hr", { className: "jGIS-Annotations-Separator" })));
31
31
  });
32
- return React.createElement("div", null, annotations);
32
+ return React.createElement("div", { className: "jgis-scrollable" }, annotations);
33
33
  }
34
34
  }
35
35
  export class Annotations extends PanelWithToolbar {
36
36
  constructor(options) {
37
37
  super({});
38
38
  this.title.label = 'Annotations';
39
- this.addClass('jGIS-Annotations');
39
+ this.addClass('jgis-scrollable');
40
40
  this._annotationModel = options.annotationModel;
41
41
  this._rightPanelModel = options.rightPanelModel;
42
42
  this._widget = ReactWidget.create(React.createElement(AnnotationsPanel, { rightPanelModel: this._rightPanelModel, annotationModel: this._annotationModel }));
@@ -1,6 +1,8 @@
1
1
  import { LabIcon, ReactWidget, caretDownIcon } from '@jupyterlab/ui-components';
2
2
  import { Panel } from '@lumino/widgets';
3
3
  import React, { useEffect, useRef, useState } from 'react';
4
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
+ import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons';
4
6
  export class IdentifyPanel extends Panel {
5
7
  constructor(options) {
6
8
  super();
@@ -9,6 +11,7 @@ export class IdentifyPanel extends Panel {
9
11
  this.id = 'jupytergis::identifyPanel';
10
12
  this.title.caption = 'Identify';
11
13
  this.title.label = 'Identify';
14
+ this.addClass('jgis-scrollable');
12
15
  this.addWidget(ReactWidget.create(React.createElement(IdentifyPanelComponent, { controlPanelModel: this._model, tracker: this._tracker })));
13
16
  }
14
17
  }
@@ -80,8 +83,13 @@ const IdentifyPanelComponent = ({ controlPanelModel, tracker }) => {
80
83
  jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.clientStateChanged.disconnect(handleClientStateChanged);
81
84
  };
82
85
  }, [jgisModel]);
86
+ const highlightFeatureOnMap = (feature) => {
87
+ var _a, _b;
88
+ (_a = jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.highlightFeatureSignal) === null || _a === void 0 ? void 0 : _a.emit(feature);
89
+ const geometry = feature.geometry || feature._geometry;
90
+ (_b = jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.flyToGeometrySignal) === null || _b === void 0 ? void 0 : _b.emit(geometry);
91
+ };
83
92
  const toggleFeatureVisibility = (index) => {
84
- console.log('visibleFeatures', visibleFeatures);
85
93
  setVisibleFeatures(prev => (Object.assign(Object.assign({}, prev), { [index]: !prev[index] })));
86
94
  };
87
95
  return (React.createElement("div", { className: "jgis-identify-wrapper", style: {
@@ -90,14 +98,28 @@ const IdentifyPanelComponent = ({ controlPanelModel, tracker }) => {
90
98
  : 'unset'
91
99
  } }, features &&
92
100
  Object.values(features).map((feature, featureIndex) => (React.createElement("div", { key: featureIndex, className: "jgis-identify-grid-item" },
93
- React.createElement("div", { className: "jgis-identify-grid-item-header", onClick: () => toggleFeatureVisibility(featureIndex) },
94
- React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `jp-gis-layerGroupCollapser${visibleFeatures[featureIndex] ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
95
- React.createElement("span", null,
96
- "Feature ",
97
- featureIndex + 1,
98
- ":")),
101
+ React.createElement("div", { className: "jgis-identify-grid-item-header" },
102
+ React.createElement("span", { onClick: () => toggleFeatureVisibility(featureIndex) },
103
+ React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `jp-gis-layerGroupCollapser${visibleFeatures[featureIndex] ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
104
+ React.createElement("span", null,
105
+ "Feature ",
106
+ featureIndex + 1)),
107
+ (() => {
108
+ const isRasterFeature = !feature.geometry &&
109
+ !feature._geometry &&
110
+ typeof (feature === null || feature === void 0 ? void 0 : feature.x) !== 'number' &&
111
+ typeof (feature === null || feature === void 0 ? void 0 : feature.y) !== 'number';
112
+ return (React.createElement("button", { className: "jgis-highlight-button", onClick: e => {
113
+ e.stopPropagation();
114
+ highlightFeatureOnMap(feature);
115
+ }, title: isRasterFeature
116
+ ? 'Highlight not available for raster features'
117
+ : 'Highlight feature on map', disabled: isRasterFeature },
118
+ React.createElement(FontAwesomeIcon, { icon: faMagnifyingGlass })));
119
+ })()),
99
120
  visibleFeatures[featureIndex] && (React.createElement(React.Fragment, null, Object.entries(feature)
100
121
  .filter(([key, value]) => typeof value !== 'object' || value === null)
122
+ .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
101
123
  .map(([key, value]) => (React.createElement("div", { key: key, className: "jgis-identify-grid-body" },
102
124
  React.createElement("strong", null,
103
125
  key,
@@ -94,6 +94,7 @@ function LayersBodyComponent(props) {
94
94
  };
95
95
  model === null || model === void 0 ? void 0 : model.sharedModel.layersChanged.connect(updateLayers);
96
96
  model === null || model === void 0 ? void 0 : model.sharedModel.layerTreeChanged.connect(updateLayers);
97
+ updateLayers();
97
98
  return () => {
98
99
  model === null || model === void 0 ? void 0 : model.sharedModel.layersChanged.disconnect(updateLayers);
99
100
  model === null || model === void 0 ? void 0 : model.sharedModel.layerTreeChanged.disconnect(updateLayers);
@@ -1,6 +1,5 @@
1
1
  import { SidePanel } from '@jupyterlab/ui-components';
2
2
  import { LayersPanel } from './components/layers';
3
- import { SourcesPanel } from './components/sources';
4
3
  import { ControlPanelHeader } from './header';
5
4
  import { FilterPanel } from './components/filter-panel/Filter';
6
5
  import { CommandIDs } from '../constants';
@@ -62,13 +61,6 @@ export class LeftPanelWidget extends SidePanel {
62
61
  this._commands = options.commands;
63
62
  const header = new ControlPanelHeader();
64
63
  this.header.addWidget(header);
65
- const sourcesPanel = new SourcesPanel({
66
- model: this._model,
67
- onSelect: this._onSelect
68
- });
69
- sourcesPanel.title.caption = 'Sources';
70
- sourcesPanel.title.label = 'Sources';
71
- this.addWidget(sourcesPanel);
72
64
  const layerTree = new LayersPanel({
73
65
  model: this._model,
74
66
  state: this._state,
@@ -30,6 +30,7 @@ export class RightPanelWidget extends SidePanel {
30
30
  });
31
31
  identifyPanel.title.caption = 'Identify';
32
32
  identifyPanel.title.label = 'Identify';
33
+ identifyPanel.addClass('jgis-scrollable');
33
34
  this.addWidget(identifyPanel);
34
35
  this._model.documentChanged.connect((_, changed) => {
35
36
  if (changed) {
@@ -1,9 +1,10 @@
1
1
  import { CommandToolbarButton } from '@jupyterlab/apputils';
2
- import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon, terminalIcon, undoIcon } from '@jupyterlab/ui-components';
3
- import { Menu, Widget } from '@lumino/widgets';
2
+ import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon, undoIcon } from '@jupyterlab/ui-components';
3
+ import { Widget } from '@lumino/widgets';
4
4
  import * as React from 'react';
5
5
  import { CommandIDs } from '../constants';
6
- import { rasterIcon } from '../icons';
6
+ import { terminalToolbarIcon } from '../icons';
7
+ import { rasterSubMenu, vectorSubMenu } from '../menus';
7
8
  import { UsersItem } from './usertoolbaritem';
8
9
  export const TOOLBAR_SEPARATOR_CLASS = 'jGIS-Toolbar-Separator';
9
10
  export const TOOLBAR_GROUPNAME_CLASS = 'jGIS-Toolbar-GroupName';
@@ -21,75 +22,48 @@ export class ToolbarWidget extends ReactiveToolbar {
21
22
  super();
22
23
  this.addClass('jGIS-toolbar-widget');
23
24
  if (options.commands) {
24
- this.addItem('undo', new CommandToolbarButton({
25
+ const undoButton = new CommandToolbarButton({
25
26
  id: CommandIDs.undo,
26
27
  label: '',
27
28
  icon: undoIcon,
28
29
  commands: options.commands
29
- }));
30
- this.addItem('redo', new CommandToolbarButton({
30
+ });
31
+ this.addItem('undo', undoButton);
32
+ undoButton.node.dataset.testid = 'undo-button';
33
+ const redoButton = new CommandToolbarButton({
31
34
  id: CommandIDs.redo,
32
35
  label: '',
33
36
  icon: redoIcon,
34
37
  commands: options.commands
35
- }));
38
+ });
39
+ this.addItem('redo', redoButton);
36
40
  this.addItem('separator0', new Separator());
37
- this.addItem('Toggle console', new CommandToolbarButton({
41
+ const toggleConsoleButton = new CommandToolbarButton({
38
42
  id: CommandIDs.toggleConsole,
39
43
  commands: options.commands,
40
44
  label: '',
41
- icon: terminalIcon
42
- }));
45
+ icon: terminalToolbarIcon
46
+ });
47
+ this.addItem('Toggle console', toggleConsoleButton);
48
+ toggleConsoleButton.node.dataset.testid = 'toggle-console-button';
43
49
  this.addItem('separator1', new Separator());
44
- this.addItem('openLayerBrowser', new CommandToolbarButton({
50
+ const openLayersBrowserButton = new CommandToolbarButton({
45
51
  id: CommandIDs.openLayerBrowser,
46
52
  label: '',
47
53
  commands: options.commands
48
- }));
49
- this.addItem('newRasterEntry', new CommandToolbarButton({
50
- id: CommandIDs.newRasterEntry,
51
- label: '',
52
- commands: options.commands
53
- }));
54
- this.addItem('newVectorTileEntry', new CommandToolbarButton({
55
- id: CommandIDs.newVectorTileEntry,
56
- label: '',
57
- commands: options.commands
58
- }));
59
- // vector sub menu
60
- const vectorSubMenu = new Menu({ commands: options.commands });
61
- vectorSubMenu.title.label = 'Add Vector Layer';
62
- vectorSubMenu.title.iconClass = 'fa fa-vector-square';
63
- vectorSubMenu.id = 'jp-gis-toolbar-vector-menu';
64
- vectorSubMenu.addItem({
65
- type: 'command',
66
- command: CommandIDs.newGeoJSONEntry
67
- });
68
- vectorSubMenu.addItem({
69
- type: 'command',
70
- command: CommandIDs.newShapefileLayer
71
- });
72
- //raster submenu
73
- const rasterSubMenu = new Menu({ commands: options.commands });
74
- rasterSubMenu.title.label = 'Add Raster Layer';
75
- rasterSubMenu.title.icon = rasterIcon;
76
- rasterSubMenu.id = 'jp-gis-toolbar-raster-menu';
77
- rasterSubMenu.addItem({
78
- type: 'command',
79
- command: CommandIDs.newHillshadeEntry
80
- });
81
- rasterSubMenu.addItem({
82
- type: 'command',
83
- command: CommandIDs.newImageEntry
84
- });
85
- rasterSubMenu.addItem({
86
- type: 'command',
87
- command: CommandIDs.newGeoTiffEntry
88
54
  });
55
+ this.addItem('openLayerBrowser', openLayersBrowserButton);
56
+ openLayersBrowserButton.node.dataset.testid = 'open-layers-browser';
89
57
  const NewSubMenu = new MenuSvg({ commands: options.commands });
90
58
  NewSubMenu.title.label = 'Add Layer';
91
- NewSubMenu.addItem({ type: 'submenu', submenu: rasterSubMenu });
92
- NewSubMenu.addItem({ type: 'submenu', submenu: vectorSubMenu });
59
+ NewSubMenu.addItem({
60
+ type: 'submenu',
61
+ submenu: rasterSubMenu(options.commands)
62
+ });
63
+ NewSubMenu.addItem({
64
+ type: 'submenu',
65
+ submenu: vectorSubMenu(options.commands)
66
+ });
93
67
  const NewEntryButton = new ToolbarButton({
94
68
  icon: addIcon,
95
69
  noFocusOnClick: false,
@@ -101,18 +75,31 @@ export class ToolbarWidget extends ReactiveToolbar {
101
75
  NewSubMenu.open(bbox.x, bbox.bottom);
102
76
  }
103
77
  });
78
+ NewEntryButton.node.dataset.testid = 'new-entry-button';
104
79
  this.addItem('New', NewEntryButton);
105
80
  this.addItem('separator2', new Separator());
106
- this.addItem('identify', new CommandToolbarButton({
81
+ const geolocationButton = new CommandToolbarButton({
82
+ id: CommandIDs.getGeolocation,
83
+ commands: options.commands,
84
+ label: ''
85
+ });
86
+ this.addItem('Geolocation', geolocationButton);
87
+ geolocationButton.node.dataset.testid = 'geolocation-button';
88
+ const identifyButton = new CommandToolbarButton({
107
89
  id: CommandIDs.identify,
108
90
  label: '',
109
91
  commands: options.commands
110
- }));
111
- this.addItem('temporalController', new CommandToolbarButton({
92
+ });
93
+ this.addItem('identify', identifyButton);
94
+ identifyButton.node.dataset.testid = 'identify-button';
95
+ const temporalControllerButton = new CommandToolbarButton({
112
96
  id: CommandIDs.temporalController,
113
97
  label: '',
114
98
  commands: options.commands
115
- }));
99
+ });
100
+ this.addItem('temporalController', temporalControllerButton);
101
+ temporalControllerButton.node.dataset.testid =
102
+ 'temporal-controller-button';
116
103
  this.addItem('spacer', ReactiveToolbar.createSpacerItem());
117
104
  // Users
118
105
  this.addItem('users', ReactWidget.create(React.createElement(UsersItem, { model: options.model })));
package/lib/tools.d.ts CHANGED
@@ -65,7 +65,7 @@ export declare const getFromIndexedDB: (key: string) => Promise<{
65
65
  */
66
66
  export declare const loadGeoTiff: (sourceInfo: {
67
67
  url?: string | undefined;
68
- }, file?: Contents.IModel | null) => Promise<{
68
+ }, model: IJupyterGISModel, file?: Contents.IModel | null) => Promise<{
69
69
  file: any;
70
70
  metadata: any;
71
71
  sourceUrl: string;
package/lib/tools.js CHANGED
@@ -301,11 +301,19 @@ export const getFromIndexedDB = async (key) => {
301
301
  request.onerror = () => reject(request.error);
302
302
  });
303
303
  };
304
- const fetchWithProxies = async (url, parseResponse) => {
304
+ const fetchWithProxies = async (url, model, parseResponse) => {
305
+ let settings = null;
306
+ try {
307
+ settings = await model.getSettings();
308
+ }
309
+ catch (e) {
310
+ console.warn('Failed to get settings from model. Falling back.', e);
311
+ }
312
+ const proxyUrl = settings && settings.proxyUrl ? settings.proxyUrl : 'https://corsproxy.io';
305
313
  const proxyUrls = [
306
314
  url, // Direct fetch
307
315
  `/jupytergis_core/proxy?url=${encodeURIComponent(url)}`, // Internal proxy
308
- `https://corsproxy.io/?url=${encodeURIComponent(url)}` // External proxy
316
+ `${proxyUrl}/?url=${encodeURIComponent(url)}` // External proxy
309
317
  ];
310
318
  for (const proxyUrl of proxyUrls) {
311
319
  try {
@@ -328,7 +336,7 @@ const fetchWithProxies = async (url, parseResponse) => {
328
336
  * @param sourceInfo object containing the URL of the GeoTIFF file.
329
337
  * @returns A promise that resolves to the file as a Blob, or undefined .
330
338
  */
331
- export const loadGeoTiff = async (sourceInfo, file) => {
339
+ export const loadGeoTiff = async (sourceInfo, model, file) => {
332
340
  if (!(sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url)) {
333
341
  return null;
334
342
  }
@@ -347,7 +355,7 @@ export const loadGeoTiff = async (sourceInfo, file) => {
347
355
  }
348
356
  let fileBlob = null;
349
357
  if (!file) {
350
- fileBlob = await fetchWithProxies(url, async (response) => response.blob());
358
+ fileBlob = await fetchWithProxies(url, model, async (response) => response.blob());
351
359
  if (!fileBlob) {
352
360
  showErrorMessage('Network error', `Failed to fetch ${url}`);
353
361
  throw new Error(`Failed to fetch ${url}`);
@@ -403,7 +411,7 @@ export const loadFile = async (fileInfo) => {
403
411
  if (cached) {
404
412
  return cached.file;
405
413
  }
406
- const geojson = await fetchWithProxies(filepath, async (response) => {
414
+ const geojson = await fetchWithProxies(filepath, model, async (response) => {
407
415
  const arrayBuffer = await response.arrayBuffer();
408
416
  return shp(arrayBuffer);
409
417
  });
@@ -419,7 +427,7 @@ export const loadFile = async (fileInfo) => {
419
427
  if (cached) {
420
428
  return cached.file;
421
429
  }
422
- const geojson = await fetchWithProxies(filepath, async (response) => response.json());
430
+ const geojson = await fetchWithProxies(filepath, model, async (response) => response.json());
423
431
  if (geojson) {
424
432
  await saveToIndexedDB(filepath, geojson);
425
433
  return geojson;
@@ -476,7 +484,7 @@ export const loadFile = async (fileInfo) => {
476
484
  }
477
485
  case 'GeoTiffSource': {
478
486
  if (typeof file.content === 'string') {
479
- const tiff = loadGeoTiff({ url: filepath }, file);
487
+ const tiff = loadGeoTiff({ url: filepath }, model, file);
480
488
  return tiff;
481
489
  }
482
490
  else {
package/lib/widget.d.ts CHANGED
@@ -53,6 +53,7 @@ export declare class JupyterGISPanel extends SplitPanel {
53
53
  dispose(): void;
54
54
  get currentViewModel(): MainViewModel;
55
55
  get consolePanel(): ConsolePanel | undefined;
56
+ get consoleOpened(): boolean;
56
57
  executeConsole(): void;
57
58
  removeConsole(): void;
58
59
  toggleConsole(jgisPath: string): Promise<void>;
package/lib/widget.js CHANGED
@@ -116,6 +116,9 @@ export class JupyterGISPanel extends SplitPanel {
116
116
  var _a;
117
117
  return (_a = this._consoleView) === null || _a === void 0 ? void 0 : _a.consolePanel;
118
118
  }
119
+ get consoleOpened() {
120
+ return this._consoleOpened;
121
+ }
119
122
  executeConsole() {
120
123
  if (this._consoleView) {
121
124
  this._consoleView.execute();