@esri/solutions-components 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. package/dist/assets/t9n/map-select-tools/resources.json +1 -1
  2. package/dist/assets/t9n/map-select-tools/resources_en.json +1 -1
  3. package/dist/assets/t9n/public-notification/resources.json +5 -3
  4. package/dist/assets/t9n/public-notification/resources_en.json +5 -3
  5. package/dist/assets/t9n/refine-selection/resources.json +3 -3
  6. package/dist/assets/t9n/refine-selection/resources_en.json +3 -3
  7. package/dist/cjs/calcite-input-message_5.cjs.entry.js +8 -6
  8. package/dist/cjs/{downloadUtils-27dbd8b9.js → downloadUtils-99981c6b.js} +88 -15
  9. package/dist/cjs/{index.es-40d341ed.js → index.es-53f3bc97.js} +1 -1
  10. package/dist/cjs/layer-table.cjs.entry.js +2 -2
  11. package/dist/cjs/public-notification.cjs.entry.js +24 -9
  12. package/dist/collection/components/layer-table/layer-table.js +1 -1
  13. package/dist/collection/components/map-select-tools/map-select-tools.js +1 -1
  14. package/dist/collection/components/pdf-download/pdf-download.js +26 -6
  15. package/dist/collection/components/public-notification/public-notification.js +24 -9
  16. package/dist/collection/utils/downloadUtils.js +87 -14
  17. package/dist/collection/utils/downloadUtils.ts +123 -14
  18. package/dist/components/downloadUtils.js +87 -14
  19. package/dist/components/layer-table.js +1 -1
  20. package/dist/components/map-select-tools2.js +1 -1
  21. package/dist/components/pdf-download2.js +6 -4
  22. package/dist/components/public-notification.js +24 -9
  23. package/dist/esm/calcite-input-message_5.entry.js +8 -6
  24. package/dist/esm/{downloadUtils-76e38a94.js → downloadUtils-4bb47330.js} +88 -15
  25. package/dist/esm/{index.es-489f4f08.js → index.es-4424d2f7.js} +1 -1
  26. package/dist/esm/layer-table.entry.js +2 -2
  27. package/dist/esm/public-notification.entry.js +24 -9
  28. package/dist/solutions-components/{p-caa7e7a7.js → p-0aed9b0d.js} +11 -11
  29. package/dist/solutions-components/{p-5d27b47d.entry.js → p-0d3b0fa0.entry.js} +2 -2
  30. package/dist/solutions-components/{p-bff8aa4e.js → p-50117f71.js} +1 -1
  31. package/dist/solutions-components/p-5e4dfbe4.entry.js +6 -0
  32. package/dist/solutions-components/{p-92cb569a.entry.js → p-ec7f7804.entry.js} +1 -1
  33. package/dist/solutions-components/solutions-components.esm.js +1 -1
  34. package/dist/solutions-components/utils/downloadUtils.ts +123 -14
  35. package/dist/types/components/map-select-tools/map-select-tools.d.ts +1 -1
  36. package/dist/types/components/pdf-download/pdf-download.d.ts +4 -2
  37. package/dist/types/components/public-notification/public-notification.d.ts +9 -1
  38. package/dist/types/components.d.ts +4 -2
  39. package/dist/types/utils/downloadUtils.d.ts +4 -2
  40. package/package.json +1 -1
  41. package/dist/solutions-components/p-4ef94c6b.entry.js +0 -6
@@ -82,7 +82,8 @@ export class PublicNotification {
82
82
  console.log(oldValue);
83
83
  if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
84
84
  console.log("Emit event from parent");
85
- this.searchConfigurationChange.emit(newValue);
85
+ this.searchConfiguration = Object.assign({}, newValue);
86
+ this.searchConfigurationChange.emit(this.searchConfiguration);
86
87
  }
87
88
  }
88
89
  /**
@@ -152,8 +153,7 @@ export class PublicNotification {
152
153
  * Renders the component.
153
154
  */
