@esri/solutions-components 0.4.1 → 0.4.2

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 (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
  }