@jupytergis/base 0.2.1 → 0.3.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 (49) hide show
  1. package/lib/annotations/components/Annotation.js +1 -1
  2. package/lib/commands.js +30 -1
  3. package/lib/constants.d.ts +1 -0
  4. package/lib/constants.js +1 -0
  5. package/lib/dialogs/formdialog.d.ts +5 -0
  6. package/lib/dialogs/formdialog.js +2 -2
  7. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
  8. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +27 -0
  9. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +59 -0
  10. package/lib/dialogs/symbology/hooks/useGetProperties.js +6 -1
  11. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  12. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +14 -1
  13. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
  14. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
  15. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
  16. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +84 -0
  17. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +0 -19
  18. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +18 -59
  19. package/lib/formbuilder/creationform.d.ts +5 -0
  20. package/lib/formbuilder/creationform.js +2 -2
  21. package/lib/formbuilder/editform.d.ts +1 -0
  22. package/lib/formbuilder/editform.js +8 -3
  23. package/lib/formbuilder/formselectors.js +7 -0
  24. package/lib/formbuilder/objectform/baseform.d.ts +10 -0
  25. package/lib/formbuilder/objectform/baseform.js +39 -0
  26. package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
  27. package/lib/formbuilder/objectform/fileselectorwidget.js +81 -0
  28. package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
  29. package/lib/formbuilder/objectform/geojsonsource.js +8 -24
  30. package/lib/formbuilder/objectform/layerform.d.ts +2 -0
  31. package/lib/formbuilder/objectform/layerform.js +6 -0
  32. package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
  33. package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
  34. package/lib/icons.d.ts +1 -0
  35. package/lib/icons.js +5 -0
  36. package/lib/keybindings.json +62 -0
  37. package/lib/mainview/mainView.d.ts +22 -5
  38. package/lib/mainview/mainView.js +224 -75
  39. package/lib/panelview/components/filter-panel/Filter.js +6 -1
  40. package/lib/statusbar/StatusBar.d.ts +13 -0
  41. package/lib/statusbar/StatusBar.js +52 -0
  42. package/lib/toolbar/widget.js +0 -5
  43. package/lib/tools.d.ts +40 -1
  44. package/lib/tools.js +308 -0
  45. package/package.json +16 -5
  46. package/style/base.css +1 -0
  47. package/style/icons/logo_mini_qgz.svg +31 -0
  48. package/style/leftPanel.css +8 -0
  49. package/style/statusBar.css +16 -0
@@ -1,37 +1,106 @@
1
1
  import { JupyterGISModel } from '@jupytergis/schema';
2
+ import { CommandRegistry } from '@lumino/commands';
2
3
  import { UUID } from '@lumino/coreutils';
4
+ import { ContextMenu } from '@lumino/widgets';
3
5
  import { Collection, Map as OlMap, View, getUid } from 'ol';
6
+ //@ts-expect-error no types for ol-pmtiles
7
+ import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
4
8
  import { ScaleLine } from 'ol/control';
9
+ import { singleClick } from 'ol/events/condition';
5
10
  import { GeoJSON, MVT } from 'ol/format';
6
11
  import { DragAndDrop, Select } from 'ol/interaction';
