@theseam/ui-common 2.0.1-beta.115 → 2.0.1-beta.119

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.
@@ -1138,6 +1138,13 @@ class GoogleMapsService {
1138
1138
  * an attempt.
1139
1139
  */
1140
1140
  deleteBlocked$ = this._deleteBlockedSubject.asObservable();
1141
+ /**
1142
+ * A file reached one of the import paths and could not be read. Plain
1143
+ * `Subject`, not `BehaviorSubject`: a consumer subscribing later should not
1144
+ * be handed a failure from before it was listening.
1145
+ */
1146
+ _fileImportErrorSubject = new Subject();
1147
+ fileImportError$ = this._fileImportErrorSubject.asObservable();
1141
1148
  _selectionSubject = new BehaviorSubject(null);
1142
1149
  /**
1143
1150
  * `distinctUntilChanged` collapses repeated `null`s. Several paths clear the
@@ -1193,6 +1200,7 @@ class GoogleMapsService {
1193
1200
  this._editModeSubject.complete();
1194
1201
  this._interactionModeSubject.complete();
1195
1202
  this._deleteBlockedSubject.complete();
1203
+ this._fileImportErrorSubject.complete();
1196
1204
  this._groups = undefined;
1197
1205
  this._labelsOverlay?.destroy();
1198
1206
  this._labelsOverlay = undefined;
@@ -1825,6 +1833,14 @@ class GoogleMapsService {
1825
1833
  getFileInputHandler() {
1826
1834
  return this._fileInputHandler;
1827
1835
  }
1836
+ /**
1837
+ * Reports a file the library tried to import and could not read. Called by
1838
+ * the import controls, not by consumers — a consumer that owns importing
1839
+ * through `setFileInputHandler` reports its own failures.
1840
+ */
1841
+ notifyFileImportError(file, error) {
1842
+ this._fileImportErrorSubject.next({ file, error });
1843
+ }
1828
1844
  setInteractionMode(mode) {
1829
1845
  this._model =
1830
1846
  mode === 'grouped'
@@ -2708,7 +2724,12 @@ class TheSeamGoogleMapsUploadButtonControlComponent {
2708
2724
  async _importFile(file) {
2709
2725
  const json = await readGeoFile(file);
2710
2726
  this._mapValueManager.setValue(json, MapValueSource.Input);
2711
- this._resetInput();
2727
+ }
2728
+ _reportImportError(file, error) {
2729
+ if (file === undefined) {
2730
+ return;
2731
+ }
2732
+ this._googleMaps.notifyFileImportError(file, error);
2712
2733
  }
2713
2734
  _createHiddenInput() {
2714
2735
  const fileInputElement = this._renderer.createElement('input');
@@ -2716,16 +2737,32 @@ class TheSeamGoogleMapsUploadButtonControlComponent {
2716
2737
  this._renderer.setAttribute(fileInputElement, 'hidden', '');
2717
2738
  this._renderer.setAttribute(fileInputElement, 'accept', '.json,.geojson,.shp,.zip');
2718
2739
  this._listeners.push(this._renderer.listen(fileInputElement, 'change', (event) => {
2719
- const file = this._getFile();
2740
+ let file;
2741
+ try {
2742
+ file = this._getFile();
2743
+ }
2744
+ catch (err) {
2745
+ // `_getFile` refuses a multi-file selection. Report it like any
2746
+ // other reason the chosen files could not be imported. `_getFile`
2747
+ // has already read `files` by this point, so resetting now cannot
2748
+ // disturb the read.
2749
+ this._reportImportError(this._fileInputElement.files?.[0], err);
2750
+ this._resetInput();
2751
+ return;
2752
+ }
2720
2753
  if (file === null) {
2721
2754
  return;
2722
2755
  }
2723
2756
  const fileImportHandler = this._googleMaps.getFileInputHandler();
2724
2757
  if (fileImportHandler) {
2758
+ // The consumer owns the file now, including reporting its failures.
2725
2759
  fileImportHandler(file);
2760
+ this._resetInput();
2726
2761
  }
2727
2762
  else {
2728
- this._importFile(file);
2763
+ this._importFile(file)
2764
+ .catch((err) => this._reportImportError(file, err))
2765
+ .finally(() => this._resetInput());
2729
2766
  }
2730
2767
  }));
2731
2768
  return fileInputElement;
@@ -2910,16 +2947,55 @@ class TheSeamMapFileDropComponent {
2910
2947
  event.preventDefault();
2911
2948
  event.stopPropagation();
2912
2949
  this._renderer.setStyle(this._elementRef.nativeElement, 'display', 'none');
2913
- // TODO: Show error if multiple files?
2914
- if (!this._isSupportedDataTransfer(event.dataTransfer)) {
2950
+ if (!this._isSupportedDataTransferTypes(event.dataTransfer)) {
2951
+ // Not a file drag at all — selected text, a link, an image dragged out
2952
+ // of a page. Nobody asked for an import and there is no file to name,
2953
+ // so this stays silent rather than reporting a failure.
2954
+ return;
2955
+ }
2956
+ const files = event.dataTransfer.files;
2957
+ if (files.length !== 1) {
2958
+ if (files.length > 1) {
2959
+ // Refused before any handler is consulted, exactly as the upload
2960
+ // button's `_getFile` does: `fileImportHandler` takes a single file
2961
+ // and cannot be handed several, so the consumer never sees these and
2962
+ // this output is its only way to learn the drop was rejected. Same
2963
+ // message as the button, so a consumer matching on it needs one
2964
+ // string rather than two.
2965
+ this._ngZone.run(() => this._googleMaps.notifyFileImportError(files[0], Error('Only one file can be imported at a time.')));
2966
+ }
2915
2967
  return;
2916
2968
  }
2917
2969
  const item = event.dataTransfer.items[0];
2918
2970
  const file = item.getAsFile();
2919
- readGeoFile(file).then((json) => {
2971
+ if (file === null) {
2972
+ return;
2973
+ }
2974
+ // These listeners are registered outside Angular's zone, so re-enter it
2975
+ // before calling back into anything that expects change detection — the
2976
+ // consumer's handler may well render a message from here.
2977
+ this._ngZone.run(() => this._importDroppedFile(file));
2978
+ };
2979
+ /**
2980
+ * Routes a dropped file the same way the upload button routes a chosen one,
2981
+ * so `fileImportHandler` means "the consumer owns imported files" whichever
2982
+ * way the file arrived.
2983
+ */
2984
+ _importDroppedFile(file) {
2985
+ const fileImportHandler = this._googleMaps.getFileInputHandler();
2986
+ if (fileImportHandler) {
2987
+ // The consumer owns the file now, including reporting its failures.
2988
+ fileImportHandler(file);
2989
+ return;
2990
+ }
2991
+ readGeoFile(file)
2992
+ .then((json) => {
2920
2993
  this._mapValueManager.setValue(json, MapValueSource.Input);
2994
+ })
2995
+ .catch((error) => {
2996
+ this._googleMaps.notifyFileImportError(file, error);
2921
2997
  });
2922
- };
2998
+ }
2923
2999
  _handleDragEnterEvent = (event) => {
2924
3000
  if (!this._dropAllowed()) {
2925
3001
  return;
@@ -2938,9 +3014,6 @@ class TheSeamMapFileDropComponent {
2938
3014
  _dropAllowed() {
2939
3015
  return !this._globalDragInProgress;
2940
3016
  }
2941
- _isSupportedDataTransfer(dataTransfer) {
2942
- return dataTransfer.files.length === 1 && dataTransfer.types[0] === 'Files';
2943
- }
2944
3017
  _isSupportedDataTransferTypes(dataTransfer) {
2945
3018
  return dataTransfer.types[0] === 'Files';
2946
3019
  }
@@ -3092,6 +3165,17 @@ class TheSeamGoogleMapsComponent extends _TheSeamGoogleMapsMixinBase {
3092
3165
  streetViewControlEnabled = false;
3093
3166
  allowDrawingHoleInPolygon = false;
3094
3167
  editingEnabled = true;
3168
+ /**
3169
+ * Intercepts a file the user chose or dropped, in place of the library's own
3170
+ * import handling. Covers **both** import paths: a file picked through the
3171
+ * upload button and one dropped onto the map (`fileDropEnabled` defaults to
3172
+ * `true`, so setting this now also intercepts drops a consumer relying on
3173
+ * the default previously had handled internally).
3174
+ *
3175
+ * Once called, the consumer owns the file, including reporting any failure
3176
+ * in its own UI — `fileImportError` will not fire for a file handed to this
3177
+ * handler.
3178
+ */
3095
3179
  set fileImportHandler(value) {
3096
3180
  this._googleMaps.setFileInputHandler(value);
3097
3181
  }
@@ -3161,6 +3245,15 @@ class TheSeamGoogleMapsComponent extends _TheSeamGoogleMapsMixinBase {
3161
3245
  * so the consumer can explain why; only the consumer knows the reason.
3162
3246
  */
3163
3247
  deleteBlocked = new EventEmitter();
3248
+ /**
3249
+ * A file reached one of the import paths and could not be read — a corrupt
3250
+ * archive, a `.zip` of something else, malformed GeoJSON. Emitted so the
3251
+ * consumer can say so in its own UI; the library has nowhere to show it.
3252
+ *
3253
+ * Not emitted for a file a `fileImportHandler` took: once the consumer owns
3254
+ * the file, it owns reporting the outcome too.
3255
+ */
3256
+ fileImportError = new EventEmitter();
3164
3257
  featureContextMenu;
3165
3258
  _options = {
3166
3259
  mapTypeControl: true,
@@ -3218,6 +3311,11 @@ class TheSeamGoogleMapsComponent extends _TheSeamGoogleMapsMixinBase {
3218
3311
  this._googleMaps.deleteBlocked$
3219
3312
  .pipe(tap((target) => this.deleteBlocked.emit(target)), takeUntil(this._ngUnsubscribe))
3220
3313
  .subscribe();
3314
+ // As with `deleteBlocked$`, a plain Subject with nothing to replay, so no
3315
+ // `skip(1)`.
3316
+ this._googleMaps.fileImportError$
3317
+ .pipe(tap((value) => this.fileImportError.emit(value)), takeUntil(this._ngUnsubscribe))
3318
+ .subscribe();
3221
3319
  this._contextMenuItems$ = combineLatest([
3222
3320
  this._googleMaps.editingEnabled$,
3223
3321
  // The RIGHT-CLICKED feature's group — what "Delete Field" is
@@ -3472,7 +3570,7 @@ class TheSeamGoogleMapsComponent extends _TheSeamGoogleMapsMixinBase {
3472
3570
  return this._googleMaps.isEditMode();
3473
3571
  }
3474
3572
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamGoogleMapsComponent, deps: [{ token: i0.ElementRef }, { token: i1.FocusMonitor }, { token: GoogleMapsService }, { token: MapValueManagerService }, { token: TheSeamGoogleMapsApiLoader }], target: i0.ɵɵFactoryTarget.Component });
3475
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TheSeamGoogleMapsComponent, isStandalone: false, selector: "seam-google-maps", inputs: { disabled: "disabled", value: "value", tabIndex: "tabIndex", fileDropEnabled: "fileDropEnabled", fileUploadControlEnabled: "fileUploadControlEnabled", fullscreenControlEnabled: "fullscreenControlEnabled", reCenterControlEnabled: "reCenterControlEnabled", mapTypeControlEnabled: "mapTypeControlEnabled", streetViewControlEnabled: "streetViewControlEnabled", allowDrawingHoleInPolygon: "allowDrawingHoleInPolygon", editingEnabled: "editingEnabled", fileImportHandler: "fileImportHandler", zoom: "zoom", longitude: "longitude", latitude: "latitude", padding: "padding", interactionMode: "interactionMode", featureGroupProperty: "featureGroupProperty", featureLabelProperty: "featureLabelProperty", newGroupKeyFactory: "newGroupKeyFactory", selectedGroupKey: "selectedGroupKey", canDelete: "canDelete" }, outputs: { mapReady: "mapReady", selectionChange: "selectionChange", featureHoverChange: "featureHoverChange", deleteBlocked: "deleteBlocked" }, host: { properties: { "attr.disabled": "this._attrDisabled", "attr.tabindex": "this._attrTabIndex" } }, providers: [
3573
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TheSeamGoogleMapsComponent, isStandalone: false, selector: "seam-google-maps", inputs: { disabled: "disabled", value: "value", tabIndex: "tabIndex", fileDropEnabled: "fileDropEnabled", fileUploadControlEnabled: "fileUploadControlEnabled", fullscreenControlEnabled: "fullscreenControlEnabled", reCenterControlEnabled: "reCenterControlEnabled", mapTypeControlEnabled: "mapTypeControlEnabled", streetViewControlEnabled: "streetViewControlEnabled", allowDrawingHoleInPolygon: "allowDrawingHoleInPolygon", editingEnabled: "editingEnabled", fileImportHandler: "fileImportHandler", zoom: "zoom", longitude: "longitude", latitude: "latitude", padding: "padding", interactionMode: "interactionMode", featureGroupProperty: "featureGroupProperty", featureLabelProperty: "featureLabelProperty", newGroupKeyFactory: "newGroupKeyFactory", selectedGroupKey: "selectedGroupKey", canDelete: "canDelete" }, outputs: { mapReady: "mapReady", selectionChange: "selectionChange", featureHoverChange: "featureHoverChange", deleteBlocked: "deleteBlocked", fileImportError: "fileImportError" }, host: { properties: { "attr.disabled": "this._attrDisabled", "attr.tabindex": "this._attrTabIndex" } }, providers: [
3476
3574
  MapValueManagerService,
3477
3575
  GoogleMapsService,
3478
3576
  { provide: MAP_CONTROLS_SERVICE, useClass: GoogleMapsControlsService },
@@ -3584,6 +3682,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3584
3682
  type: Output
3585
3683
  }], deleteBlocked: [{
3586
3684
  type: Output
3685
+ }], fileImportError: [{
3686
+ type: Output
3587
3687
  }], featureContextMenu: [{
3588
3688
  type: ViewChild,
3589
3689
  args: ['featureContextMenu', { static: true, read: MenuComponent }]