154
155
  render() {
155
- const hasSelections = this._selectionSets.length > 0;
156
- return (h(Host, null, h("calcite-shell", null, h("calcite-action-bar", { class: "border-bottom-1 action-bar-size", "expand-disabled": true, layout: "horizontal", slot: "header" }, this._getActionGroup("list-check", false, EPageType.LIST, this._translations.myLists), this.showRefineSelection ? this._getActionGroup("test-data", !hasSelections, EPageType.REFINE, this._translations.refineSelection) : undefined, this._getActionGroup("file-pdf", !hasSelections, EPageType.PDF, this._translations.downloadPDF), this._getActionGroup("file-csv", !hasSelections, EPageType.CSV, this._translations.downloadCSV)), this._getPage(this._pageType))));
156
+ return (h(Host, null, h("calcite-shell", null, h("calcite-action-bar", { class: "border-bottom-1 action-bar-size", "expand-disabled": true, layout: "horizontal", slot: "header" }, this._getActionGroup("list-check", EPageType.LIST, this._translations.myLists), this.showRefineSelection ? this._getActionGroup("test-data", EPageType.REFINE, this._translations.refineSelection) : undefined, this._getActionGroup("file-pdf", EPageType.PDF, this._translations.downloadPDF), this._getActionGroup("file-csv", EPageType.CSV, this._translations.downloadCSV)), this._getPage(this._pageType))));
157
157
  }
158
158
  //--------------------------------------------------------------------------
159
159
  //