7
12
  import { Image as ImageLayer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer';
8
13
  import TileLayer from 'ol/layer/Tile';
9
- import { fromLonLat, toLonLat } from 'ol/proj';
14
+ import { fromLonLat, get as getRegisteredProjection, toLonLat, transformExtent } from 'ol/proj';
15
+ import { get as getProjection } from 'ol/proj.js';
16
+ import { register } from 'ol/proj/proj4.js';
10
17
  import Feature from 'ol/render/Feature';
11
18
  import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source';
12
19
  import Static from 'ol/source/ImageStatic';
13
- //@ts-expect-error no types for ol-pmtiles
14
- import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
15
- import { register } from 'ol/proj/proj4.js';
16
- import { get as getProjection } from 'ol/proj.js';
20
+ import TileSource from 'ol/source/Tile';
21
+ import { Circle, Fill, Stroke, Style } from 'ol/style';
17
22
  import proj4 from 'proj4';
18
- import * as React from 'react';
19
- import shp from 'shpjs';
20
- import { isLightTheme, loadGeoTIFFWithCache, throttle } from '../tools';
21
- import { Spinner } from './spinner';
22
- //@ts-expect-error no types for proj4-list
23
+ //@ts-expect-error no types for proj4list
23
24
  import proj4list from 'proj4-list';
24
- import { ContextMenu } from '@lumino/widgets';
25
- import { CommandRegistry } from '@lumino/commands';
25
+ import * as React from 'react';
26
26
  import AnnotationFloater from '../annotations/components/AnnotationFloater';
27
27
  import { CommandIDs } from '../constants';
28
- import { FollowIndicator } from './FollowIndicator';
28
+ import StatusBar from '../statusbar/StatusBar';
29
+ import { isLightTheme, loadFile, loadGeoTIFFWithCache, throttle } from '../tools';
29
30
  import CollaboratorPointers from './CollaboratorPointers';
30
- import { Circle, Fill, Stroke, Style } from 'ol/style';
31
- import { singleClick } from 'ol/events/condition';
31
+ import { FollowIndicator } from './FollowIndicator';
32
+ import { Spinner } from './spinner';
32
33
  export class MainView extends React.Component {
33
34
  constructor(props) {
34
35
  super(props);
36
+ this.createSelectInteraction = () => {
37
+ const pointStyle = new Style({
38
+ image: new Circle({
39
+ radius: 5,
40
+ fill: new Fill({
41
+ color: '#C52707'
42
+ }),
43
+ stroke: new Stroke({
44
+ color: '#171717',
45
+ width: 2
46
+ })
47
+ })
48
+ });
49
+ const lineStyle = new Style({
50
+ stroke: new Stroke({
51
+ color: '#171717',
52
+ width: 2
53
+ })
54
+ });
55
+ const polygonStyle = new Style({
56
+ fill: new Fill({ color: '#C5270780' }),
57
+ stroke: new Stroke({
58
+ color: '#171717',
59
+ width: 2
60
+ })
61
+ });
62
+ const styleFunction = (feature) => {
63
+ var _a;
64
+ const geometryType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
65
+ switch (geometryType) {
66
+ case 'Point':
67
+ case 'MultiPoint':
68
+ return pointStyle;
69
+ case 'LineString':
70
+ case 'MultiLineString':
71
+ return lineStyle;
72
+ case 'Polygon':
73
+ case 'MultiPolygon':
74
+ return polygonStyle;
75
+ }
76
+ };
77
+ const selectInteraction = new Select({
78
+ hitTolerance: 5,
79
+ multi: true,
80
+ layers: layer => {
81
+ var _a, _b;
82
+ const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
83
+ const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
84
+ if (!selectedLayers) {
85
+ return false;
86
+ }
87
+ const selectedLayerId = Object.keys(selectedLayers)[0];
88
+ return layer === this.getLayer(selectedLayerId);
89
+ },
90
+ condition: (event) => {
91
+ return singleClick(event) && this._model.isIdentifying;
92
+ },
93
+ style: styleFunction
94
+ });
95
+ selectInteraction.on('select', event => {
96
+ const identifiedFeatures = [];
97
+ selectInteraction.getFeatures().forEach(feature => {
98
+ identifiedFeatures.push(feature.getProperties());
99
+ });
100
+ this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
101
+ });
102
+ this._Map.addInteraction(selectInteraction);
103
+ };
35
104
  this.addContextMenu = () => {
36
105
  this._commands.addCommand(CommandIDs.addAnnotation, {
37
106
  execute: () => {
@@ -278,8 +347,6 @@ export class MainView extends React.Component {
278
347
  this.divRef = React.createRef(); // Reference of render div
279
348
  this._ready = false;
280
349
  this._sourceToLayerMap = new Map();
281
- proj4.defs(Array.from(proj4list));
282
- register(proj4);
283
350
  this._mainViewModel = this.props.viewModel;
284
351
  this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
285
352
  this._model = this._mainViewModel.jGISModel;
@@ -291,16 +358,20 @@ export class MainView extends React.Component {
291
358
  this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
292
359
  this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
293
360
  this._mainViewModel.jGISModel.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
294
- this._model.zoomToAnnotationSignal.connect(this._onZoomToAnnotation, this);
361
+ this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
295
362
  this.state = {
296
363
  id: this._mainViewModel.id,
297
364
  lightTheme: isLightTheme(),
298
365
  loading: true,
299
366
  firstLoad: true,
300
367
  annotations: {},
301
- clientPointers: {}
368
+ clientPointers: {},
369
+ viewProjection: { code: '', units: '' },
370
+ loadingLayer: false,
371
+ scale: 0
302
372
  };
303
373
  this._sources = [];
374
+ this._loadingLayers = new Set();
304
375
  this._commands = new CommandRegistry();
305
376
  this._contextMenu = new ContextMenu({ commands: this._commands });
306
377
  }
@@ -364,43 +435,7 @@ export class MainView extends React.Component {
364
435
  this._model.addLayer(layerId, layerModel);
365
436
  });
366
437
  this._Map.addInteraction(dragAndDropInteraction);
367
- const selectInteraction = new Select({
368
- hitTolerance: 5,
369
- multi: true,
370
- layers: layer => {
371
- var _a, _b;
372
- const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
373
- const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
374
- if (!selectedLayers) {
375
- return false;
376
- }
377
- const selectedLayerId = Object.keys(selectedLayers)[0];
378
- return layer === this.getLayer(selectedLayerId);
379
- },
380
- condition: (event) => {
381
- return singleClick(event) && this._model.isIdentifying;
382
- },
383
- style: new Style({
384
- image: new Circle({
385
- radius: 5,
386
- fill: new Fill({
387
- color: '#C52707'
388
- }),
389
- stroke: new Stroke({
390
- color: '#171717',
391
- width: 2
392
- })
393
- })
394
- })
395
- });
396
- selectInteraction.on('select', event => {
397
- const identifiedFeatures = [];
398
- selectInteraction.getFeatures().forEach(feature => {
399
- identifiedFeatures.push(feature.getProperties());
400
- });
401
- this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
402
- });
403
- this._Map.addInteraction(selectInteraction);
438
+ this.createSelectInteraction();
404
439
  const view = this._Map.getView();
405
440
  // TODO: Note for the future, will need to update listeners if view changes
406
441
  view.on('change:center', throttle(() => {
@@ -433,6 +468,7 @@ export class MainView extends React.Component {
433
468
  const projection = view.getProjection();
434
469
  const latLng = toLonLat(center, projection);
435
470
  const bearing = view.getRotation();
471
+ const resolution = view.getResolution();
436
472
  const updatedOptions = {
437
473
  latitude: latLng[1],
438
474
  longitude: latLng[0],
@@ -442,6 +478,14 @@ export class MainView extends React.Component {
442
478
  };
443
479
  updatedOptions.extent = view.calculateExtent();
444
480
  this._model.setOptions(Object.assign(Object.assign({}, currentOptions), updatedOptions));
481
+ // Calculate scale
482
+ if (resolution) {
483
+ // DPI and inches per meter values taken from OpenLayers
484
+ const dpi = 25.4 / 0.28;
485
+ const inchesPerMeter = 1000 / 25.4;
486
+ const scale = resolution * inchesPerMeter * dpi;
487
+ this.setState(old => (Object.assign(Object.assign({}, old), { scale })));
488
+ }
445
489
  });
446
490
  this._Map.on('click', this._identifyFeature.bind(this));
447
491
  this._Map
@@ -460,19 +504,10 @@ export class MainView extends React.Component {
460
504
  this._clickCoords = coordinate;
461
505
  this._contextMenu.open(event);
462
506
  });
463
- this.setState(old => (Object.assign(Object.assign({}, old), { loading: false })));
464
- }
465
- }
466
- async _loadShapefileAsGeoJSON(url) {
467
- try {
468
- const response = await fetch(`/jupytergis_core/proxy?url=${url}`);
469
- const arrayBuffer = await response.arrayBuffer();
470
- const geojson = await shp(arrayBuffer);
471
- return geojson;
472
- }
473
- catch (error) {
474
- console.error('Error loading shapefile:', error);
475
- throw error;
507
+ this.setState(old => (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
508
+ code: view.getProjection().getCode(),
509
+ units: view.getProjection().getUnits()
510
+ } })));
476
511
  }
477
512
  }
478
513
  async _loadGeoTIFFWithCache(sourceInfo) {
@@ -542,7 +577,11 @@ export class MainView extends React.Component {
542
577
  }
543
578
  case 'GeoJSONSource': {
544
579
  const data = ((_a = source.parameters) === null || _a === void 0 ? void 0 : _a.data) ||
545
- (await this._model.readGeoJSON((_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path));
580
+ (await loadFile({
581
+ filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
582
+ type: 'GeoJSONSource',
583
+ model: this._model
584
+ }));
546
585
  const format = new GeoJSON({
547
586
  featureProjection: this._Map.getView().getProjection()
548
587
  });
@@ -562,7 +601,11 @@ export class MainView extends React.Component {
562
601
  }
563
602
  case 'ShapefileSource': {
564
603
  const parameters = source.parameters;
565
- const geojson = await this._loadShapefileAsGeoJSON(parameters.path);
604
+ const geojson = await loadFile({
605
+ filepath: parameters.path,
606
+ type: 'ShapefileSource',
607
+ model: this._model
608
+ });
566
609
  const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
567
610
  const format = new GeoJSON();
568
611
  newSource = new VectorSource({
@@ -590,10 +633,15 @@ export class MainView extends React.Component {
590
633
  const maxX = bottomRight[0];
591
634
  const minY = bottomRight[1];
592
635
  const extent = [minX, minY, maxX, maxY];
636
+ const imageUrl = await loadFile({
637
+ filepath: sourceParameters.path,
638
+ type: 'ImageSource',
639
+ model: this._model
640
+ });
593
641
  newSource = new Static({
594
642
  imageExtent: extent,
595
- url: sourceParameters.url,
596
- interpolate: true,
643
+ url: imageUrl,
644
+ interpolate: false,
597
645
  crossOrigin: ''
598
646
  });
599
647
  break;
@@ -731,9 +779,12 @@ export class MainView extends React.Component {
731
779
  if (!source) {
732
780
  return;
733
781
  }
782
+ this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: true })));
783
+ this._loadingLayers.add(id);
734
784
  if (!this._sources[sourceId]) {
735
785
  await this.addSource(sourceId, source, id);
736
786
  }
787
+ this._loadingLayers.add(id);
737
788
  let newMapLayer;
738
789
  let layerParameters;
739
790
  // TODO: OpenLayers provides a bunch of sources for specific tile
@@ -800,12 +851,40 @@ export class MainView extends React.Component {
800
851
  break;
801
852
  }
802
853
  }
854
+ await this._waitForSourceReady(newMapLayer);
803
855
  // OpenLayers doesn't have name/id field so add it
804
856
  newMapLayer.set('id', id);
805
857
  // we need to keep track of which source has which layers
806
858
  this._sourceToLayerMap.set(layerParameters.source, id);
859
+ this.addProjection(newMapLayer);
860
+ this._loadingLayers.delete(id);
807
861
  return newMapLayer;
808
862
  }
863
+ addProjection(newMapLayer) {
864
+ var _a;
865
+ const sourceProjection = (_a = newMapLayer.getSource()) === null || _a === void 0 ? void 0 : _a.getProjection();
866
+ if (!sourceProjection) {
867
+ console.warn('Layer source projection is undefined or invalid');
868
+ return;
869
+ }
870
+ const projectionCode = sourceProjection.getCode();
871
+ const isProjectionRegistered = getRegisteredProjection(projectionCode);
872
+ if (!isProjectionRegistered) {
873
+ // Check if the projection exists in proj4list
874
+ if (!proj4list[projectionCode]) {
875
+ console.warn(`Projection code '${projectionCode}' not found in proj4list`);
876
+ return;
877
+ }
878
+ try {
879
+ proj4.defs([proj4list[projectionCode]]);
880
+ register(proj4);
881
+ }
882
+ catch (error) {
883
+ console.warn(`Failed to register projection '${projectionCode}'. Error: ${error.message}`);
884
+ return;
885
+ }
886
+ }
887
+ }
809
888
  /**
810
889
  * Add a layer to the map.
811
890
  *
@@ -820,6 +899,7 @@ export class MainView extends React.Component {
820
899
  }
821
900
  const newMapLayer = await this._buildMapLayer(id, layer);
822
901
  if (newMapLayer !== undefined) {
902
+ await this._waitForReady();
823
903
  this._Map.getLayers().insertAt(index, newMapLayer);
824
904
  }
825
905
  }
@@ -875,6 +955,46 @@ export class MainView extends React.Component {
875
955
  }
876
956
  }
877
957
  }
958
+ /**
959
+ * Wait for all layers to be loaded.
960
+ */
961
+ _waitForReady() {
962
+ return new Promise(resolve => {
963
+ const checkReady = () => {
964
+ if (this._loadingLayers.size === 0) {
965
+ this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
966
+ resolve();
967
+ }
968
+ else {
969
+ setTimeout(checkReady, 50);
970
+ }
971
+ };
972
+ checkReady();
973
+ });
974
+ }
975
+ /**
976
+ * Wait for a layers source state to be 'ready'
977
+ * @param layer The Layer to check
978
+ */
979
+ _waitForSourceReady(layer) {
980
+ return new Promise((resolve, reject) => {
981
+ const checkState = () => {
982
+ const state = layer.getSourceState();
983
+ if (state === 'ready') {
984
+ layer.un('change', checkState);
985
+ resolve();
986
+ }
987
+ else if (state === 'error') {
988
+ layer.un('change', checkState);
989
+ reject(new Error('Source failed to load.'));
990
+ }
991
+ };
992
+ // Listen for state changes
993
+ layer.on('change', checkState);
994
+ // Check the state immediately in case it's already 'ready'
995
+ checkState();
996
+ });
997
+ }
878
998
  /**
879
999
  * Remove a layer from the map.
880
1000
  *
@@ -1051,12 +1171,40 @@ export class MainView extends React.Component {
1051
1171
  }
1052
1172
  });
1053
1173
  }
1054
- _onZoomToAnnotation(_, id) {
1174
+ _onZoomToPosition(_, id) {
1055
1175
  var _a;
1176
+ // Check if the id is an annotation
1056
1177
  const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
1057
1178
  if (annotation) {
1058
1179
  this._moveToPosition(annotation.position, annotation.zoom);
1180
+ return;
1181
+ }
1182
+ // The id is a layer
1183
+ let extent;
1184
+ const layer = this.getLayer(id);
1185
+ const source = layer === null || layer === void 0 ? void 0 : layer.getSource();
1186
+ if (source instanceof VectorSource) {
1187
+ extent = source.getExtent();
1188
+ }
1189
+ if (source instanceof TileSource) {
1190
+ // Tiled sources don't have getExtent() so we get it from the grid
1191
+ const tileGrid = source.getTileGrid();
1192
+ extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
1059
1193
  }
1194
+ if (!extent) {
1195
+ console.warn('Layer has no extent.');
1196
+ return;
1197
+ }
1198
+ // Convert layer extent value to view projection if needed
1199
+ const sourceProjection = source === null || source === void 0 ? void 0 : source.getProjection();
1200
+ const viewProjection = this._Map.getView().getProjection();
1201
+ const transformedExtent = sourceProjection && sourceProjection !== viewProjection
1202
+ ? transformExtent(extent, sourceProjection, viewProjection)
1203
+ : extent;
1204
+ this._Map.getView().fit(transformedExtent, {
1205
+ size: this._Map.getSize(),
1206
+ duration: 500
1207
+ });
1060
1208
  }
1061
1209
  _moveToPosition(center, zoom, duration = 1000) {
1062
1210
  const view = this._Map.getView();
@@ -1132,6 +1280,7 @@ export class MainView extends React.Component {
1132
1280
  React.createElement("div", { ref: this.divRef, style: {
1133
1281
  width: '100%',
1134
1282
  height: 'calc(100%)'
1135
- } }))));
1283
+ } })),
1284
+ React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })));
1136
1285
  }
1137
1286
  }
@@ -4,6 +4,7 @@ import { cloneDeep } from 'lodash';
4
4
  import React, { useEffect, useRef, useState } from 'react';
5
5
  import { debounce, getLayerTileInfo } from '../../../tools';
6
6
  import FilterRow from './FilterRow';
7
+ import { loadFile } from '../../../tools';
7
8
  /**
8
9
  * The filters panel widget.
9
10
  */
@@ -150,7 +151,11 @@ const FilterComponent = (props) => {
150
151
  break;
151
152
  }
152
153
  case 'GeoJSONSource': {
153
- const data = await (model === null || model === void 0 ? void 0 : model.readGeoJSON((_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path));
154
+ const data = await loadFile({
155
+ filepath: (_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path,
156
+ type: 'GeoJSONSource',
157
+ model: model
158
+ });
154
159
  data === null || data === void 0 ? void 0 : data.features.forEach((feature) => {
155
160
  feature.properties &&
156
161
  addFeatureValue(feature.properties, aggregatedProperties);
@@ -0,0 +1,13 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IStatusBarProps {
4
+ jgisModel: IJupyterGISModel;
5
+ loading?: boolean;
6
+ projection?: {
7
+ code: string;
8
+ units: string;
9
+ };
10
+ scale: number;
11
+ }
12
+ declare const StatusBar: ({ jgisModel, loading, projection, scale }: IStatusBarProps) => React.JSX.Element;
13
+ export default StatusBar;
@@ -0,0 +1,52 @@
1
+ import { faGlobe, faLocationDot, faRuler } from '@fortawesome/free-solid-svg-icons';
2
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
+ import { Progress } from '@jupyter/react-components';
4
+ import React, { useEffect, useState } from 'react';
5
+ import { version } from '../../package.json'; // Adjust the path as necessary
6
+ const StatusBar = ({ jgisModel, loading, projection, scale }) => {
7
+ var _a;
8
+ const [coords, setCoords] = useState({ x: 0, y: 0 });
9
+ useEffect(() => {
10
+ const handleClientStateChanged = () => {
11
+ var _a, _b;
12
+ const pointer = (_b = (_a = jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.localState) === null || _a === void 0 ? void 0 : _a.pointer) === null || _b === void 0 ? void 0 : _b.value;
13
+ if (!pointer) {
14
+ return;
15
+ }
16
+ setCoords({ x: pointer === null || pointer === void 0 ? void 0 : pointer.coordinates.x, y: pointer === null || pointer === void 0 ? void 0 : pointer.coordinates.y });
17
+ };
18
+ jgisModel.clientStateChanged.connect(handleClientStateChanged);
19
+ return () => {
20
+ jgisModel.clientStateChanged.disconnect(handleClientStateChanged);
21
+ };
22
+ }, [jgisModel]);
23
+ return (React.createElement("div", { className: "jgis-status-bar" },
24
+ loading && (React.createElement("div", { style: { width: '16%', padding: '0 6px' } },
25
+ React.createElement(Progress, { height: 14 }))),
26
+ React.createElement("div", { className: "jgis-status-bar-item" },
27
+ React.createElement("span", null,
28
+ "jgis: ",
29
+ version)),
30
+ React.createElement("div", { className: "jgis-status-bar-item jgis-status-bar-coords" },
31
+ React.createElement(FontAwesomeIcon, { icon: faLocationDot }),
32
+ React.createElement("span", null,
33
+ ' ',
34
+ "x: ",
35
+ Math.trunc(coords.x),
36
+ " y: ",
37
+ Math.trunc(coords.y))),
38
+ React.createElement("div", { className: "jgis-status-bar-item" },
39
+ React.createElement(FontAwesomeIcon, { icon: faRuler }),
40
+ ' ',
41
+ React.createElement("span", null,
42
+ "Scale: 1: ",
43
+ Math.trunc(scale))),
44
+ React.createElement("div", { className: "jgis-status-bar-item" },
45
+ React.createElement(FontAwesomeIcon, { icon: faGlobe }),
46
+ ' ',
47
+ React.createElement("span", null, (_a = projection === null || projection === void 0 ? void 0 : projection.code) !== null && _a !== void 0 ? _a : null)),
48
+ React.createElement("div", { className: "jgis-status-bar-item" },
49
+ "Units: ", projection === null || projection === void 0 ? void 0 :
50
+ projection.units)));
51
+ };
52
+ export default StatusBar;
@@ -101,11 +101,6 @@ export class ToolbarWidget extends ReactiveToolbar {
101
101
  label: '',
102
102
  commands: options.commands
103
103
  }));
104
- options.commands.addKeyBinding({
105
- command: CommandIDs.identify,
106
- keys: ['Escape'],
107
- selector: '#main'
108
- });
109
104
  this.addItem('spacer', ReactiveToolbar.createSpacerItem());
110
105
  // Users
111
106
  this.addItem('users', ReactWidget.create(React.createElement(UsersItem, { model: options.model })));
package/lib/tools.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { VectorTile } from '@mapbox/vector-tile';
2
- import { IDict, IJGISLayerBrowserRegistry, IJGISOptions } from '@jupytergis/schema';
2
+ import { IDict, IJGISLayerBrowserRegistry, IJGISOptions, IJGISSource, IJupyterGISModel } from '@jupytergis/schema';
3
3
  export declare const debounce: (func: CallableFunction, timeout?: number) => CallableFunction;
4
4
  export declare function throttle<T extends (...args: any[]) => void>(callback: T, delay?: number): T;
5
5
  export declare function getElementFromProperty(filePath?: string | null, prop?: string | null): HTMLElement | undefined | null;
@@ -67,3 +67,42 @@ export declare const loadGeoTIFFWithCache: (sourceInfo: {
67
67
  metadata: any;
68
68
  sourceUrl: string;
69
69
  } | null>;
70
+ /**
71
+ * Generalized file reader for different source types.
72
+ *
73
+ * @param fileInfo - Object containing the file path and source type.
74
+ * @returns A promise that resolves to the file content.
75
+ */
76
+ export declare const loadFile: (fileInfo: {
77
+ filepath: string;
78
+ type: IJGISSource["type"];
79
+ model: IJupyterGISModel;
80
+ }) => Promise<any>;
81
+ /**
82
+ * Converts a base64-encoded string to a Blob.
83
+ *
84
+ * @param base64 - The base64-encoded string representing the file data.
85
+ * @param mimeType - The MIME type of the data.
86
+ * @returns A promise that resolves to a Blob representing the decoded data.
87
+ */
88
+ export declare const base64ToBlob: (base64: string, mimeType: string) => Promise<Blob>;
89
+ /**
90
+ * A mapping of file extensions to their corresponding MIME types.
91
+ */
92
+ export declare const MIME_TYPES: {
93
+ [ext: string]: string;
94
+ };
95
+ /**
96
+ * Determine the MIME type based on the file extension.
97
+ *
98
+ * @param filename - The name of the file.
99
+ * @returns A string representing the MIME type.
100
+ */
101
+ export declare const getMimeType: (filename: string) => string;
102
+ /**
103
+ * Helper to convert a string (base64) to ArrayBuffer.
104
+ *
105
+ * @param content - File content as a base64 string.
106
+ * @returns An ArrayBuffer.
107
+ */
108
+ export declare const stringToArrayBuffer: (content: string) => Promise<ArrayBuffer>;