@eeacms/volto-arcgis-block 0.1.427 → 0.1.429

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [0.1.429](https://github.com/eea/volto-arcgis-block/compare/0.1.428...0.1.429) - 17 February 2026
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - (bug): styles for buttons in upload widget and error popup message for file size limit on hspae files [Unai Bolivar - [`d9ca59e`](https://github.com/eea/volto-arcgis-block/commit/d9ca59ec245fdc94a2d1bf678b6e3d610d5a792d)]
12
+ - (bug): reset upload widget to allow users to start from zero without finishing a process first [Unai Bolivar - [`1539a3a`](https://github.com/eea/volto-arcgis-block/commit/1539a3ada8fef0fbda5203b61ec2aed60ab014b5)]
13
+ - (bug): truncate title to 33 max char and add ellipsis for user created services and move trashcan icon to the right edge of the menu item div [Unai Bolivar - [`a31612d`](https://github.com/eea/volto-arcgis-block/commit/a31612df1ee629937e115b1fec1c50092b659626)]
14
+ - (bug): fix xml to json conversion for unsupported features and error report for features with no geometry data [Unai Bolivar - [`51774c1`](https://github.com/eea/volto-arcgis-block/commit/51774c155b59c8a0624d725ffb12cf130b1f65f1)]
15
+ - (bug): Check for match between selected service and provided service url [Unai Bolivar - [`d7247dc`](https://github.com/eea/volto-arcgis-block/commit/d7247dc29755df4e70c6b4ad3b175f0e1e29f24b)]
16
+ - (bug): Extent button in active layers bug fixed for user created services [Unai Bolivar - [`dfee085`](https://github.com/eea/volto-arcgis-block/commit/dfee085a821b9a85e21dd48ab3c7a0988d135b77)]
17
+ - (bug): fix trash can icon styles for user created services [Unai Bolivar - [`0588462`](https://github.com/eea/volto-arcgis-block/commit/05884627281671b9186608c65570e7fd95cfe999)]
18
+ ### [0.1.428](https://github.com/eea/volto-arcgis-block/compare/0.1.427...0.1.428) - 13 February 2026
19
+
7
20
  ### [0.1.427](https://github.com/eea/volto-arcgis-block/compare/0.1.426...0.1.427) - 11 February 2026
8
21
 
9
22
  ### [0.1.426](https://github.com/eea/volto-arcgis-block/compare/0.1.425...0.1.426) - 9 February 2026
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-arcgis-block",
3
- "version": "0.1.427",
3
+ "version": "0.1.429",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -344,6 +344,7 @@ class MapViewer extends React.Component {
344
344
  userServiceType: '',
345
345
  userServiceFile: null,
346
346
  uploadError: false,
347
+ uploadErrorType: 'uploadError',
347
348
  // Track current user state for comparison in componentDidUpdate
348
349
  currentUserState: props.initialUserState || {
349
350
  user_id: null,
@@ -613,10 +614,10 @@ class MapViewer extends React.Component {
613
614
  this.setState({ uploadedFile: message });
614
615
  }
615
616
 
616
- uploadFileErrorHandler = () => {
617
- this.setState({ uploadError: true });
617
+ uploadFileErrorHandler = (errorType = 'uploadError') => {
618
+ this.setState({ uploadError: true, uploadErrorType: errorType });
618
619
  setTimeout(() => {
619
- this.setState({ uploadError: false });
620
+ this.setState({ uploadError: false, uploadErrorType: 'uploadError' });
620
621
  }, 3000);
621
622
  };
622
623
 
@@ -1036,6 +1037,7 @@ class MapViewer extends React.Component {
1036
1037
  mapViewer={this}
1037
1038
  wmsServiceUrl={this.state.wmsServiceUrl}
1038
1039
  showErrorPopup={this.state.uploadError}
1040
+ showErrorPopupType={this.state.uploadErrorType}
1039
1041
  uploadUrlServiceHandler={this.uploadUrlServiceHandler}
1040
1042
  uploadFileErrorHandler={this.uploadFileErrorHandler}
1041
1043
  />
@@ -2421,12 +2421,547 @@ class MenuWidget extends React.Component {
2421
2421
  }
2422
2422
  }
2423
2423
 
2424
+ resolveRequestConfig(xml) {
2425
+ let doc = xml;
2426
+ try {
2427
+ if (typeof xml === 'string') {
2428
+ const parser = new DOMParser();
2429
+ doc = parser.parseFromString(xml, 'text/xml');
2430
+ }
2431
+ const versionSet = new Set();
2432
+ const formatSet = new Set();
2433
+ const rootVersion = doc?.documentElement?.getAttribute?.('version');
2434
+ if (rootVersion) {
2435
+ versionSet.add(rootVersion.trim());
2436
+ }
2437
+ const serviceTypeVersionNodes = doc.querySelectorAll(
2438
+ 'ServiceTypeVersion, ows\\:ServiceTypeVersion',
2439
+ );
2440
+ serviceTypeVersionNodes.forEach((node) => {
2441
+ const nodeValue = (node.textContent || '').trim();
2442
+ if (nodeValue) {
2443
+ versionSet.add(nodeValue);
2444
+ }
2445
+ });
2446
+ const acceptVersionNodes = doc.querySelectorAll(
2447
+ 'Parameter[name="AcceptVersions"] Value, ows\\:Parameter[name="AcceptVersions"] ows\\:Value',
2448
+ );
2449
+ acceptVersionNodes.forEach((node) => {
2450
+ const nodeValue = (node.textContent || '').trim();
2451
+ if (nodeValue) {
2452
+ versionSet.add(nodeValue);
2453
+ }
2454
+ });
2455
+ const outputNodes = doc.querySelectorAll(
2456
+ 'OutputFormats > Format, OutputFormat, Parameter[name="outputFormat"] Value, ows\\:Parameter[name="outputFormat"] ows\\:Value',
2457
+ );
2458
+ outputNodes.forEach((node) => {
2459
+ const nodeValue = (node.textContent || '').trim();
2460
+ if (nodeValue) {
2461
+ formatSet.add(nodeValue);
2462
+ }
2463
+ });
2464
+ const srsSet = new Set();
2465
+ const srsNodes = doc.querySelectorAll(
2466
+ 'DefaultSRS, DefaultCRS, OtherSRS, OtherCRS, Parameter[name="srsName"] Value, ows\\:Parameter[name="srsName"] ows\\:Value',
2467
+ );
2468
+ srsNodes.forEach((node) => {
2469
+ const nodeValue = (node.textContent || '').trim();
2470
+ if (nodeValue) {
2471
+ srsSet.add(nodeValue);
2472
+ }
2473
+ });
2474
+ const versionList = Array.from(versionSet);
2475
+ const formatList = Array.from(formatSet);
2476
+ const srsList = Array.from(srsSet);
2477
+ const requestVersion = versionList.includes('2.0.0')
2478
+ ? '2.0.0'
2479
+ : versionList.includes('1.1.0')
2480
+ ? '1.1.0'
2481
+ : versionList.includes('1.0.0')
2482
+ ? '1.0.0'
2483
+ : rootVersion || '2.0.0';
2484
+ const preferredJsonFormat = formatList.find((item) =>
2485
+ /application\/json|geojson|json/i.test(item),
2486
+ );
2487
+ const preferredXmlFormat = formatList.find((item) =>
2488
+ /text\/xml|xml|gml/i.test(item),
2489
+ );
2490
+ const requestSrsName = srsList.includes('EPSG:4326')
2491
+ ? 'EPSG:4326'
2492
+ : srsList[0] || 'EPSG:4326';
2493
+ return {
2494
+ requestVersion,
2495
+ requestFormat: preferredJsonFormat || preferredXmlFormat || null,
2496
+ hasJsonFormat: Boolean(preferredJsonFormat),
2497
+ requestSrsName,
2498
+ };
2499
+ } catch (e) {
2500
+ return {
2501
+ requestVersion: '2.0.0',
2502
+ requestFormat: 'application/json',
2503
+ hasJsonFormat: true,
2504
+ requestSrsName: 'EPSG:4326',
2505
+ };
2506
+ }
2507
+ }
2508
+
2509
+ resolveExceptionMessage(responseText) {
2510
+ const textValue = typeof responseText === 'string' ? responseText : '';
2511
+ if (!textValue) {
2512
+ return '';
2513
+ }
2514
+ const exceptionMatch = textValue.match(
2515
+ /<(?:\w+:)?ExceptionText>\s*([\s\S]*?)\s*<\/(?:\w+:)?ExceptionText>/i,
2516
+ );
2517
+ if (exceptionMatch && exceptionMatch[1]) {
2518
+ return exceptionMatch[1].trim();
2519
+ }
2520
+ return '';
2521
+ }
2522
+
2523
+ isNoGeometryError(error) {
2524
+ const message = ((error && error.message) || '').toLowerCase();
2525
+ return (
2526
+ message.includes('no geometry') ||
2527
+ message.includes('cannot be displayed on map')
2528
+ );
2529
+ }
2530
+
2531
+ resolveUploadErrorType(error) {
2532
+ const detailsFromRoot =
2533
+ error && error.details && Array.isArray(error.details)
2534
+ ? error.details
2535
+ : [];
2536
+ const detailsFromNested =
2537
+ error &&
2538
+ error.details &&
2539
+ error.details.details &&
2540
+ Array.isArray(error.details.details)
2541
+ ? error.details.details
2542
+ : [];
2543
+ const detailsFromResponse =
2544
+ error &&
2545
+ error.response &&
2546
+ error.response.data &&
2547
+ error.response.data.error &&
2548
+ Array.isArray(error.response.data.error.details)
2549
+ ? error.response.data.error.details
2550
+ : [];
2551
+ const errorDetails = [
2552
+ ...detailsFromRoot,
2553
+ ...detailsFromNested,
2554
+ ...detailsFromResponse,
2555
+ ];
2556
+ const normalizedDetails = errorDetails.map((detailValue) =>
2557
+ String(detailValue || '').toLowerCase(),
2558
+ );
2559
+ const hasShapefileLimit = normalizedDetails.some((detailValue) =>
2560
+ detailValue.includes('shape file exceeds the max size allowed of 2mb'),
2561
+ );
2562
+ if (hasShapefileLimit) {
2563
+ return 'shapefileLimit';
2564
+ }
2565
+ const hasFileLimit = normalizedDetails.some((detailValue) =>
2566
+ detailValue.includes('file exceeds the max size allowed of 10mb'),
2567
+ );
2568
+ if (hasFileLimit) {
2569
+ return 'fileLimit';
2570
+ }
2571
+ return 'uploadError';
2572
+ }
2573
+
2574
+ hasExceptionResponse(responseText) {
2575
+ const textValue = typeof responseText === 'string' ? responseText : '';
2576
+ if (!textValue) {
2577
+ return false;
2578
+ }
2579
+ return /<\s*(?:\w+:)?ExceptionReport\b|<\s*(?:\w+:)?Exception\b/i.test(
2580
+ textValue,
2581
+ );
2582
+ }
2583
+
2584
+ processXmlData(xmlInput) {
2585
+ let doc = xmlInput;
2586
+ if (typeof xmlInput === 'string') {
2587
+ const parser = new DOMParser();
2588
+ doc = parser.parseFromString(xmlInput, 'text/xml');
2589
+ }
2590
+ if (!doc || !doc.documentElement) {
2591
+ return null;
2592
+ }
2593
+ const processNodeData = (node) => {
2594
+ const nodeData = {
2595
+ name: node.localName || node.nodeName,
2596
+ value: null,
2597
+ attributes: {},
2598
+ children: [],
2599
+ };
2600
+ if (node.attributes && node.attributes.length) {
2601
+ Array.from(node.attributes).forEach((attributeNode) => {
2602
+ nodeData.attributes[attributeNode.name] = attributeNode.value;
2603
+ });
2604
+ }
2605
+ if (node.childNodes && node.childNodes.length) {
2606
+ Array.from(node.childNodes).forEach((childNode) => {
2607
+ if (childNode.nodeType === 1) {
2608
+ nodeData.children.push(processNodeData(childNode));
2609
+ } else if (childNode.nodeType === 3) {
2610
+ const value = (childNode.nodeValue || '').trim();
2611
+ if (value) {
2612
+ nodeData.value = value;
2613
+ }
2614
+ }
2615
+ });
2616
+ }
2617
+ return nodeData;
2618
+ };
2619
+ return processNodeData(doc.documentElement);
2620
+ }
2621
+
2622
+ resolveNodeList(nodeData, nodeName) {
2623
+ const resultList = [];
2624
+ const processNodeData = (currentNode) => {
2625
+ if (!currentNode) {
2626
+ return;
2627
+ }
2628
+ if ((currentNode.name || '').toLowerCase() === nodeName.toLowerCase()) {
2629
+ resultList.push(currentNode);
2630
+ }
2631
+ (currentNode.children || []).forEach((childNode) => {
2632
+ processNodeData(childNode);
2633
+ });
2634
+ };
2635
+ processNodeData(nodeData);
2636
+ return resultList;
2637
+ }
2638
+
2639
+ resolveCoordinateResult(valueText) {
2640
+ const textValue = (valueText || '').trim();
2641
+ if (!textValue) {
2642
+ return [];
2643
+ }
2644
+ if (textValue.includes(',')) {
2645
+ return textValue
2646
+ .trim()
2647
+ .split(/\s+/)
2648
+ .map((item) => item.split(',').map(Number))
2649
+ .filter((item) => item.length >= 2 && !item.some(Number.isNaN))
2650
+ .map((item) => [item[0], item[1]]);
2651
+ }
2652
+ const valueList = textValue
2653
+ .split(/\s+/)
2654
+ .map(Number)
2655
+ .filter((item) => !Number.isNaN(item));
2656
+ const resultList = [];
2657
+ for (let i = 0; i + 1 < valueList.length; i += 2) {
2658
+ resultList.push([valueList[i], valueList[i + 1]]);
2659
+ }
2660
+ return resultList;
2661
+ }
2662
+
2663
+ resolveGeometryResult(featureData) {
2664
+ const geometryTypeList = [
2665
+ 'Point',
2666
+ 'LineString',
2667
+ 'Polygon',
2668
+ 'MultiPoint',
2669
+ 'MultiLineString',
2670
+ 'MultiPolygon',
2671
+ ];
2672
+ const geometryNode = geometryTypeList
2673
+ .map((typeValue) => this.resolveNodeList(featureData, typeValue)[0])
2674
+ .find(Boolean);
2675
+ if (!geometryNode) {
2676
+ return null;
2677
+ }
2678
+ const nodeName = geometryNode.name;
2679
+ if (nodeName === 'Point') {
2680
+ const posNode =
2681
+ this.resolveNodeList(geometryNode, 'pos')[0] ||
2682
+ this.resolveNodeList(geometryNode, 'coordinates')[0];
2683
+ const points = this.resolveCoordinateResult(posNode?.value || '');
2684
+ if (!points.length) {
2685
+ return null;
2686
+ }
2687
+ return { type: 'Point', coordinates: points[0] };
2688
+ }
2689
+ if (nodeName === 'LineString') {
2690
+ const posListNode =
2691
+ this.resolveNodeList(geometryNode, 'posList')[0] ||
2692
+ this.resolveNodeList(geometryNode, 'coordinates')[0];
2693
+ const points = this.resolveCoordinateResult(posListNode?.value || '');
2694
+ if (!points.length) {
2695
+ return null;
2696
+ }
2697
+ return { type: 'LineString', coordinates: points };
2698
+ }
2699
+ if (nodeName === 'Polygon') {
2700
+ const ringNodes = this.resolveNodeList(geometryNode, 'LinearRing');
2701
+ const ringList = ringNodes
2702
+ .map((ringNode) => {
2703
+ const posListNode =
2704
+ this.resolveNodeList(ringNode, 'posList')[0] ||
2705
+ this.resolveNodeList(ringNode, 'coordinates')[0];
2706
+ return this.resolveCoordinateResult(posListNode?.value || '');
2707
+ })
2708
+ .filter((ringValue) => ringValue.length > 0);
2709
+ if (!ringList.length) {
2710
+ return null;
2711
+ }
2712
+ return { type: 'Polygon', coordinates: ringList };
2713
+ }
2714
+ return null;
2715
+ }
2716
+
2717
+ resolveFeatureResult(featureData) {
2718
+ const geometryResult = this.resolveGeometryResult(featureData);
2719
+ if (!geometryResult) {
2720
+ return null;
2721
+ }
2722
+ const processPropertyData = (nodeData, propertyData) => {
2723
+ if (!nodeData) {
2724
+ return;
2725
+ }
2726
+ const geometryNames = new Set([
2727
+ 'Point',
2728
+ 'LineString',
2729
+ 'Polygon',
2730
+ 'MultiPoint',
2731
+ 'MultiLineString',
2732
+ 'MultiPolygon',
2733
+ 'pos',
2734
+ 'posList',
2735
+ 'coordinates',
2736
+ 'LinearRing',
2737
+ 'exterior',
2738
+ 'interior',
2739
+ 'boundedBy',
2740
+ ]);
2741
+ if (geometryNames.has(nodeData.name)) {
2742
+ return;
2743
+ }
2744
+ if ((nodeData.children || []).length === 0 && nodeData.value) {
2745
+ if (!propertyData[nodeData.name]) {
2746
+ propertyData[nodeData.name] = nodeData.value;
2747
+ }
2748
+ return;
2749
+ }
2750
+ (nodeData.children || []).forEach((childNode) => {
2751
+ processPropertyData(childNode, propertyData);
2752
+ });
2753
+ };
2754
+ const properties = {};
2755
+ processPropertyData(featureData, properties);
2756
+ const featureId =
2757
+ featureData.attributes['gml:id'] ||
2758
+ featureData.attributes.id ||
2759
+ featureData.attributes.fid ||
2760
+ null;
2761
+ return {
2762
+ type: 'Feature',
2763
+ id: featureId,
2764
+ geometry: geometryResult,
2765
+ properties,
2766
+ };
2767
+ }
2768
+
2769
+ resolveFeatureCollection(xmlData) {
2770
+ const memberList = [
2771
+ ...this.resolveNodeList(xmlData, 'featureMember'),
2772
+ ...this.resolveNodeList(xmlData, 'member'),
2773
+ ];
2774
+ const featureList = memberList
2775
+ .map((memberData) => {
2776
+ const featureData = (memberData.children || []).find(
2777
+ (childData) => childData.name !== 'boundedBy',
2778
+ );
2779
+ if (!featureData) {
2780
+ return null;
2781
+ }
2782
+ return this.resolveFeatureResult(featureData);
2783
+ })
2784
+ .filter(Boolean);
2785
+ return {
2786
+ type: 'FeatureCollection',
2787
+ features: featureList,
2788
+ memberCount: memberList.length,
2789
+ };
2790
+ }
2791
+
2792
+ buildRequestParams(featureName, requestConfig) {
2793
+ const typeParam =
2794
+ requestConfig.requestVersion === '2.0.0' ? 'typeNames' : 'typeName';
2795
+ const params = {
2796
+ service: 'WFS',
2797
+ request: 'GetFeature',
2798
+ version: requestConfig.requestVersion,
2799
+ [typeParam]: featureName,
2800
+ srsName: requestConfig.requestSrsName,
2801
+ };
2802
+ if (requestConfig.requestVersion === '2.0.0') {
2803
+ params.count = '1000';
2804
+ } else {
2805
+ params.maxFeatures = '1000';
2806
+ }
2807
+ return params;
2808
+ }
2809
+
2424
2810
  async handleNewMapServiceLayer(viewService, serviceType, serviceSelection) {
2425
2811
  let resourceLayers = [];
2426
2812
  const proxiedUrl = this.buildProxiedUrl(viewService);
2427
2813
  try {
2428
2814
  const rawUrl = (proxiedUrl || '').trim();
2429
2815
  const baseUrl = rawUrl.split('?')[0];
2816
+ const processBboxData = (xml) => {
2817
+ const buildExtentResult = (xmin, ymin, xmax, ymax) => {
2818
+ const numericValues = [xmin, ymin, xmax, ymax].map((value) =>
2819
+ Number(value),
2820
+ );
2821
+ if (numericValues.some((value) => Number.isNaN(value))) {
2822
+ return null;
2823
+ }
2824
+ return {
2825
+ xmin: numericValues[0],
2826
+ ymin: numericValues[1],
2827
+ xmax: numericValues[2],
2828
+ ymax: numericValues[3],
2829
+ spatialReference: { wkid: 4326 },
2830
+ };
2831
+ };
2832
+ const parseCornerValues = (value) => {
2833
+ if (!value || typeof value !== 'string') {
2834
+ return [];
2835
+ }
2836
+ return value
2837
+ .trim()
2838
+ .split(/\s+/)
2839
+ .map((item) => Number(item));
2840
+ };
2841
+ try {
2842
+ let doc = xml;
2843
+ if (typeof xml === 'string') {
2844
+ const parser = new DOMParser();
2845
+ doc = parser.parseFromString(xml, 'text/xml');
2846
+ }
2847
+ if (doc && typeof doc.querySelector === 'function') {
2848
+ const wgs84Node =
2849
+ doc.querySelector('ows\\:WGS84BoundingBox') ||
2850
+ doc.querySelector('WGS84BoundingBox') ||
2851
+ doc.querySelector('wgs84boundingbox');
2852
+ if (wgs84Node) {
2853
+ const lowerCornerNode =
2854
+ wgs84Node.querySelector('ows\\:LowerCorner') ||
2855
+ wgs84Node.querySelector('LowerCorner') ||
2856
+ wgs84Node.querySelector('lowercorner');
2857
+ const upperCornerNode =
2858
+ wgs84Node.querySelector('ows\\:UpperCorner') ||
2859
+ wgs84Node.querySelector('UpperCorner') ||
2860
+ wgs84Node.querySelector('uppercorner');
2861
+ const lowerCorner = parseCornerValues(
2862
+ lowerCornerNode ? lowerCornerNode.textContent : '',
2863
+ );
2864
+ const upperCorner = parseCornerValues(
2865
+ upperCornerNode ? upperCornerNode.textContent : '',
2866
+ );
2867
+ if (lowerCorner.length >= 2 && upperCorner.length >= 2) {
2868
+ const extentResult = buildExtentResult(
2869
+ lowerCorner[0],
2870
+ lowerCorner[1],
2871
+ upperCorner[0],
2872
+ upperCorner[1],
2873
+ );
2874
+ if (extentResult) {
2875
+ return extentResult;
2876
+ }
2877
+ }
2878
+ }
2879
+
2880
+ const exGeoNode =
2881
+ doc.querySelector('EX_GeographicBoundingBox') ||
2882
+ doc.querySelector('ex_geographicboundingbox');
2883
+ if (exGeoNode) {
2884
+ const westNode =
2885
+ exGeoNode.querySelector('westBoundLongitude') ||
2886
+ exGeoNode.querySelector('ows\\:westBoundLongitude') ||
2887
+ exGeoNode.querySelector('westboundlongitude');
2888
+ const eastNode =
2889
+ exGeoNode.querySelector('eastBoundLongitude') ||
2890
+ exGeoNode.querySelector('ows\\:eastBoundLongitude') ||
2891
+ exGeoNode.querySelector('eastboundlongitude');
2892
+ const southNode =
2893
+ exGeoNode.querySelector('southBoundLatitude') ||
2894
+ exGeoNode.querySelector('ows\\:southBoundLatitude') ||
2895
+ exGeoNode.querySelector('southboundlatitude');
2896
+ const northNode =
2897
+ exGeoNode.querySelector('northBoundLatitude') ||
2898
+ exGeoNode.querySelector('ows\\:northBoundLatitude') ||
2899
+ exGeoNode.querySelector('northboundlatitude');
2900
+ const extentResult = buildExtentResult(
2901
+ westNode ? westNode.textContent : NaN,
2902
+ southNode ? southNode.textContent : NaN,
2903
+ eastNode ? eastNode.textContent : NaN,
2904
+ northNode ? northNode.textContent : NaN,
2905
+ );
2906
+ if (extentResult) {
2907
+ return extentResult;
2908
+ }
2909
+ }
2910
+ }
2911
+ } catch (e) {}
2912
+
2913
+ let xmlText = '';
2914
+ if (typeof xml === 'string') {
2915
+ xmlText = xml;
2916
+ } else if (xml && typeof xml.xml === 'string') {
2917
+ xmlText = xml.xml;
2918
+ }
2919
+ if (xmlText) {
2920
+ const lowerMatch = xmlText.match(
2921
+ /<(?:ows:)?LowerCorner>\s*([^<]+?)\s*<\/(?:ows:)?LowerCorner>/i,
2922
+ );
2923
+ const upperMatch = xmlText.match(
2924
+ /<(?:ows:)?UpperCorner>\s*([^<]+?)\s*<\/(?:ows:)?UpperCorner>/i,
2925
+ );
2926
+ if (lowerMatch && upperMatch) {
2927
+ const lowerCorner = parseCornerValues(lowerMatch[1]);
2928
+ const upperCorner = parseCornerValues(upperMatch[1]);
2929
+ if (lowerCorner.length >= 2 && upperCorner.length >= 2) {
2930
+ const extentResult = buildExtentResult(
2931
+ lowerCorner[0],
2932
+ lowerCorner[1],
2933
+ upperCorner[0],
2934
+ upperCorner[1],
2935
+ );
2936
+ if (extentResult) {
2937
+ return extentResult;
2938
+ }
2939
+ }
2940
+ }
2941
+
2942
+ const westMatch = xmlText.match(
2943
+ /<(?:ows:)?westBoundLongitude>\s*([^<]+?)\s*<\/(?:ows:)?westBoundLongitude>/i,
2944
+ );
2945
+ const eastMatch = xmlText.match(
2946
+ /<(?:ows:)?eastBoundLongitude>\s*([^<]+?)\s*<\/(?:ows:)?eastBoundLongitude>/i,
2947
+ );
2948
+ const southMatch = xmlText.match(
2949
+ /<(?:ows:)?southBoundLatitude>\s*([^<]+?)\s*<\/(?:ows:)?southBoundLatitude>/i,
2950
+ );
2951
+ const northMatch = xmlText.match(
2952
+ /<(?:ows:)?northBoundLatitude>\s*([^<]+?)\s*<\/(?:ows:)?northBoundLatitude>/i,
2953
+ );
2954
+ if (westMatch && eastMatch && southMatch && northMatch) {
2955
+ return buildExtentResult(
2956
+ westMatch[1],
2957
+ southMatch[1],
2958
+ eastMatch[1],
2959
+ northMatch[1],
2960
+ );
2961
+ }
2962
+ }
2963
+ return null;
2964
+ };
2430
2965
  const isWFS =
2431
2966
  serviceType === 'WFS' ||
2432
2967
  /service=WFS/i.test(rawUrl) ||
@@ -2449,31 +2984,73 @@ class MenuWidget extends React.Component {
2449
2984
  }),
2450
2985
  ];
2451
2986
  } else if (isWFS) {
2452
- resourceLayers = Object.entries(serviceSelection || {})
2453
- .map(([name, title]) => {
2987
+ await this.getCapabilities(viewService, 'WFS');
2988
+ const requestConfig = this.resolveRequestConfig(this.xml);
2989
+ const serviceEntries = Object.entries(serviceSelection || {});
2990
+ const layerResults = await Promise.all(
2991
+ serviceEntries.map(async ([name, title]) => {
2454
2992
  if (!name) return null;
2455
- const params = new URLSearchParams({
2456
- service: 'WFS',
2457
- request: 'GetFeature',
2458
- version: '2.0.0',
2459
- typeName: name,
2460
- outputFormat: 'application/json',
2461
- srsName: 'EPSG:4326',
2462
- }).toString();
2463
- const wfsUrl = baseUrl + '?' + params;
2993
+ const requestParams = this.buildRequestParams(name, requestConfig);
2994
+ if (requestConfig.hasJsonFormat && requestConfig.requestFormat) {
2995
+ requestParams.outputFormat = requestConfig.requestFormat;
2996
+ }
2997
+ const requestQuery = new URLSearchParams(requestParams).toString();
2998
+ const requestUrl = baseUrl + '?' + requestQuery;
2464
2999
 
2465
3000
  const id = (name || baseUrl).toUpperCase().replace(/[: ]/g, '_');
3001
+ if (requestConfig.hasJsonFormat) {
3002
+ const layer = new GeoJSONLayer({
3003
+ url: requestUrl,
3004
+ id: id,
3005
+ title: title || name,
3006
+ });
3007
+ layer.LayerId = id;
3008
+ layer.ViewService = baseUrl;
3009
+ layer.name = name;
3010
+ return layer;
3011
+ }
3012
+
3013
+ const xmlResponse = await esriRequest(requestUrl, {
3014
+ responseType: 'text',
3015
+ });
3016
+ if (this.hasExceptionResponse(xmlResponse.data)) {
3017
+ const exceptionMessage = this.resolveExceptionMessage(
3018
+ xmlResponse.data,
3019
+ );
3020
+ throw new Error(exceptionMessage || 'WFS request failed');
3021
+ }
3022
+ const xmlData = this.processXmlData(xmlResponse.data);
3023
+ const featureCollection = this.resolveFeatureCollection(xmlData);
3024
+ if (
3025
+ featureCollection &&
3026
+ featureCollection.memberCount > 0 &&
3027
+ (!featureCollection.features ||
3028
+ featureCollection.features.length === 0)
3029
+ ) {
3030
+ throw new Error(
3031
+ 'Selected WFS feature type has no geometry and cannot be displayed on map',
3032
+ );
3033
+ }
3034
+ if (!featureCollection || !featureCollection.features?.length) {
3035
+ throw new Error('No WFS features were returned for this layer');
3036
+ }
3037
+ const blobData = new Blob([JSON.stringify(featureCollection)], {
3038
+ type: 'application/json',
3039
+ });
3040
+ const blobUrl = URL.createObjectURL(blobData);
2466
3041
  const layer = new GeoJSONLayer({
2467
- url: wfsUrl,
3042
+ url: blobUrl,
2468
3043
  id: id,
2469
3044
  title: title || name,
2470
3045
  });
2471
3046
  layer.LayerId = id;
2472
3047
  layer.ViewService = baseUrl;
2473
3048
  layer.name = name;
3049
+ layer.ServiceDataUrl = blobUrl;
2474
3050
  return layer;
2475
- })
2476
- .filter(Boolean);
3051
+ }),
3052
+ );
3053
+ resourceLayers = layerResults.filter(Boolean);
2477
3054
  } else {
2478
3055
  await this.getCapabilities(viewService, 'WMS');
2479
3056
  const wmsLayers = this.parseWMSLayers(this.xml);
@@ -2503,8 +3080,16 @@ class MenuWidget extends React.Component {
2503
3080
  }),
2504
3081
  ];
2505
3082
  }
3083
+ const bboxData = processBboxData(this.xml);
3084
+ if (bboxData && resourceLayers[0]) {
3085
+ resourceLayers[0].fullExtent = bboxData;
3086
+ }
2506
3087
  } catch (error) {
2507
- this.props.uploadFileErrorHandler();
3088
+ if (this.isNoGeometryError(error)) {
3089
+ this.props.uploadFileErrorHandler('noGeometryError');
3090
+ } else {
3091
+ this.props.uploadFileErrorHandler();
3092
+ }
2508
3093
  return;
2509
3094
  }
2510
3095
 
@@ -2522,7 +3107,9 @@ class MenuWidget extends React.Component {
2522
3107
  if (typeof resourceLayer.load === 'function' && serviceType === 'WFS') {
2523
3108
  try {
2524
3109
  await resourceLayer.load();
2525
- } catch (e) {}
3110
+ } catch (e) {
3111
+ throw e;
3112
+ }
2526
3113
  }
2527
3114
  if (serviceType === 'WMS' || serviceType === 'WMTS') {
2528
3115
  const forced = (proxiedUrl || '').trim();
@@ -2565,7 +3152,11 @@ class MenuWidget extends React.Component {
2565
3152
 
2566
3153
  this.props.onServiceChange();
2567
3154
  } catch (error) {
2568
- this.props.uploadFileErrorHandler();
3155
+ if (this.isNoGeometryError(error)) {
3156
+ this.props.uploadFileErrorHandler('noGeometryError');
3157
+ } else {
3158
+ this.props.uploadFileErrorHandler();
3159
+ }
2569
3160
  return;
2570
3161
  }
2571
3162
  }
@@ -2612,7 +3203,7 @@ class MenuWidget extends React.Component {
2612
3203
  this.addUploadedFeatureCollectionToMap(featureCollection, displayTitle);
2613
3204
  this.props.onServiceChange && this.props.onServiceChange();
2614
3205
  } catch (error) {
2615
- this.props.uploadFileErrorHandler();
3206
+ this.props.uploadFileErrorHandler(this.resolveUploadErrorType(error));
2616
3207
  }
2617
3208
  }
2618
3209
 
@@ -2699,12 +3290,18 @@ class MenuWidget extends React.Component {
2699
3290
  createUserServices(serviceLayers) {
2700
3291
  const fieldset = document.getElementById('map-menu-services');
2701
3292
  if (!fieldset) return;
3293
+ const processTitleData = (titleText) => {
3294
+ if (!titleText) return titleText;
3295
+ if (titleText.length <= 33) return titleText;
3296
+ return titleText.slice(0, 33) + '...';
3297
+ };
2702
3298
 
2703
3299
  // Create an array of all layer elements
2704
3300
  const layerElements = serviceLayers.map((layer, index) => {
2705
3301
  const { LayerId, title, description } = layer;
2706
3302
  const parentIndex = this.layers[layer.id];
2707
3303
  const checkboxId = LayerId;
3304
+ const displayTitle = processTitleData(title || `Layer ${index + 1}`);
2708
3305
 
2709
3306
  return (
2710
3307
  <div
@@ -2740,14 +3337,14 @@ class MenuWidget extends React.Component {
2740
3337
  <legend className="ccl-form-legend">
2741
3338
  {description ? (
2742
3339
  <Popup
2743
- trigger={<span>{title}</span>}
3340
+ trigger={<span>{displayTitle}</span>}
2744
3341
  content={description}
2745
3342
  basic
2746
3343
  className="custom"
2747
3344
  style={{ transform: 'translateX(-4rem)' }}
2748
3345
  />
2749
3346
  ) : (
2750
- <span>{title || `Layer ${index + 1}`}</span>
3347
+ <span>{displayTitle}</span>
2751
3348
  )}
2752
3349
  </legend>
2753
3350
  </label>
@@ -2782,7 +3379,14 @@ class MenuWidget extends React.Component {
2782
3379
  }
2783
3380
 
2784
3381
  // Delete from layers object
2785
- if (this.layers[elemId]) delete this.layers[elemId];
3382
+ if (this.layers[elemId]) {
3383
+ if (this.layers[elemId].ServiceDataUrl) {
3384
+ try {
3385
+ URL.revokeObjectURL(this.layers[elemId].ServiceDataUrl);
3386
+ } catch (e) {}
3387
+ }
3388
+ delete this.layers[elemId];
3389
+ }
2786
3390
 
2787
3391
  // Remove from ArcGIS map
2788
3392
  let removeLayer = this.props.map.findLayerById(elemId) || null;
@@ -4305,7 +4909,7 @@ class MenuWidget extends React.Component {
4305
4909
  // Get the coordinates of the click on the view
4306
4910
  const proxiedUrl = this.buildProxiedUrl(url);
4307
4911
  return esriRequest(proxiedUrl, {
4308
- responseType: 'html',
4912
+ responseType: 'text',
4309
4913
  sync: 'true',
4310
4914
  query: {
4311
4915
  request: 'GetCapabilities',
@@ -4315,7 +4919,7 @@ class MenuWidget extends React.Component {
4315
4919
  .then((response) => {
4316
4920
  const xmlDoc = response.data;
4317
4921
  const parser = new DOMParser();
4318
- this.xml = parser.parseFromString(xmlDoc, 'text/html');
4922
+ this.xml = parser.parseFromString(xmlDoc, 'text/xml');
4319
4923
  })
4320
4924
  .catch(() => {});
4321
4925
  };
@@ -4654,9 +5258,36 @@ class MenuWidget extends React.Component {
4654
5258
  });
4655
5259
  this.view.goTo(myExtent);
4656
5260
  }
5261
+ } else if (serviceLayer) {
5262
+ const storedServiceExtent =
5263
+ this.layers[elem.id]?.fullExtent ||
5264
+ serviceLayer.fullExtent ||
5265
+ (this.layers[elem.id]?.fullExtents &&
5266
+ this.layers[elem.id]?.fullExtents[0]
5267
+ ? this.layers[elem.id]?.fullExtents[0]
5268
+ : null) ||
5269
+ (serviceLayer.fullExtents && serviceLayer.fullExtents[0]
5270
+ ? serviceLayer.fullExtents[0]
5271
+ : null);
5272
+ if (
5273
+ storedServiceExtent &&
5274
+ storedServiceExtent.xmin !== undefined &&
5275
+ storedServiceExtent.ymin !== undefined &&
5276
+ storedServiceExtent.xmax !== undefined &&
5277
+ storedServiceExtent.ymax !== undefined
5278
+ ) {
5279
+ BBoxes = {
5280
+ dataset: {
5281
+ xmin: Number(storedServiceExtent.xmin),
5282
+ ymin: Number(storedServiceExtent.ymin),
5283
+ xmax: Number(storedServiceExtent.xmax),
5284
+ ymax: Number(storedServiceExtent.ymax),
5285
+ },
5286
+ };
5287
+ }
4657
5288
  } else if (this.url?.toLowerCase().endsWith('mapserver')) {
4658
5289
  BBoxes = await this.parseBBOXMAPSERVER(this.layers[elem.id]);
4659
- } else if (this.url?.toLowerCase().includes('wms') || serviceLayer) {
5290
+ } else if (this.url?.toLowerCase().includes('wms')) {
4660
5291
  await this.getCapabilities(this.url, 'wms');
4661
5292
  BBoxes = this.parseBBOXWMS(this.xml);
4662
5293
  } else if (this.url?.toLowerCase().includes('wmts')) {
@@ -150,8 +150,32 @@ class UploadWidget extends React.Component {
150
150
  this.setState({ serviceUrl: event.target.value });
151
151
  };
152
152
 
153
+ buildUploadResetState = () => {
154
+ return {
155
+ serviceUrl: '',
156
+ wfsFeatures: {},
157
+ selectedFeatures: {},
158
+ selectedFile: null,
159
+ showInfoPopup: false,
160
+ infoPopupType: '',
161
+ globalDragActive: false,
162
+ };
163
+ };
164
+
153
165
  handleServiceTypeChange = (event) => {
154
- this.setState({ selectedServiceType: event.target.value });
166
+ const nextServiceType = event.target.value;
167
+ this.setState((prevState) => {
168
+ if (prevState.selectedServiceType === nextServiceType) {
169
+ return null;
170
+ }
171
+ return {
172
+ selectedServiceType: nextServiceType,
173
+ ...this.buildUploadResetState(),
174
+ };
175
+ });
176
+ if (this.fileInput && this.fileInput.current) {
177
+ this.fileInput.current.value = null;
178
+ }
155
179
  };
156
180
 
157
181
  setActiveTab(tab) {
@@ -222,6 +246,42 @@ class UploadWidget extends React.Component {
222
246
  }
223
247
  };
224
248
 
249
+ getServiceTypeFromUrl = (serviceUrl) => {
250
+ try {
251
+ const parsedUrl = new URL(serviceUrl);
252
+ const queryService = (parsedUrl.searchParams.get('service') || '')
253
+ .trim()
254
+ .toUpperCase();
255
+ if (
256
+ queryService === 'WMS' ||
257
+ queryService === 'WMTS' ||
258
+ queryService === 'WFS'
259
+ ) {
260
+ return queryService;
261
+ }
262
+ const encodedUrl = (
263
+ (parsedUrl.hostname || '') + (parsedUrl.pathname || '')
264
+ ).toLowerCase();
265
+ const serviceMatch = encodedUrl.match(
266
+ /(^|[^a-z])(wmts|wms|wfs)([^a-z]|$)/i,
267
+ );
268
+ if (serviceMatch && serviceMatch[2]) {
269
+ return serviceMatch[2].toUpperCase();
270
+ }
271
+ return null;
272
+ } catch (e) {
273
+ return null;
274
+ }
275
+ };
276
+
277
+ isServiceTypeMatchingUrl = (serviceUrl, selectedServiceType) => {
278
+ const encodedServiceType = this.getServiceTypeFromUrl(serviceUrl);
279
+ if (!encodedServiceType) {
280
+ return true;
281
+ }
282
+ return encodedServiceType === selectedServiceType;
283
+ };
284
+
225
285
  stripProtocol = (url) => {
226
286
  return (url || '').replace(/^https?:\/\//i, '');
227
287
  };
@@ -236,11 +296,163 @@ class UploadWidget extends React.Component {
236
296
  return this.getProxyBase() + this.stripProtocol(url);
237
297
  };
238
298
 
299
+ resolveNodeValue = (nodeValue) => {
300
+ if (nodeValue === null || nodeValue === undefined) {
301
+ return null;
302
+ }
303
+ if (typeof nodeValue === 'string' || typeof nodeValue === 'number') {
304
+ const textValue = String(nodeValue).trim();
305
+ return textValue === '' ? null : textValue;
306
+ }
307
+ if (Array.isArray(nodeValue)) {
308
+ for (let i = 0; i < nodeValue.length; i += 1) {
309
+ const resolvedValue = this.resolveNodeValue(nodeValue[i]);
310
+ if (resolvedValue) {
311
+ return resolvedValue;
312
+ }
313
+ }
314
+ return null;
315
+ }
316
+ if (typeof nodeValue === 'object') {
317
+ if (Object.prototype.hasOwnProperty.call(nodeValue, '#text')) {
318
+ return this.resolveNodeValue(nodeValue['#text']);
319
+ }
320
+ if (Object.prototype.hasOwnProperty.call(nodeValue, '_')) {
321
+ return this.resolveNodeValue(nodeValue._);
322
+ }
323
+ if (Object.prototype.hasOwnProperty.call(nodeValue, '$t')) {
324
+ return this.resolveNodeValue(nodeValue.$t);
325
+ }
326
+ if (Object.prototype.hasOwnProperty.call(nodeValue, 'value')) {
327
+ return this.resolveNodeValue(nodeValue.value);
328
+ }
329
+ }
330
+ return null;
331
+ };
332
+
333
+ processXmlCapabilitiesData = (xmlInput) => {
334
+ let xmlDoc = xmlInput;
335
+ if (typeof xmlInput === 'string') {
336
+ const parser = new DOMParser();
337
+ xmlDoc = parser.parseFromString(xmlInput, 'text/xml');
338
+ }
339
+ if (!xmlDoc || typeof xmlDoc.querySelectorAll !== 'function') {
340
+ return null;
341
+ }
342
+ const featureTypeList = [];
343
+ const featureTypes = xmlDoc.querySelectorAll('FeatureType, featuretype');
344
+ featureTypes.forEach((featureTypeNode) => {
345
+ const titleNode =
346
+ featureTypeNode.querySelector('Title') ||
347
+ featureTypeNode.querySelector('title') ||
348
+ featureTypeNode.querySelector('ows\\:Title');
349
+ const nameNode =
350
+ featureTypeNode.querySelector('Name') ||
351
+ featureTypeNode.querySelector('name') ||
352
+ featureTypeNode.querySelector('wfs\\:Name') ||
353
+ featureTypeNode.querySelector('ows\\:Identifier');
354
+ const nameValue = this.resolveNodeValue(nameNode && nameNode.textContent);
355
+ const titleValue = this.resolveNodeValue(
356
+ titleNode && titleNode.textContent,
357
+ );
358
+ if (nameValue) {
359
+ featureTypeList.push({
360
+ Name: nameValue,
361
+ Title: titleValue,
362
+ });
363
+ }
364
+ });
365
+ return {
366
+ FeatureTypeList: {
367
+ FeatureType: featureTypeList,
368
+ },
369
+ };
370
+ };
371
+
372
+ resolveCapabilitiesData = (capabilitiesResponse) => {
373
+ if (
374
+ capabilitiesResponse &&
375
+ typeof capabilitiesResponse === 'object' &&
376
+ !Array.isArray(capabilitiesResponse) &&
377
+ typeof capabilitiesResponse.querySelectorAll !== 'function'
378
+ ) {
379
+ return capabilitiesResponse;
380
+ }
381
+ if (typeof capabilitiesResponse === 'string') {
382
+ const trimmedResponse = capabilitiesResponse.trim();
383
+ if (trimmedResponse === '') {
384
+ return null;
385
+ }
386
+ try {
387
+ return JSON.parse(trimmedResponse);
388
+ } catch (e) {
389
+ return this.processXmlCapabilitiesData(trimmedResponse);
390
+ }
391
+ }
392
+ if (
393
+ capabilitiesResponse &&
394
+ typeof capabilitiesResponse === 'object' &&
395
+ typeof capabilitiesResponse.querySelectorAll === 'function'
396
+ ) {
397
+ return this.processXmlCapabilitiesData(capabilitiesResponse);
398
+ }
399
+ return null;
400
+ };
401
+
402
+ resolveFeatureTypeList = (capabilitiesData) => {
403
+ const featureTypeList = [];
404
+ const processNode = (nodeValue) => {
405
+ if (nodeValue === null || nodeValue === undefined) {
406
+ return;
407
+ }
408
+ if (Array.isArray(nodeValue)) {
409
+ nodeValue.forEach((itemValue) => {
410
+ processNode(itemValue);
411
+ });
412
+ return;
413
+ }
414
+ if (typeof nodeValue !== 'object') {
415
+ return;
416
+ }
417
+ const nameValue = this.resolveNodeValue(
418
+ nodeValue.Name ||
419
+ nodeValue.name ||
420
+ nodeValue['wfs:Name'] ||
421
+ nodeValue['ows:Identifier'],
422
+ );
423
+ const titleValue = this.resolveNodeValue(
424
+ nodeValue.Title || nodeValue.title || nodeValue['ows:Title'],
425
+ );
426
+ if (nameValue) {
427
+ featureTypeList.push({
428
+ Name: nameValue,
429
+ Title: titleValue,
430
+ });
431
+ }
432
+ const nestedFeatureType =
433
+ nodeValue.FeatureType || nodeValue.featureType || nodeValue.featuretype;
434
+ if (nestedFeatureType) {
435
+ processNode(nestedFeatureType);
436
+ }
437
+ Object.keys(nodeValue).forEach((keyValue) => {
438
+ if (
439
+ keyValue !== 'FeatureType' &&
440
+ keyValue !== 'featureType' &&
441
+ keyValue !== 'featuretype'
442
+ ) {
443
+ processNode(nodeValue[keyValue]);
444
+ }
445
+ });
446
+ };
447
+ processNode(capabilitiesData);
448
+ return featureTypeList;
449
+ };
450
+
239
451
  getCapabilities = (url, serviceType) => {
240
452
  // Get the coordinates of the click on the view
241
453
  const proxiedUrl = this.buildProxiedUrl(url);
242
454
  return esriRequest(proxiedUrl, {
243
- responseType: 'html',
455
+ responseType: 'text',
244
456
  query: {
245
457
  request: 'GetCapabilities',
246
458
  service: serviceType,
@@ -248,34 +460,19 @@ class UploadWidget extends React.Component {
248
460
  },
249
461
  })
250
462
  .then((response) => {
251
- const xmlDoc = response.data;
252
- const parser = new DOMParser();
253
- this.xml = parser.parseFromString(xmlDoc, 'text/html');
463
+ const capabilitiesData = this.resolveCapabilitiesData(response.data);
464
+ this.capabilitiesData = capabilitiesData;
254
465
  })
255
466
  .catch(() => {});
256
467
  };
257
468
 
258
- parseWFSFeatures = (xml) => {
259
- let doc = xml;
469
+ parseWFSFeatures = (capabilitiesData) => {
260
470
  try {
261
- if (typeof xml === 'string') {
262
- const parser = new DOMParser();
263
- doc = parser.parseFromString(xml, 'text/xml');
264
- }
265
471
  const features = {};
266
- const featureTypes = doc.querySelectorAll('FeatureType, featuretype');
267
- featureTypes.forEach((ft) => {
268
- const titleEl =
269
- ft.querySelector('Title') ||
270
- ft.querySelector('title') ||
271
- ft.querySelector('ows\\:Title');
272
- const nameEl =
273
- ft.querySelector('Name') ||
274
- ft.querySelector('name') ||
275
- ft.querySelector('wfs\\:Name') ||
276
- ft.querySelector('ows\\:Identifier');
277
- const key = nameEl ? (nameEl.textContent || '').trim() : null;
278
- const title = titleEl ? (titleEl.textContent || '').trim() : null;
472
+ const featureTypeList = this.resolveFeatureTypeList(capabilitiesData);
473
+ featureTypeList.forEach((featureTypeValue) => {
474
+ const key = this.resolveNodeValue(featureTypeValue.Name);
475
+ const title = this.resolveNodeValue(featureTypeValue.Title);
279
476
  if (key) {
280
477
  features[key] = title ?? null;
281
478
  }
@@ -310,14 +507,15 @@ class UploadWidget extends React.Component {
310
507
  selectedServiceType === 'WFS' &&
311
508
  serviceUrl &&
312
509
  serviceUrl.trim() !== '' &&
313
- this.isValidUrl(serviceUrl)
510
+ this.isValidUrl(serviceUrl) &&
511
+ this.isServiceTypeMatchingUrl(serviceUrl, selectedServiceType)
314
512
  ) {
315
513
  const normalizedUrl = this.getNormalizedUrlForType(
316
514
  serviceUrl,
317
515
  selectedServiceType,
318
516
  );
319
517
  await this.getCapabilities(normalizedUrl, selectedServiceType);
320
- const result = this.parseWFSFeatures(this.xml);
518
+ const result = this.parseWFSFeatures(this.capabilitiesData);
321
519
  this.setState(() => ({
322
520
  wfsFeatures: result,
323
521
  }));
@@ -334,7 +532,8 @@ class UploadWidget extends React.Component {
334
532
  selectedServiceType &&
335
533
  serviceUrl &&
336
534
  serviceUrl.trim() !== '' &&
337
- this.isValidUrl(serviceUrl)
535
+ this.isValidUrl(serviceUrl) &&
536
+ this.isServiceTypeMatchingUrl(serviceUrl, selectedServiceType)
338
537
  ) {
339
538
  const normalizedUrl = this.getNormalizedUrlForType(
340
539
  serviceUrl,
@@ -375,10 +574,10 @@ class UploadWidget extends React.Component {
375
574
  this.setState({ wfsFeatures: {}, serviceUrl: '', selectedFeatures: {} });
376
575
  };
377
576
 
378
- errorPopup = () => {
577
+ errorPopup = (popupType = 'uploadError') => {
379
578
  this.setState({
380
579
  showInfoPopup: true,
381
- infoPopupType: 'uploadError',
580
+ infoPopupType: popupType,
382
581
  });
383
582
  setTimeout(() => {
384
583
  this.setState({
@@ -456,7 +655,7 @@ class UploadWidget extends React.Component {
456
655
 
457
656
  componentDidUpdate(prevProps, prevState) {
458
657
  if (!prevProps.showErrorPopup && this.props.showErrorPopup) {
459
- this.errorPopup();
658
+ this.errorPopup(this.props.showErrorPopupType || 'uploadError');
460
659
  }
461
660
  }
462
661
 
@@ -723,6 +922,38 @@ class UploadWidget extends React.Component {
723
922
  </div>
724
923
  </>
725
924
  )}
925
+ {this.state.infoPopupType === 'fileLimit' && (
926
+ <>
927
+ <span className="drawRectanglePopup-icon">
928
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
929
+ </span>
930
+ <div className="drawRectanglePopup-text">
931
+ File exceeds the max size allowed of 10MB.
932
+ </div>
933
+ </>
934
+ )}
935
+ {this.state.infoPopupType === 'shapefileLimit' && (
936
+ <>
937
+ <span className="drawRectanglePopup-icon">
938
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
939
+ </span>
940
+ <div className="drawRectanglePopup-text">
941
+ Uploading shapefiles files larger than 2MB is not
942
+ allowed.
943
+ </div>
944
+ </>
945
+ )}
946
+ {this.state.infoPopupType === 'noGeometryError' && (
947
+ <>
948
+ <span className="drawRectanglePopup-icon">
949
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
950
+ </span>
951
+ <div className="drawRectanglePopup-text">
952
+ Selected service has no geometry data and cannot be
953
+ displayed on the map.
954
+ </div>
955
+ </>
956
+ )}
726
957
  </div>
727
958
  </div>
728
959
  </div>
@@ -833,9 +1064,6 @@ class UploadWidget extends React.Component {
833
1064
  disabled={!selectedFile}
834
1065
  style={{
835
1066
  width: '100%',
836
- border: '2px solid #c0d36b',
837
- background: '#ffffff',
838
- color: '#8ea92a',
839
1067
  }}
840
1068
  onClick={this.handleAddClick}
841
1069
  >
@@ -847,9 +1075,6 @@ class UploadWidget extends React.Component {
847
1075
  disabled={false}
848
1076
  style={{
849
1077
  width: '100%',
850
- border: '2px solid #c0d36b',
851
- background: '#ffffff',
852
- color: '#8ea92a',
853
1078
  }}
854
1079
  onClick={this.handleBrowseClick}
855
1080
  >
@@ -685,6 +685,19 @@ div.upload-container
685
685
  cursor: default;
686
686
  }
687
687
 
688
+ .upload-container .esri-button {
689
+ border: 1px solid #a0b128;
690
+ background-color: #a0b128;
691
+ color: white;
692
+ }
693
+
694
+ .upload-container .esri-button:enabled:hover,
695
+ .upload-container .esri-button[enabled]:hover {
696
+ border: 1px solid #a0b128;
697
+ background-color: white;
698
+ color: #a0b128;
699
+ }
700
+
688
701
  .upload-panel {
689
702
  display: unset !important;
690
703
  }
@@ -999,6 +1012,35 @@ div.upload-container
999
1012
  margin-left: 1.75rem;
1000
1013
  }
1001
1014
 
1015
+ .land #map-menu-services .map-menu-service {
1016
+ width: 100%;
1017
+ align-items: center;
1018
+ }
1019
+
1020
+ .land #map-menu-services .ccl-expandable__button .dropdown-icon {
1021
+ display: flex;
1022
+ width: 100%;
1023
+ height: auto;
1024
+ margin-right: 0;
1025
+ }
1026
+
1027
+ .land #map-menu-services .map-menu-service .ccl-form-check-label {
1028
+ min-width: 0;
1029
+ flex: 1;
1030
+ }
1031
+
1032
+ .land #map-menu-services .map-menu-service .ccl-form-legend {
1033
+ display: block;
1034
+ overflow: hidden;
1035
+ text-overflow: ellipsis;
1036
+ white-space: nowrap;
1037
+ }
1038
+
1039
+ #map-menu-services .map-menu-icon.map-menu-service-icon {
1040
+ margin-right: 0;
1041
+ margin-left: auto;
1042
+ }
1043
+
1002
1044
  .map-menu-dropdown i:hover {
1003
1045
  color: black;
1004
1046
  }
@@ -1941,16 +1983,13 @@ div.map-container.popup-block {
1941
1983
  }
1942
1984
 
1943
1985
  .esri-popup__icon {
1944
- color: white;
1986
+ color: auto;
1945
1987
  }
1946
1988
 
1947
1989
  .esri-widget__anchor {
1948
1990
  color: #7c8921 !important;
1949
1991
  outline: 0px !important;
1950
1992
  }
1951
- /* .esri-widget__anchor:focus {
1952
- background-color: #a0b128 !important;
1953
- } */
1954
1993
 
1955
1994
  .esri-popup__header-container {
1956
1995
  background-color: #a0b128 !important;
@@ -1963,5 +2002,16 @@ div.map-container.popup-block {
1963
2002
 
1964
2003
  .esri-popup__button {
1965
2004
  background-color: #a0b128 !important;
2005
+ color: white !important;
1966
2006
  outline: 0px !important;
1967
2007
  }
2008
+
2009
+ .esri-popup__button:hover {
2010
+ background-color: #7c8921 !important;
2011
+ }
2012
+
2013
+ .esri-popup__action:hover {
2014
+ background-color: white !important;
2015
+ color: #a0b128 !important;
2016
+ outline: solid 2px #a0b128 !important;
2017
+ }