@@ -183,9 +183,9 @@ export class PublicNotification {
183
183
  *
184
184
  * @protected
185
185
  */
186
- _getActionGroup(icon, disabled, pageType, tip) {
186
+ _getActionGroup(icon, pageType, tip) {
187
187
  const groupClass = this.showRefineSelection ? "action-center w-1-4" : "action-center w-1-3";
188
- return (h("calcite-action-group", { class: groupClass, layout: "horizontal" }, h("calcite-action", { active: this._pageType === pageType, alignment: "center", class: "width-full height-full", compact: false, disabled: disabled, icon: icon, id: icon, onClick: () => { this._setPageType(pageType); }, text: "" }), h("calcite-tooltip", { label: "", placement: "bottom", "reference-element": icon }, h("span", null, tip))));
188
+ return (h("calcite-action-group", { class: groupClass, layout: "horizontal" }, h("calcite-action", { active: this._pageType === pageType, alignment: "center", class: "width-full height-full", compact: false, icon: icon, id: icon, onClick: () => { this._setPageType(pageType); }, text: "" }), h("calcite-tooltip", { label: "", placement: "bottom", "reference-element": icon }, h("span", null, tip))));
189
189
  }
190
190
  /**
191
191
  * Navigate to the defined page type
@@ -299,6 +299,16 @@ export class PublicNotification {
299
299
  this._layerSelectionChangeEvt.detail[0] : "";
300
300
  await this._updateAddresseeLayer(id);
301
301
  }
302
+ /**
303
+ * Check if any selection sets exist.
304
+ *
305
+ * @returns true if selection sets exist
306
+ *
307
+ * @protected
308
+ */
309
+ _hasSelections() {
310
+ return this._selectionSets.length > 0;
311
+ }
302
312
  /**
303
313
  * Create the Select page that shows the selection workflows
304
314
  *
@@ -327,7 +337,9 @@ export class PublicNotification {
327
337
  * @protected
328
338
  */
329
339
  _getRefinePage() {
330
- return (h("calcite-panel", null, this._getLabel(this._translations.refineSelection), this._getNotice(this._translations.refineTip, "padding-sides-1"), h("refine-selection", { addresseeLayer: this.addresseeLayer, enabledLayerIds: this.selectionLayerIds, mapView: this.mapView, selectionSets: this._selectionSets })));
340
+ const hasSelections = this._hasSelections();
341
+ return (h("calcite-panel", null, this._getLabel(this._translations.refineSelection), hasSelections ? (h("div", null, this._getNotice(this._translations.refineTip, "padding-sides-1"), h("refine-selection", { addresseeLayer: this.addresseeLayer, enabledLayerIds: this.selectionLayerIds, mapView: this.mapView, selectionSets: this._selectionSets }))) :
342
+ this._getNotice(this._translations.refineTipNoSelections, "padding-sides-1")));
331
343
  }
332
344
  /**
333
345
  * Create the PDF download page that shows the download options
@@ -358,7 +370,8 @@ export class PublicNotification {
358
370
  */
359
371
  _getDownloadPage(type) {
360
372
  const isPdf = type === EExportType.PDF;
361
- return (h("calcite-panel", null, h("div", null, h("div", { class: "padding-top-sides-1" }, h("calcite-label", { class: "font-bold" }, isPdf ? this._translations.downloadPDF : this._translations.downloadCSV), h("calcite-label", null, this._translations.notifications)), this._getSelectionLists(), h("div", { class: "margin-side-1 padding-top-1 border-bottom" }), h("div", { class: "padding-top-sides-1" }, h("calcite-label", { layout: "inline" }, h("calcite-checkbox", { disabled: !this._downloadActive, ref: (el) => { this._removeDuplicates = el; } }), this._translations.removeDuplicate)), h("div", { class: isPdf ? "" : "display-none" }, this._getLabel(this._translations.selectPDFLabelOption, false), h("div", { class: "padding-sides-1" }, h("pdf-download", { disabled: !this._downloadActive, layerView: this.addresseeLayer, ref: (el) => { this._downloadTools = el; } }))), h("div", { class: "padding-1 display-flex" }, h("calcite-button", { disabled: !this._downloadActive, onClick: isPdf ? () => this._downloadPDF() : () => this._downloadCSV(), width: "full" }, isPdf ? this._translations.downloadPDF : this._translations.downloadCSV)))));
373
+ const hasSelections = this._hasSelections();
374
+ return (h("calcite-panel", null, h("div", null, h("div", { class: "padding-top-sides-1" }, h("calcite-label", { class: "font-bold" }, isPdf ? this._translations.downloadPDF : this._translations.downloadCSV)), hasSelections ? (h("div", null, h("calcite-label", null, this._translations.notifications), this._getSelectionLists(), h("div", { class: "margin-side-1 padding-top-1 border-bottom" }), h("div", { class: "padding-top-sides-1" }, h("calcite-label", { layout: "inline" }, h("calcite-checkbox", { disabled: !this._downloadActive, ref: (el) => { this._removeDuplicates = el; } }), this._translations.removeDuplicate)), h("div", { class: isPdf ? "" : "display-none" }, this._getLabel(this._translations.selectPDFLabelOption, false), h("div", { class: "padding-sides-1" }, h("pdf-download", { disabled: !this._downloadActive, layerView: this.addresseeLayer, ref: (el) => { this._downloadTools = el; } }))), h("div", { class: "padding-1 display-flex" }, h("calcite-button", { disabled: !this._downloadActive, onClick: isPdf ? () => this._downloadPDF() : () => this._downloadCSV(), width: "full" }, isPdf ? this._translations.downloadPDF : this._translations.downloadCSV)))) : (this._getNotice(this._translations.downloadNoLists, "padding-sides-1 padding-bottom-1")))));
362
375
  }
363
376
  /**
364
377
  * Create the stacked navigation buttons for a page
@@ -441,7 +454,8 @@ export class PublicNotification {
441
454
  */
442
455
  _downloadPDF() {
443
456
  const ids = utils.getSelectionIds(this._getDownloadSelectionSets());
444
- void this._downloadTools.downloadPDF(ids, this._removeDuplicates.checked);
457
+ const selectionSetNames = this._selectionSets.map(set => set.label);
458
+ void this._downloadTools.downloadPDF(selectionSetNames, ids, this._removeDuplicates.checked);
445
459
  }
446
460
  /**
447
461
  * Download all selection sets as CSV
@@ -450,7 +464,8 @@ export class PublicNotification {
450
464
  */
451
465
  _downloadCSV() {
452
466
  const ids = utils.getSelectionIds(this._getDownloadSelectionSets());
453
- void this._downloadTools.downloadCSV(ids, this._removeDuplicates.checked);
467
+ const selectionSetNames = this._selectionSets.map(set => set.label);
468
+ void this._downloadTools.downloadCSV(selectionSetNames, ids, this._removeDuplicates.checked);
454
469
  }
455
470
  /**
456
471
  * Get all enabled selection sets
@@ -29,6 +29,7 @@ export { ILabel } from "./pdfUtils";
29
29
  /**
30
30
  * Downloads csv of mailing labels for the provided list of ids
31
31
  *
32
+ * @param selectionSetNames Names of the selection sets used to provide ids
32
33
  * @param layer Layer providing features and attributes for download
33
34
  * @param ids List of ids to download
34
35
  * @param formatUsingLayerPopup When true, the layer's popup is used to choose attributes for each column; when false,
@@ -37,7 +38,8 @@ export { ILabel } from "./pdfUtils";
37
38
  * @param addColumnTitle Indicates if column headings should be included in output
38
39
  * @returns Promise resolving when function is done
39
40
  */
40
- export async function downloadCSV(layer, ids, formatUsingLayerPopup, removeDuplicates = false, addColumnTitle = false) {
41
+ export async function downloadCSV(selectionSetNames, layer, ids, formatUsingLayerPopup, removeDuplicates = false, addColumnTitle = false) {
42
+ console.log("downloadCSV using selectionSetNames " + JSON.stringify(selectionSetNames)); //???
41
43
  const labels = await _prepareLabels(layer, ids, removeDuplicates, formatUsingLayerPopup, addColumnTitle);
42
44
  exportCSV(labels);
43
45
  return Promise.resolve();
@@ -45,13 +47,15 @@ export async function downloadCSV(layer, ids, formatUsingLayerPopup, removeDupli
45
47
  /**
46
48
  * Downloads csv of mailing labels for the provided list of ids
47
49
  *
50
+ * @param selectionSetNames Names of the selection sets used to provide ids
48
51
  * @param layer Layer providing features and attributes for download
49
52
  * @param ids List of ids to download
50
53
  * @param removeDuplicates When true a single label is generated when multiple featues have a shared address value
51
54
  * @param labelPageDescription Provides PDF page layout info
52
55
  * @returns Promise resolving when function is done
53
56
  */
54
- export async function downloadPDF(layer, ids, removeDuplicates, labelPageDescription) {
57
+ export async function downloadPDF(selectionSetNames, layer, ids, removeDuplicates, labelPageDescription) {
58
+ console.log("downloadPDF using selectionSetNames " + JSON.stringify(selectionSetNames)); //???
55
59
  const labels = await _prepareLabels(layer, ids, removeDuplicates);
56
60
  exportPDF(labels, labelPageDescription);
57
61
  return Promise.resolve();
@@ -62,13 +66,14 @@ export async function downloadPDF(layer, ids, removeDuplicates, labelPageDescrip
62
66
  * Converts a set of fieldInfos into template lines.
63
67
  *
64
68
  * @param fieldInfos Layer's fieldInfos structure
69
+ * @param bypassFieldVisiblity Indicates if the configured fieldInfo visibility property should be ignored
65
70
  * @return Label spec
66
71
  */
67
- function _convertPopupFieldsToLabelSpec(fieldInfos) {
72
+ function _convertPopupFieldsToLabelSpec(fieldInfos, bypassFieldVisiblity = false) {
68
73
  const labelSpec = [];
69
74
  // Every visible attribute is used
70
75
  fieldInfos.forEach(fieldInfo => {
71
- if (fieldInfo.visible) {
76
+ if (fieldInfo.visible || bypassFieldVisiblity) {
72
77
  labelSpec.push(`{${fieldInfo.fieldName}}`);
73
78
  }
74
79
  });
@@ -100,6 +105,49 @@ function _convertPopupTextToLabelSpec(popupInfo) {
100
105
  return labelSpec;
101
106
  }
102
107
  ;
108
+ /**
109
+ * Extracts Arcade expressions from the lines of a label format and creates an Arcade executor for each
110
+ * referenced expression name.
111
+ *
112
+ * @param labelFormat Label to examine
113
+ * @param layer Layer from which to fetch features
114
+ * @return Promise resolving to a set of executors keyed using the expression name
115
+ */
116
+ async function _createArcadeExecutors(labelFormat, layer) {
117
+ const arcadeExecutors = {};
118
+ // Are any Arcade expressions in the layer?
119
+ if (!Array.isArray(layer.popupTemplate.expressionInfos) || layer.popupTemplate.expressionInfos.length === 0) {
120
+ return Promise.resolve(arcadeExecutors);
121
+ }
122
+ // Are there any Arcade expressions in the label format?
123
+ const arcadeExpressionRegExp = /\{expression\/\w+\}/g;
124
+ const arcadeExpressionsMatches = labelFormat.join("|").match(arcadeExpressionRegExp);
125
+ if (!arcadeExpressionsMatches) {
126
+ return Promise.resolve(arcadeExecutors);
127
+ }
128
+ // Generate an Arcade executor for each match
129
+ const [arcade] = await loadModules(["esri/arcade"]);
130
+ const labelingProfile = arcade.createArcadeProfile("popup");
131
+ const createArcadeExecutorPromises = {};
132
+ arcadeExpressionsMatches.forEach((match) => {
133
+ const expressionName = match.substring(match.indexOf("/") + 1, match.length - 1);
134
+ (layer.popupTemplate.expressionInfos || []).forEach(expressionInfo => {
135
+ if (expressionInfo.name === expressionName) {
136
+ createArcadeExecutorPromises[expressionName] =
137
+ arcade.createArcadeExecutor(expressionInfo.expression, labelingProfile);
138
+ }
139
+ });
140
+ });
141
+ const promises = Object.values(createArcadeExecutorPromises);
142
+ return Promise.all(promises)
143
+ .then(executors => {
144
+ const expressionNames = Object.keys(createArcadeExecutorPromises);
145
+ for (let i = 0; i < expressionNames.length; ++i) {
146
+ arcadeExecutors[expressionNames[i]] = executors[i].valueOf();
147
+ }
148
+ return arcadeExecutors;
149
+ });
150
+ }
103
151
  /**
104
152
  * Creates labels from items.
105
153
  *
@@ -113,33 +161,57 @@ function _convertPopupTextToLabelSpec(popupInfo) {
113
161
  */
114
162
  async function _prepareLabels(layer, ids, removeDuplicates = true, formatUsingLayerPopup = true, includeHeaderNames = false) {
115
163
  var _a, _b, _c, _d;
116
- const [intl] = await loadModules([
117
- "esri/intl"
118
- ]);
119
- // Get the attributes of the features to export
164
+ const [intl] = await loadModules(["esri/intl"]);
165
+ // Get the features to export
120
166
  const featureSet = await queryFeaturesByID(ids, layer);
121
- const featuresAttrs = featureSet.features.map(f => f.attributes);
122
167
  // Get the label formatting, if any
123
168
  let labelFormat;
169
+ let arcadeExecutors = {};
124
170
  if (layer.popupEnabled) {
125
171
  // What data fields are used in the labels?
126
172
  // Example labelFormat: ['{NAME}', '{STREET}', '{CITY}, {STATE} {ZIP}']
127
173
  if (formatUsingLayerPopup && ((_b = (_a = layer.popupTemplate) === null || _a === void 0 ? void 0 : _a.content[0]) === null || _b === void 0 ? void 0 : _b.type) === "fields") {
128
174
  labelFormat = _convertPopupFieldsToLabelSpec(layer.popupTemplate.fieldInfos);
175
+ // If popup is configured with "no attribute information", then no fields will visible
176
+ if (labelFormat.length === 0) {
177
+ // Can we use the popup title?
178
+ // eslint-disable-next-line unicorn/prefer-ternary
179
+ if (typeof layer.popupTemplate.title === "string") {
180
+ labelFormat = [layer.popupTemplate.title];
181
+ // Otherwise revert to using attributes
182
+ }
183
+ else {
184
+ labelFormat = _convertPopupFieldsToLabelSpec(layer.popupTemplate.fieldInfos, true);
185
+ }
186
+ }
129
187
  }
130
188
  else if (formatUsingLayerPopup && ((_d = (_c = layer.popupTemplate) === null || _c === void 0 ? void 0 : _c.content[0]) === null || _d === void 0 ? void 0 : _d.type) === "text") {
131
189
  labelFormat = _convertPopupTextToLabelSpec(layer.popupTemplate.content[0].text);
190
+ // Do we need any Arcade executors?
191
+ arcadeExecutors = await _createArcadeExecutors(labelFormat, layer);
132
192
  }
133
193
  }
134
194
  // Apply the label format
135
195
  let labels;
136
196
  // eslint-disable-next-line unicorn/prefer-ternary
137
197
  if (labelFormat) {
198
+ const arcadeExpressionRegExp = /\{expression\/\w+\}/g;
138
199
  // Convert attributes into an array of labels
139
- labels = featuresAttrs.map(featureAttributes => {
200
+ labels = featureSet.features.map(feature => {
140
201
  const label = [];
141
202
  labelFormat.forEach(labelLineTemplate => {
142
- const labelLine = intl.substitute(labelLineTemplate, featureAttributes).trim();
203
+ let labelLine = labelLineTemplate;
204
+ // Replace Arcade expressions
205
+ const arcadeExpressionsMatches = labelLine.match(arcadeExpressionRegExp);
206
+ if (arcadeExpressionsMatches) {
207
+ arcadeExpressionsMatches.forEach((match) => {
208
+ const expressionName = match.substring(match.indexOf("/") + 1, match.length - 1);
209
+ const replacement = arcadeExecutors[expressionName].execute({ "$feature": feature });
210
+ labelLine = labelLine.replace(match, replacement);
211
+ });
212
+ }
213
+ // Replace fields; must be done after Arcade check because `substitute` will discard Arcade expressions!
214
+ labelLine = intl.substitute(labelLine, feature.attributes).trim();
143
215
  if (labelLine.length > 0) {
144
216
  label.push(labelLine);
145
217
  }
@@ -151,8 +223,8 @@ async function _prepareLabels(layer, ids, removeDuplicates = true, formatUsingLa
151
223
  }
152
224
  else {
153
225
  // Export all attributes
154
- labels = featuresAttrs.map(featureAttributes => {
155
- return Object.values(featureAttributes).map(attribute => `${attribute}`);
226
+ labels = featureSet.features.map(feature => {
227
+ return Object.values(feature.attributes).map(attribute => `${attribute}`);
156
228
  });
157
229
  }
158
230
  // Remove duplicates
@@ -168,7 +240,8 @@ async function _prepareLabels(layer, ids, removeDuplicates = true, formatUsingLa
168
240
  headerNames = labelFormat.map(labelFormatLine => labelFormatLine.replace(/\{/g, "").replace(/\}/g, ""));
169
241
  }
170
242
  else {
171
- Object.keys(featuresAttrs[0]).forEach(k => {
243
+ const featuresAttrs = featureSet.features[0].attributes;
244
+ Object.keys(featuresAttrs).forEach(k => {
172
245
  if (featuresAttrs[0].hasOwnProperty(k)) {
173
246
  headerNames.push(k);
174
247
  }
@@ -23,12 +23,21 @@ import { queryFeaturesByID } from "./queryUtils";
23
23
 
24
24
  export { ILabel } from "./pdfUtils";
25
25
 
26
+ interface IArcadeExecutors {
27
+ [expressionName: string]: __esri.ArcadeExecutor;
28
+ }
29
+
30
+ interface IArcadeExecutorPromises {
31
+ [expressionName: string]: Promise<__esri.ArcadeExecutor>;
32
+ }
33
+
26
34
  //#endregion
27
35
  //#region Public functions
28
36
 
29
37
  /**
30
38
  * Downloads csv of mailing labels for the provided list of ids
31
39
  *
40
+ * @param selectionSetNames Names of the selection sets used to provide ids
32
41
  * @param layer Layer providing features and attributes for download
33
42
  * @param ids List of ids to download
34
43
  * @param formatUsingLayerPopup When true, the layer's popup is used to choose attributes for each column; when false,
@@ -38,12 +47,14 @@ export { ILabel } from "./pdfUtils";
38
47
  * @returns Promise resolving when function is done
39
48
  */
40
49
  export async function downloadCSV(
50
+ selectionSetNames: string[],
41
51
  layer: __esri.FeatureLayer,
42
52
  ids: number[],
43
53
  formatUsingLayerPopup: boolean,
44
54
  removeDuplicates = false,
45
55
  addColumnTitle = false
46
56
  ): Promise<void> {
57
+ console.log("downloadCSV using selectionSetNames " + JSON.stringify(selectionSetNames));//???
47
58
  const labels = await _prepareLabels(layer, ids, removeDuplicates, formatUsingLayerPopup, addColumnTitle);
48
59
 
49
60
  exportCSV(labels);
@@ -54,6 +65,7 @@ export async function downloadCSV(
54
65
  /**
55
66
  * Downloads csv of mailing labels for the provided list of ids
56
67
  *
68
+ * @param selectionSetNames Names of the selection sets used to provide ids
57
69
  * @param layer Layer providing features and attributes for download
58
70
  * @param ids List of ids to download
59
71
  * @param removeDuplicates When true a single label is generated when multiple featues have a shared address value
@@ -61,11 +73,13 @@ export async function downloadCSV(
61
73
  * @returns Promise resolving when function is done
62
74
  */
63
75
  export async function downloadPDF(
76
+ selectionSetNames: string[],
64
77
  layer: __esri.FeatureLayer,
65
78
  ids: number[],
66
79
  removeDuplicates: boolean,
67
80
  labelPageDescription: ILabel
68
81
  ): Promise<void> {
82
+ console.log("downloadPDF using selectionSetNames " + JSON.stringify(selectionSetNames));//???
69
83
  const labels = await _prepareLabels(layer, ids, removeDuplicates);
70
84
 
71
85
  exportPDF(labels, labelPageDescription);
@@ -80,17 +94,19 @@ export async function downloadPDF(
80
94
  * Converts a set of fieldInfos into template lines.
81
95
  *
82
96
  * @param fieldInfos Layer's fieldInfos structure
97
+ * @param bypassFieldVisiblity Indicates if the configured fieldInfo visibility property should be ignored
83
98
  * @return Label spec
84
99
  */
85
100
  function _convertPopupFieldsToLabelSpec(
86
- fieldInfos: __esri.FieldInfo[]
101
+ fieldInfos: __esri.FieldInfo[],
102
+ bypassFieldVisiblity = false
87
103
  ): string[] {
88
104
  const labelSpec: string[] = [];
89
105
 
90
106
  // Every visible attribute is used
91
107
  fieldInfos.forEach(
92
108
  fieldInfo => {
93
- if (fieldInfo.visible) {
109
+ if (fieldInfo.visible || bypassFieldVisiblity) {
94
110
  labelSpec.push(`{${fieldInfo.fieldName}}`);
95
111
  }
96
112
  }
@@ -129,6 +145,67 @@ function _convertPopupTextToLabelSpec(
129
145
  return labelSpec;
130
146
  };
131
147
 
148
+ /**
149
+ * Extracts Arcade expressions from the lines of a label format and creates an Arcade executor for each
150
+ * referenced expression name.
151
+ *
152
+ * @param labelFormat Label to examine
153
+ * @param layer Layer from which to fetch features
154
+ * @return Promise resolving to a set of executors keyed using the expression name
155
+ */
156
+ async function _createArcadeExecutors(
157
+ labelFormat: string[],
158
+ layer: __esri.FeatureLayer
159
+ ): Promise<IArcadeExecutors> {
160
+ const arcadeExecutors: IArcadeExecutors = {};
161
+
162
+ // Are any Arcade expressions in the layer?
163
+ if (!Array.isArray(layer.popupTemplate.expressionInfos) || layer.popupTemplate.expressionInfos.length === 0) {
164
+ return Promise.resolve(arcadeExecutors);
165
+ }
166
+
167
+ // Are there any Arcade expressions in the label format?
168
+ const arcadeExpressionRegExp = /\{expression\/\w+\}/g;
169
+ const arcadeExpressionsMatches = labelFormat.join("|").match(arcadeExpressionRegExp);
170
+ if (!arcadeExpressionsMatches) {
171
+ return Promise.resolve(arcadeExecutors);
172
+ }
173
+
174
+ // Generate an Arcade executor for each match
175
+ const [arcade] = await loadModules(["esri/arcade"]);
176
+ const labelingProfile: __esri.Profile = arcade.createArcadeProfile("popup");
177
+
178
+ const createArcadeExecutorPromises: IArcadeExecutorPromises = {};
179
+ arcadeExpressionsMatches.forEach(
180
+ (match: string) => {
181
+ const expressionName = match.substring(match.indexOf("/") + 1, match.length - 1);
182
+
183
+ (layer.popupTemplate.expressionInfos || []).forEach(
184
+ expressionInfo => {
185
+ if (expressionInfo.name === expressionName) {
186
+ createArcadeExecutorPromises[expressionName] =
187
+ arcade.createArcadeExecutor(expressionInfo.expression, labelingProfile);
188
+ }
189
+ }
190
+ );
191
+ }
192
+ );
193
+
194
+ const promises = Object.values(createArcadeExecutorPromises);
195
+ return Promise.all(promises)
196
+ .then(
197
+ executors => {
198
+ const expressionNames = Object.keys(createArcadeExecutorPromises);
199
+
200
+ for (let i = 0; i < expressionNames.length; ++i) {
201
+ arcadeExecutors[expressionNames[i]] = executors[i].valueOf() as __esri.ArcadeExecutor;
202
+ }
203
+
204
+ return arcadeExecutors;
205
+ }
206
+ );
207
+ }
208
+
132
209
  /**
133
210
  * Creates labels from items.
134
211
  *
@@ -147,25 +224,38 @@ async function _prepareLabels(
147
224
  formatUsingLayerPopup = true,
148
225
  includeHeaderNames = false
149
226
  ): Promise<string[][]> {
150
- const [intl] = await loadModules([
151
- "esri/intl"
152
- ]);
227
+ const [intl] = await loadModules(["esri/intl"]);
153
228
 
154
- // Get the attributes of the features to export
229
+ // Get the features to export
155
230
  const featureSet = await queryFeaturesByID(ids, layer);
156
- const featuresAttrs = featureSet.features.map(f => f.attributes);
157
231
 
158
232
  // Get the label formatting, if any
159
233
  let labelFormat: string[];
234
+ let arcadeExecutors: IArcadeExecutors = {};
160
235
  if (layer.popupEnabled) {
161
236
  // What data fields are used in the labels?
162
237
  // Example labelFormat: ['{NAME}', '{STREET}', '{CITY}, {STATE} {ZIP}']
163
238
  if (formatUsingLayerPopup && layer.popupTemplate?.content[0]?.type === "fields") {
164
239
  labelFormat = _convertPopupFieldsToLabelSpec(layer.popupTemplate.fieldInfos);
165
240
 
241
+ // If popup is configured with "no attribute information", then no fields will visible
242
+ if (labelFormat.length === 0) {
243
+ // Can we use the popup title?
244
+ // eslint-disable-next-line unicorn/prefer-ternary
245
+ if (typeof layer.popupTemplate.title === "string") {
246
+ labelFormat = [layer.popupTemplate.title];
247
+
248
+ // Otherwise revert to using attributes
249
+ } else {
250
+ labelFormat = _convertPopupFieldsToLabelSpec(layer.popupTemplate.fieldInfos, true);
251
+ }
252
+ }
253
+
166
254
  } else if (formatUsingLayerPopup && layer.popupTemplate?.content[0]?.type === "text") {
167
255
  labelFormat = _convertPopupTextToLabelSpec(layer.popupTemplate.content[0].text);
168
256
 
257
+ // Do we need any Arcade executors?
258
+ arcadeExecutors = await _createArcadeExecutors(labelFormat, layer);
169
259
  }
170
260
  }
171
261
 
@@ -173,13 +263,31 @@ async function _prepareLabels(
173
263
  let labels: string[][];
174
264
  // eslint-disable-next-line unicorn/prefer-ternary
175
265
  if (labelFormat) {
266
+ const arcadeExpressionRegExp = /\{expression\/\w+\}/g;
267
+
176
268
  // Convert attributes into an array of labels
177
- labels = featuresAttrs.map(
178
- featureAttributes => {
269
+ labels = featureSet.features.map(
270
+ feature => {
179
271
  const label: string[] = [];
180
272
  labelFormat.forEach(
181
273
  labelLineTemplate => {
182
- const labelLine = intl.substitute(labelLineTemplate, featureAttributes).trim();
274
+ let labelLine = labelLineTemplate;
275
+
276
+ // Replace Arcade expressions
277
+ const arcadeExpressionsMatches = labelLine.match(arcadeExpressionRegExp);
278
+ if (arcadeExpressionsMatches) {
279
+ arcadeExpressionsMatches.forEach(
280
+ (match: string) => {
281
+ const expressionName = match.substring(match.indexOf("/") + 1, match.length - 1);
282
+ const replacement = arcadeExecutors[expressionName].execute({"$feature": feature});
283
+ labelLine = labelLine.replace(match, replacement);
284
+ }
285
+ )
286
+ }
287
+
288
+ // Replace fields; must be done after Arcade check because `substitute` will discard Arcade expressions!
289
+ labelLine = intl.substitute(labelLine, feature.attributes).trim();
290
+
183
291
  if (labelLine.length > 0) {
184
292
  label.push(labelLine);
185
293
  }
@@ -193,9 +301,9 @@ async function _prepareLabels(
193
301
 
194
302
  } else {
195
303
  // Export all attributes
196
- labels = featuresAttrs.map(
197
- featureAttributes => {
198
- return Object.values(featureAttributes).map(
304
+ labels = featureSet.features.map(
305
+ feature => {
306
+ return Object.values(feature.attributes).map(
199
307
  attribute => `${attribute}`
200
308
  );
201
309
  }
@@ -219,7 +327,8 @@ async function _prepareLabels(
219
327
  headerNames = labelFormat.map(labelFormatLine => labelFormatLine.replace(/\{/g, "").replace(/\}/g, ""));
220
328
 
221
329
  } else {
222
- Object.keys(featuresAttrs[0]).forEach(k => {
330
+ const featuresAttrs = featureSet.features[0].attributes;
331
+ Object.keys(featuresAttrs).forEach(k => {
223
332
  if (featuresAttrs[0].hasOwnProperty(k)) {
224
333
  headerNames.push(k);
225
334
  }