@salesforcedevs/arch-components 1.20.17-alpha13 → 1.20.17-alpha15

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 (146) hide show
  1. package/package.json +1 -1
  2. package/src/assets/css/arch-variables.css +512 -0
  3. package/src/modules/arch/badge/badge.css +22 -0
  4. package/src/modules/arch/badge/badge.html +5 -0
  5. package/src/modules/arch/badge/badge.ts +9 -0
  6. package/src/modules/arch/button/button.css +1 -0
  7. package/src/modules/arch/button/button.html +20 -0
  8. package/src/modules/arch/button/button.ts +67 -0
  9. package/src/modules/arch/buttonLink/buttonLink.css +1 -0
  10. package/src/modules/arch/buttonLink/buttonLink.html +19 -0
  11. package/src/modules/arch/buttonLink/buttonLink.stories.js +24 -0
  12. package/src/modules/arch/buttonLink/buttonLink.ts +8 -0
  13. package/src/modules/arch/buttonStyles/buttonStyles.css +220 -0
  14. package/src/modules/arch/card/card.css +128 -0
  15. package/src/modules/arch/card/card.html +85 -0
  16. package/src/modules/arch/card/card.ts +277 -0
  17. package/src/modules/arch/cardBase/cardBase.css +11 -0
  18. package/src/modules/arch/cardBase/cardBase.html +2 -0
  19. package/src/modules/arch/cardGridA/cardGridA.css +11 -0
  20. package/src/modules/arch/cardGridA/cardGridA.html +21 -0
  21. package/src/modules/arch/cardGridA/cardGridA.stories.js +107 -0
  22. package/src/modules/arch/cardGridA/cardGridA.ts +24 -0
  23. package/src/modules/arch/cardGridC/cardGridC.css +24 -0
  24. package/src/modules/arch/cardGridC/cardGridC.html +22 -0
  25. package/src/modules/arch/cardGridC/cardGridC.stories.js +42 -0
  26. package/src/modules/arch/cardGridC/cardGridC.ts +11 -0
  27. package/src/modules/arch/cardGridD/cardGridD.css +17 -0
  28. package/src/modules/arch/cardGridD/cardGridD.html +20 -0
  29. package/src/modules/arch/cardGridD/cardGridD.stories.js +34 -0
  30. package/src/modules/arch/cardGridD/cardGridD.ts +7 -0
  31. package/src/modules/arch/cardNew/cardNew.css +31 -0
  32. package/src/modules/arch/cardNew/cardNew.html +32 -0
  33. package/src/modules/arch/cardNew/cardNew.ts +66 -0
  34. package/src/modules/arch/children/children.html +2 -0
  35. package/src/modules/arch/children/children.ts +31 -0
  36. package/src/modules/arch/color/color.ts +59 -0
  37. package/src/modules/arch/content/__fixtures__/index.ts +884 -0
  38. package/src/modules/arch/content/content.css +643 -0
  39. package/src/modules/arch/content/content.html +65 -0
  40. package/src/modules/arch/content/content.stories.js +14 -0
  41. package/src/modules/arch/content/content.ts +169 -0
  42. package/src/modules/arch/contentIcon/contentIcon.css +48 -0
  43. package/src/modules/arch/contentIcon/contentIcon.html +15 -0
  44. package/src/modules/arch/contentIcon/contentIcon.stories.js +110 -0
  45. package/src/modules/arch/contentIcon/contentIcon.ts +68 -0
  46. package/src/modules/arch/context/context.ts +21 -19
  47. package/src/modules/arch/contextAdapter/constants.ts +1 -0
  48. package/src/modules/arch/contextAdapter/contextAdapter.html +1 -0
  49. package/src/modules/arch/contextAdapter/contextAdapter.ts +54 -0
  50. package/src/modules/arch/debounce/debounce.ts +32 -0
  51. package/src/modules/arch/dialog/dialog.ts +154 -0
  52. package/src/modules/arch/dialogStyles/dialogStyles.css +90 -0
  53. package/src/modules/arch/effectAdapter/effectAdapter.ts +19 -9
  54. package/src/modules/arch/explorer/explorer.css +301 -0
  55. package/src/modules/arch/explorer/explorer.html +418 -0
  56. package/src/modules/arch/explorer/explorer.ts +718 -0
  57. package/src/modules/arch/explorer/types.d.ts +60 -0
  58. package/src/modules/arch/fetch/fetch.ts +55 -0
  59. package/src/modules/arch/footerMfe/footerMfe.html +3 -0
  60. package/src/modules/arch/footerMfe/footerMfe.ts +19 -0
  61. package/src/modules/arch/gallery/gallery.css +365 -0
  62. package/src/modules/arch/gallery/gallery.html +71 -0
  63. package/src/modules/arch/gallery/gallery.ts +366 -0
  64. package/src/modules/arch/gallery/types.d.ts +35 -0
  65. package/src/modules/arch/heading/heading.css +1 -0
  66. package/src/modules/arch/heading/heading.html +9 -0
  67. package/src/modules/arch/heading/heading.ts +36 -0
  68. package/src/modules/arch/helpers/helpers.ts +141 -0
  69. package/src/modules/arch/heroA/heroA.css +116 -0
  70. package/src/modules/arch/heroA/heroA.html +28 -0
  71. package/src/modules/arch/heroA/heroA.stories.js +49 -0
  72. package/src/modules/arch/heroA/heroA.ts +53 -0
  73. package/src/modules/arch/heroB/heroB.css +79 -0
  74. package/src/modules/arch/heroB/heroB.html +27 -0
  75. package/src/modules/arch/heroB/heroB.stories.js +44 -0
  76. package/src/modules/arch/heroB/heroB.ts +26 -0
  77. package/src/modules/arch/i18n/i18n.ts +78 -0
  78. package/src/modules/arch/icon/icon.css +28 -0
  79. package/src/modules/arch/icon/icon.html +17 -0
  80. package/src/modules/arch/icon/icon.stories.js +18 -0
  81. package/src/modules/arch/icon/icon.ts +92 -0
  82. package/src/modules/arch/instrumentation/instrumentation.css +1 -0
  83. package/src/modules/arch/instrumentation/instrumentation.html +1 -0
  84. package/src/modules/arch/instrumentation/instrumentation.ts +113 -0
  85. package/src/modules/arch/labels/helpers.ts +25 -0
  86. package/src/modules/arch/labels/pointHelpers.ts +47 -0
  87. package/src/modules/arch/labels/timeHelpers.ts +182 -0
  88. package/src/modules/arch/labels/types.d.ts +5 -0
  89. package/src/modules/arch/logger/logger.ts +33 -0
  90. package/src/modules/arch/menu/menu.ts +260 -0
  91. package/src/modules/arch/overflow/overflow.ts +71 -0
  92. package/src/modules/arch/page/page.css +3 -0
  93. package/src/modules/arch/page/page.html +3 -0
  94. package/src/modules/arch/page/page.stories.js +10 -0
  95. package/src/modules/arch/page/page.ts +3 -0
  96. package/src/modules/arch/pageHeaderA/pageHeaderA.css +82 -0
  97. package/src/modules/arch/pageHeaderA/pageHeaderA.html +24 -0
  98. package/src/modules/arch/pageHeaderA/pageHeaderA.stories.js +18 -0
  99. package/src/modules/arch/pageHeaderA/pageHeaderA.ts +51 -0
  100. package/src/modules/arch/pill/pill.css +70 -0
  101. package/src/modules/arch/pill/pill.html +17 -0
  102. package/src/modules/arch/pill/pill.ts +34 -0
  103. package/src/modules/arch/polling-request.ts +97 -0
  104. package/src/modules/arch/reflectedElement/reflectedElement.html +2 -0
  105. package/src/modules/arch/reflectedElement/reflectedElement.ts +5 -3
  106. package/src/modules/arch/reset/reset.css +39 -0
  107. package/src/modules/arch/searchList/searchList.css +120 -0
  108. package/src/modules/arch/searchList/searchList.html +46 -0
  109. package/src/modules/arch/searchList/searchList.ts +53 -0
  110. package/src/modules/arch/sectionA/sectionA.css +64 -0
  111. package/src/modules/arch/sectionA/sectionA.html +21 -0
  112. package/src/modules/arch/sectionA/sectionA.stories.js +18 -0
  113. package/src/modules/arch/sectionA/sectionA.ts +27 -0
  114. package/src/modules/arch/select/select.css +40 -0
  115. package/src/modules/arch/select/select.html +24 -0
  116. package/src/modules/arch/select/select.ts +64 -0
  117. package/src/modules/arch/socialShare/socialShare.css +50 -0
  118. package/src/modules/arch/socialShare/socialShare.html +56 -0
  119. package/src/modules/arch/socialShare/socialShare.ts +29 -0
  120. package/src/modules/arch/spinner/spinner.css +195 -0
  121. package/src/modules/arch/spinner/spinner.html +9 -0
  122. package/src/modules/arch/spinner/spinner.ts +15 -0
  123. package/src/modules/arch/styles/styles.css +24 -0
  124. package/src/modules/arch/summary/summary.css +134 -0
  125. package/src/modules/arch/summary/summary.html +71 -0
  126. package/src/modules/arch/summary/summary.stories.js +148 -0
  127. package/src/modules/arch/summary/summary.ts +96 -0
  128. package/src/modules/arch/tab/tab.css +3 -0
  129. package/src/modules/arch/tab/tab.html +5 -0
  130. package/src/modules/arch/tab/tab.ts +46 -0
  131. package/src/modules/arch/tabset/tabset.css +112 -0
  132. package/src/modules/arch/tabset/tabset.html +62 -0
  133. package/src/modules/arch/tabset/tabset.ts +244 -0
  134. package/src/modules/arch/testutils.ts +118 -0
  135. package/src/modules/arch/threeCardGrid/threeCardGrid.css +6 -0
  136. package/src/modules/arch/threeCardGrid/threeCardGrid.html +5 -0
  137. package/src/modules/arch/threeCardGrid/threeCardGrid.ts +3 -0
  138. package/src/modules/arch/track/track.ts +23 -0
  139. package/src/modules/arch/trailhead.ts +120 -0
  140. package/src/modules/arch/types.d.ts +1 -0
  141. package/src/modules/arch/useEffectAttr.ts +16 -0
  142. package/src/modules/arch/utils/utils.ts +20 -0
  143. package/src/modules/arch/withState.ts +21 -0
  144. package/src/modules/arch/xsfMfeEvents/xsfMfeEvents.html +1 -0
  145. package/src/modules/arch/xsfMfeEvents/xsfMfeEvents.ts +47 -0
  146. package/src/modules/arch/slot/slot.ts +0 -20
@@ -0,0 +1,718 @@
1
+ import { LightningElement, api } from 'lwc';
2
+ //import { ExpandableSection } from '../expandableSection/expandableSection';
3
+ import { Pattern, Behavior, Location, Filter } from './types';
4
+ import jsPDF from 'jspdf';
5
+ import { debounce } from 'arch/debounce';
6
+ import { sendInteractionEvent, InteractionEventTypes } from 'arch/helpers';
7
+
8
+ export default class Explorer extends LightningElement {
9
+
10
+ // get the inuts from the calling page
11
+ _content: string | undefined;
12
+
13
+ // lets get the inputs from the form
14
+ private _keywordFilter: string = '';
15
+ private _selectedTrusted: string = '-1';
16
+ private _selectedEasy: string = '-1';
17
+ private _selectedAdaptable: string = '-1';
18
+ private _selectedLocation: string = '-1';
19
+ private _selectedProductArea: string = '-1';
20
+
21
+ // set up some controlling sys vars
22
+ private isLoading = true;
23
+ private filterData = '/assets/data/wa_framework.json';
24
+ private feedbackURL = 'https://docs.google.com/forms/d/e/1FAIpQLSf172WosVDuSjMnTS2tGL8024TyBswpWTv4vNjwfbwces0HlQ/viewform';
25
+ @api private version: string = '';
26
+ private frameworkArr: any;
27
+ private trustedArr: Behavior[] = [];
28
+ private easyArr: Behavior[] = [];
29
+ private adaptableArr: Behavior[] = [];
30
+ private locationsArr: Location[] = [];
31
+ private productAreasArr: Location[] = [];
32
+ private selectedFilters: Filter[] = [];
33
+ private currentPageIndex = 0;
34
+ private pageSize = 10;
35
+ private shareUrl = window.location.href;
36
+
37
+
38
+ // vars for pdf export
39
+ private logoURL = 'https://a.sfdcstatic.com/developer-website/images/architect/architects_logo_horizontal_bg.png';
40
+ private logoImageData: string | undefined;
41
+
42
+
43
+ // lets set the pattern data (this includes anti-patterns)
44
+ entireLibrary: Pattern[] = [];
45
+ allPatterns: Pattern[] = [];
46
+ allAntiPatterns: Pattern[] = [];
47
+ filteredPatterns: Pattern[] = [];
48
+ filteredAntiPatterns: Pattern[] = [];
49
+
50
+ connectedCallback(): void {
51
+
52
+ this.loadImage(this.logoURL).then(data => {
53
+ this.logoImageData = data;
54
+ })
55
+ .catch(error => {
56
+ console.error('Error loading image:', error);
57
+ });
58
+ }
59
+
60
+ @api
61
+ set content(value) {
62
+ if (this._content !== value) {
63
+ this._content = value;
64
+ this.fetchFilterData();
65
+ this.fetchPatternData();
66
+ }
67
+
68
+ }
69
+ get content() {
70
+ return this._content;
71
+ }
72
+
73
+ // get the patterns loaded into a variable
74
+ async fetchPatternData() {
75
+ try {
76
+ if (!this._content) {
77
+ return;
78
+ }
79
+ const response = await fetch(this._content);
80
+ const data = await response.json();
81
+ this.allPatterns = this.sortPatterns(data.values[0][0].Patterns as Pattern[]);
82
+ this.allAntiPatterns = this.sortPatterns(data.values[0][0].AntiPatterns as Pattern[]);
83
+ this.removeEmptyFilterOptions();
84
+ this.checkURLParams();
85
+ this.applyFilters();
86
+
87
+ } catch (error: unknown) {
88
+ console.error("An error occurred fetching the library:", error instanceof Error ? error.message : String(error));
89
+ }
90
+ }
91
+ private removeEmptyFilterOptions = () => {
92
+ const availableProductAreas = [...new Set(
93
+ [...this.allPatterns.map(item => item.ProductArea),
94
+ ...this.allAntiPatterns.map(item => item.ProductArea)
95
+ ]
96
+ )].sort();
97
+
98
+ this.productAreasArr = availableProductAreas.map(area => ({ Name: area, isDisabled: false }))
99
+ }
100
+ private sortPatterns(arr: any[]) {
101
+ return arr.sort((a, b) => {
102
+ // Compare by CapabilityWeight
103
+ if (a.CapabilityWeight > b.CapabilityWeight) {
104
+ return -1;
105
+ }
106
+ if (a.CapabilityWeight < b.CapabilityWeight) {
107
+ return 1;
108
+ }
109
+
110
+ // If CapabilityWeight is equal, compare by BehaviorWeight
111
+ if (a.BehaviorWeight > b.BehaviorWeight) {
112
+ return -1;
113
+ }
114
+ if (a.BehaviorWeight < b.BehaviorWeight) {
115
+ return 1;
116
+ }
117
+
118
+ // If BehaviorWeight is equal, compare by DimensionWeight
119
+ if (a.DimensionWeight > b.DimensionWeight) {
120
+ return -1;
121
+ }
122
+ if (a.DimensionWeight < b.DimensionWeight) {
123
+ return 1;
124
+ }
125
+
126
+ // If DimensionWeight is equal, compare by ConsiderationWeight
127
+ if (a.ConsiderationWeight > b.ConsiderationWeight) {
128
+ return -1;
129
+ }
130
+ if (a.ConsiderationWeight < b.ConsiderationWeight) {
131
+ return 1;
132
+ }
133
+
134
+ // If all properties are equal, consider the elements equal in this sort context
135
+ return 0;
136
+ });
137
+ }
138
+
139
+ private get hasPatterns(): boolean {
140
+ return this.filteredPatterns.length > 0;
141
+ }
142
+
143
+ private get hasAntiPatterns(): boolean {
144
+ return this.filteredAntiPatterns.length > 0;
145
+ }
146
+
147
+ handleFeedbackClick() {
148
+ window.open(this.feedbackURL, '_blank');
149
+ }
150
+
151
+ //#region FILTER FUNCTIONS
152
+
153
+ async fetchFilterData() {
154
+ try {
155
+
156
+ fetch(this.filterData).then((response) => response.json()).then((data) => {
157
+ // get the entire framework
158
+ this.frameworkArr = data.values[0][0].Capabilities;
159
+
160
+ // populate the framework dropdowns
161
+ this.trustedArr = this.frameworkArr.filter((capability: any) => capability.Name === "Trusted")[0].Behaviors.sort((a: any, b: any) => b.Weight - a.Weight);
162
+ this.trustedArr.forEach((behavior: any) => {
163
+ behavior.Dimensions.forEach((dim: any) => {
164
+ dim.isDisabled = false;
165
+ });
166
+ });
167
+
168
+ this.easyArr = this.frameworkArr.filter((capability: any) => capability.Name === "Easy")[0].Behaviors.sort((a: any, b: any) => b.Weight - a.Weight);
169
+ this.easyArr.forEach((behavior: any) => {
170
+ behavior.Dimensions.forEach((dim: any) => {
171
+ dim.isDisabled = false;
172
+ });
173
+ });
174
+
175
+ this.adaptableArr = this.frameworkArr.filter((capability: any) => capability.Name === "Adaptable")[0].Behaviors.sort((a: any, b: any) => b.Weight - a.Weight);
176
+ this.adaptableArr.forEach((behavior: any) => {
177
+ behavior.Dimensions.forEach((dim: any) => {
178
+ dim.isDisabled = false;
179
+ });
180
+ });
181
+
182
+ this.locationsArr.push(...data.values[0][0].Locations);
183
+ this.locationsArr.forEach((loc: any) => {
184
+ loc.isDisabled = false;
185
+ });
186
+
187
+ this.productAreasArr.push(...data.values[0][0].ProductAreas);
188
+ this.productAreasArr.forEach((area: any) => {
189
+ area.isDisabled = false;
190
+ });
191
+
192
+ });
193
+
194
+ } catch (error: unknown) {
195
+ console.error("An error occurred getting the filter list:", error instanceof Error ? error.message : String(error));
196
+ }
197
+ }
198
+
199
+ filterChange(evt: CustomEvent) {
200
+ if (evt.detail === "-1") {
201
+ return;
202
+ }
203
+ const newFilter: Filter = {
204
+ Type: (evt.target as HTMLElement).id,
205
+ Name: evt.detail
206
+ };
207
+ // Check if the filter already exists in the array
208
+ const filterExists = this.selectedFilters.some(filter => filter.Type === newFilter.Type && filter.Name === newFilter.Name);
209
+ if (!filterExists) {
210
+ this.selectedFilters.push(newFilter);
211
+ this.setDimensionOption(newFilter.Type, newFilter.Name, true);
212
+ }
213
+
214
+ this.applyFilters();
215
+ }
216
+
217
+ removeFilter(evt: Event) {
218
+ const index = this.selectedFilters.findIndex(filter => filter.Name === (evt.target as HTMLElement).innerText);
219
+ this.setDimensionOption(this.selectedFilters[index].Type, this.selectedFilters[index].Name, false);
220
+ this.selectedFilters.splice(index, 1);
221
+ this.applyFilters();
222
+ }
223
+
224
+ private sendInteractionEvent = debounce((event: Event, target: HTMLElement) => {
225
+ sendInteractionEvent("Explorer Search", InteractionEventTypes.INPUT_CHANGE, event, target);
226
+ }, 1000);
227
+ // handle typing in the search box
228
+ keywordFilterChange(evt: InputEvent) {
229
+ const target = (evt.currentTarget as HTMLInputElement);
230
+ this.sendInteractionEvent(evt,target);
231
+ this._keywordFilter = target.value;
232
+ this.applyFilters();
233
+ }
234
+
235
+
236
+ async applyFilters() {
237
+ try {
238
+ this.filteredPatterns = [...this.allPatterns];
239
+ this.filteredAntiPatterns = [...this.allAntiPatterns];
240
+ const frameworkFilters = this.selectedFilters.filter(filter => filter.Type !== 'Location' && filter.Type !== 'ProductArea');
241
+ const locationFilters = this.selectedFilters.filter(filter => filter.Type === 'Location');
242
+ const productAreaFilters = this.selectedFilters.filter(filter => filter.Type === 'ProductArea');
243
+
244
+ if (frameworkFilters.length > 0) {
245
+ this.filteredPatterns = this.filteredPatterns.filter(pattern =>
246
+ frameworkFilters.some(filter => (pattern.Capability === filter.Type && pattern.Dimension === filter.Name))
247
+ );
248
+
249
+ this.filteredAntiPatterns = this.filteredAntiPatterns.filter(pattern =>
250
+ frameworkFilters.some(filter => (pattern.Capability === filter.Type && pattern.Dimension === filter.Name))
251
+ );
252
+ }
253
+ if (locationFilters.length > 0) {
254
+ this.filteredPatterns = this.filteredPatterns.filter(pattern =>
255
+ locationFilters.some(filter => (filter.Type === 'Location' && pattern.Location === filter.Name))
256
+ );
257
+ this.filteredAntiPatterns = this.filteredAntiPatterns.filter(pattern =>
258
+ locationFilters.some(filter => (filter.Type === 'Location' && pattern.Location === filter.Name))
259
+ );
260
+ }
261
+ if (productAreaFilters.length > 0) {
262
+ this.filteredPatterns = this.filteredPatterns.filter(pattern =>
263
+ productAreaFilters.some(filter => (filter.Type === 'ProductArea' && pattern.ProductArea === filter.Name))
264
+ );
265
+ this.filteredAntiPatterns = this.filteredAntiPatterns.filter(pattern =>
266
+ productAreaFilters.some(filter => (filter.Type === 'ProductArea' && pattern.ProductArea === filter.Name))
267
+ );
268
+ }
269
+ this.isLoading = false;
270
+
271
+ // and we want to the search to be reductive as well so only check filterd array
272
+ if (this._keywordFilter.length > 0) {
273
+ this.filteredPatterns = this.filteredPatterns.filter(pattern =>
274
+ pattern.Name?.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
275
+ pattern.Description.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
276
+ pattern.Location?.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
277
+ pattern.Dimension?.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
278
+ pattern.Consideration?.toLowerCase().includes(this._keywordFilter.toLowerCase())
279
+ );
280
+
281
+ this.filteredAntiPatterns = this.filteredAntiPatterns.filter(ap =>
282
+ ap.Name?.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
283
+ ap.Description.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
284
+ ap.Location?.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
285
+ ap.Dimension?.toLowerCase().includes(this._keywordFilter.toLowerCase()) ||
286
+ ap.Consideration?.toLowerCase().includes(this._keywordFilter.toLowerCase())
287
+ );
288
+ }
289
+
290
+ this.currentPageIndex = 0;
291
+ this.udpateSearchParams();
292
+
293
+ } catch (error: unknown) {
294
+ console.error("An error occurred applying the filters:", error instanceof Error ? error.message : String(error));
295
+ }
296
+ }
297
+
298
+ checkURLParams() {
299
+ // Load filters from URL params
300
+ const url = new URL(window.location.href.replace('#', ''));
301
+ const params = url.searchParams;
302
+ for (const [key, value] of params.entries()) {
303
+ switch (key) {
304
+ case 'Trusted':
305
+ case 'Easy':
306
+ case 'Adaptable':
307
+ case 'Location':
308
+ case 'ProductArea':
309
+ this.selectedFilters.push({ Type: key, Name: value });
310
+ this.setDimensionOption(key, value, true);
311
+ break;
312
+ case 'keywords':
313
+ this._keywordFilter = value;
314
+ break;
315
+ default:
316
+ break;
317
+ }
318
+ };
319
+ }
320
+
321
+ udpateSearchParams() {
322
+ try {
323
+ // Update page state so that we track filters in URL params
324
+ const params = new URLSearchParams();
325
+
326
+ this.selectedFilters.forEach((filter) => {
327
+ params.append(filter.Type, filter.Name);
328
+ });
329
+
330
+ if (this._keywordFilter.length > 0 && this._keywordFilter !== '') {
331
+ params.set('keywords', this._keywordFilter);
332
+ }
333
+
334
+ const paramsString = params.toString();
335
+ let updatedUrl = window.location.pathname;
336
+
337
+ if (paramsString) {
338
+ updatedUrl += `?${paramsString}`;
339
+ }
340
+
341
+ // eslint-disable-next-line no-restricted-globals
342
+ history.replaceState(history.state, document.title, updatedUrl);
343
+ this.shareUrl = `${window.location.protocol}//${window.location.hostname}${updatedUrl}`;
344
+
345
+ } catch (error: unknown) {
346
+ console.error("An error occured applying the url params:", error instanceof Error ? error.message : String(error));
347
+ }
348
+ }
349
+
350
+ handleResetFiltersClick() {
351
+ this.selectedFilters = [] as Filter[];
352
+ this._keywordFilter = '';
353
+ this.locationsArr.forEach((loc: any) => {
354
+ loc.isDisabled = false;
355
+ });
356
+ this.productAreasArr.forEach((area: any) => {
357
+ area.isDisabled = false;
358
+ });
359
+ this.trustedArr.forEach((behavior: any) => {
360
+ behavior.Dimensions.forEach((dim: any) => {
361
+ dim.isDisabled = false;
362
+ });
363
+ });
364
+ this.easyArr.forEach((behavior: any) => {
365
+ behavior.Dimensions.forEach((dim: any) => {
366
+ dim.isDisabled = false;
367
+ });
368
+ });
369
+ this.adaptableArr.forEach((behavior: any) => {
370
+ behavior.Dimensions.forEach((dim: any) => {
371
+ dim.isDisabled = false;
372
+ });
373
+ });
374
+ this.applyFilters();
375
+ }
376
+
377
+ setDimensionOption(dropDown: string, itemToDisable: string, state: boolean) {
378
+
379
+ switch (dropDown) {
380
+ case 'ProductArea':
381
+ this.productAreasArr = this.productAreasArr.map((area: any) => area.Name === itemToDisable ? { ...area, isDisabled: state } : area);
382
+ break;
383
+ case 'Location':
384
+ this.locationsArr = this.locationsArr.map((location: any) => location.Name === itemToDisable ? { ...location, isDisabled: state } : location);
385
+ break;
386
+ case 'Trusted':
387
+ this.trustedArr = this.trustedArr.map((behavior: any) => ({
388
+ ...behavior,
389
+ Dimensions: behavior.Dimensions.map((dimension: any) => dimension.Name === itemToDisable ? { ...dimension, isDisabled: state } : dimension)
390
+ }));
391
+ break;
392
+ case 'Easy':
393
+ this.easyArr = this.easyArr.map((behavior: any) => ({
394
+ ...behavior,
395
+ Dimensions: behavior.Dimensions.map((dimension: any) => dimension.Name === itemToDisable ? { ...dimension, isDisabled: state } : dimension)
396
+ }));
397
+ break;
398
+ case 'Adaptable':
399
+ this.adaptableArr = this.adaptableArr.map((behavior: any) => ({
400
+ ...behavior,
401
+ Dimensions: behavior.Dimensions.map((dimension: any) => dimension.Name === itemToDisable ? { ...dimension, isDisabled: state } : dimension)
402
+ }));
403
+ break;
404
+ default:
405
+ break;
406
+ }
407
+ }
408
+
409
+ //#endregion END FILTER FUNCTIONS
410
+
411
+ //#region PAGINATION FUNCTIONS
412
+
413
+ private handlePageSizeChange(event: CustomEvent) {
414
+ this.pageSize = parseInt(event.detail, 10);
415
+ this.applyFilters();
416
+ }
417
+
418
+ private handlePreviousPageClick() {
419
+ if (this.currentPageIndex > 0) {
420
+ this.currentPageIndex--;
421
+ }
422
+ }
423
+
424
+ private handleNextPageClick() {
425
+ if (this.currentPageIndex < this.totalPages - 1) {
426
+ this.currentPageIndex++;
427
+ }
428
+ }
429
+
430
+ private get filterStatsLabel(): string {
431
+ const filteredItemCount = (this.activeTab === 'pattern') ? this.filteredPatterns.length : this.filteredAntiPatterns.length;
432
+ const allItemCount = (this.activeTab === 'pattern') ? this.allPatterns.length : this.allAntiPatterns.length;
433
+
434
+ let label;
435
+ if (filteredItemCount === allItemCount) {
436
+ label = `Showing ${allItemCount} ${this.activeTab}${allItemCount === 1 ? '' : 's'
437
+ }`;
438
+ } else {
439
+ label = `Showing ${filteredItemCount} ${this.activeTab}${filteredItemCount === 1 ? '' : 's'
440
+ }`;
441
+ }
442
+ if (this.totalPages > 1) {
443
+ label += ` • page ${this.currentPageIndex + 1} out of ${this.totalPages
444
+ }`;
445
+ }
446
+ return label;
447
+ }
448
+
449
+ private get currentPagePatterns(): Pattern[] {
450
+ const startIndex = this.currentPageIndex * this.pageSize;
451
+ return this.filteredPatterns.slice(
452
+ startIndex,
453
+ startIndex + this.pageSize
454
+ );
455
+ }
456
+
457
+ private get currentPageAntiPatterns(): Pattern[] {
458
+ const startIndex = this.currentPageIndex * this.pageSize;
459
+ return this.filteredAntiPatterns.slice(
460
+ startIndex,
461
+ startIndex + this.pageSize
462
+ );
463
+ }
464
+
465
+ private get isLastPage() {
466
+ let isActive = false;
467
+ if (this.activeTab === 'pattern') {
468
+ isActive = this.filteredPatterns.length === 0 || (this.currentPageIndex + 1) * this.pageSize >= this.filteredPatterns.length;
469
+ } else {
470
+ isActive = this.filteredAntiPatterns.length === 0 || (this.currentPageIndex + 1) * this.pageSize >= this.filteredAntiPatterns.length;
471
+ }
472
+ return isActive;
473
+ }
474
+
475
+ private get isFirstPage() {
476
+ return this.currentPageIndex === 0;
477
+ }
478
+
479
+ private get totalPages() {
480
+ const totPages = (this.activeTab === 'pattern') ? Math.ceil(this.filteredPatterns.length / this.pageSize) : Math.ceil(this.filteredAntiPatterns.length / this.pageSize);
481
+ return totPages;
482
+ }
483
+
484
+ private get activeTab() {
485
+ const tabset = this.template.querySelector('arch-tabset');
486
+ const activeTab = (tabset as any).activeTabValue;
487
+ return activeTab;
488
+ }
489
+
490
+ //#endregion PAGINATION FUNCTIONS
491
+
492
+ //#region EXPORT FUNCTIONS
493
+
494
+ loadImage(url: string): Promise<string> {
495
+ // Function to load an image and convert it to a base64 string
496
+ return new Promise((resolve, reject) => {
497
+ const img = new Image();
498
+ img.crossOrigin = 'Anonymous';
499
+ img.onload = () => {
500
+ const canvas = document.createElement('canvas');
501
+ canvas.width = img.width;
502
+ canvas.height = img.height;
503
+ const ctx = canvas.getContext('2d');
504
+ ctx?.drawImage(img, 0, 0);
505
+ resolve(canvas.toDataURL('image/png'));
506
+ };
507
+ img.onerror = reject;
508
+ img.src = url;
509
+ });
510
+ }
511
+
512
+ getExportFileMetaData() {
513
+ let fileName = '';
514
+ let keywords = '';
515
+ this.selectedFilters.forEach((filter) => {
516
+ fileName += `${filter.Name}_`;
517
+ keywords += `${filter.Name} `;
518
+ });
519
+ keywords = keywords.trimEnd();
520
+ fileName += (this.activeTab === 'pattern') ? `Patterns_${this.version}` : `Anti-Patterns_${this.version}`;
521
+ return { fileName, keywords };
522
+ }
523
+
524
+ async handlePdfClick() {
525
+
526
+ const fileMeta = this.getExportFileMetaData();
527
+
528
+ const margins = {
529
+ top: 20,
530
+ bottom: 20,
531
+ left: 20,
532
+ width: 522,
533
+ windowWidth: 800
534
+ };
535
+
536
+ const doc = new jsPDF('p', 'pt', 'letter');
537
+ doc.setFont('Helvetica');
538
+ doc.setFontSize(10);
539
+ doc.setProperties({
540
+ title: 'Salesforce Well-Architected Pattern & Anti-Pattern Explorer',
541
+ subject: 'Exported lists of patterns and anti-patterns',
542
+ author: 'Salesforce Architects',
543
+ keywords: 'Salesoforce, Well-Architected, ' + fileMeta.keywords,
544
+ creator: 'Salesforce Architects'
545
+ });
546
+
547
+ // set up vars for the header once
548
+ this.loadImage('https://a.sfdcstatic.com/developer-website/images/architect/architects_logo_horizontal.png');
549
+ const expLink = fileMeta.keywords.replaceAll(' ', ' | ');
550
+ const expLinkWidth = doc.getTextWidth(expLink);
551
+ const pageWidth = doc.internal.pageSize.width;
552
+ const expLinkXPosition = pageWidth - expLinkWidth - margins.left;
553
+
554
+ const header = (pdfDoc: jsPDF) => {
555
+ // Add header content, e.g., a title or an image
556
+ if (this.logoImageData) {
557
+ pdfDoc.addImage(this.logoImageData, 'png', margins.top, margins.left, 200, 19);
558
+ pdfDoc.setTextColor("0070d2");
559
+ pdfDoc.textWithLink(expLink, expLinkXPosition, margins.top, {
560
+ url: window.location.href
561
+ });
562
+ }
563
+ };
564
+
565
+ // get the up footer vars once
566
+ const pageHeight = doc.internal.pageSize.height;
567
+
568
+ const footer = (pdfDoc: jsPDF) => {
569
+
570
+ // link to the release page
571
+ pdfDoc.setTextColor("0070d2");
572
+ pdfDoc.textWithLink(`Release (${this.version})`, margins.left, pageHeight - margins.bottom, {
573
+ url: "https://architect.salesforce.com/releases/"
574
+ });
575
+
576
+ // get the page count and formatting
577
+ const pageCount = `Page ${pdfDoc.getNumberOfPages()}`;
578
+ const pageCountTextWidth = pdfDoc.getTextWidth(pageCount);
579
+ const pageCountXPosition = pageWidth - pageCountTextWidth - margins.left;
580
+ // add a page count before changing color
581
+ pdfDoc.setTextColor("000000");
582
+ pdfDoc.text(pageCount, pageCountXPosition, pageHeight - margins.bottom);
583
+ };
584
+
585
+ doc.internal.events.subscribe('addPage', () => {
586
+ header(doc);
587
+ footer(doc);
588
+ });
589
+
590
+ // add the initial header and footer
591
+ header(doc);
592
+ footer(doc);
593
+
594
+ const htmlOptions = {
595
+ margin: [margins.top * 2.5, margins.left, margins.bottom * 2, margins.left],
596
+ autoPaging: true,
597
+ x: margins.left,
598
+ y: margins.top,
599
+ width: margins.width,
600
+ windowWidth: margins.windowWidth
601
+ };
602
+
603
+ const htmlTable = (this.activeTab === 'pattern') ? this.makeHTMLTable('What Good Looks Like<p>Pattern</p>', this.filteredPatterns) : this.makeHTMLTable('What to Avoid<p>Anti-Pattern</p>', this.filteredAntiPatterns);
604
+
605
+ await this.addHtmlToPdf(doc, htmlTable, htmlOptions);
606
+ htmlOptions.y = doc.internal.pageSize.getHeight() * doc.internal.pages.length;
607
+
608
+ // below if we want them in the same doc
609
+ // doc.addPage();
610
+ //await this.addHtmlToPdf(doc, antipatternsHtml, htmlOptions);
611
+
612
+ doc.save(`${fileMeta.fileName}.pdf`);
613
+
614
+ }
615
+
616
+ addHtmlToPdf(pdfDoc: jsPDF, htmlContent: string, options: any = {}) {
617
+ return new Promise((resolve) => {
618
+ pdfDoc.html(htmlContent, {
619
+ ...options,
620
+ callback: function (pdfDocResult: jsPDF) {
621
+ resolve(pdfDocResult);
622
+ }
623
+ });
624
+ });
625
+ }
626
+
627
+ makeHTMLTable(tableName: string, tableArr: Pattern[]) {
628
+ let htmlTable = `
629
+ <table width="100%" style="font-family:Helvetica; border-collapse: collapse;">
630
+ <colgroup>
631
+ <col width="44%">
632
+ <col width="26%">
633
+ <col width="30%">
634
+ </colgroup>
635
+ <thead>
636
+ <tr>
637
+ <th style="background-color: #ebf5ff; border: 1px solid #dddbda; text-align: left; padding: 10px; vertical-align: middle;">${tableName}</th>
638
+ <th style="background-color: #ebf5ff; border: 1px solid #dddbda; text-align: left; padding: 10px; vertical-align: middle;">Where to Look<p>Product Area | Location</p></th>
639
+ <th style="background-color: #ebf5ff; border: 1px solid #dddbda; text-align: left; padding: 10px; vertical-align: middle;">Learn More <p>Dimension | Consideration</p></th>
640
+ </tr>
641
+ </thead>
642
+ <tbody>`;
643
+
644
+ tableArr.forEach((pattern: Pattern, index: number) => {
645
+ const backgroundColor = (index % 2 === 0) ? '#fafaf9' : '#ffffff'; // Add background color to even rows
646
+ const description = (typeof pattern.Name === 'undefined' || pattern.Name === null || pattern.Name === '') ? pattern.Description : `${pattern.Name}<br/>${pattern.Description}`;
647
+
648
+ htmlTable += `<tr style="background-color: ${backgroundColor}">
649
+ <td style="border: 1px solid #dddbda; text-align: left; padding: 10px;">
650
+ ${description}
651
+ </td>
652
+ <td style="border: 1px solid #dddbda; text-align: left; padding: 10px;">
653
+ ${pattern.ProductArea} | In your ${pattern.Location}
654
+ </td>
655
+ <td style="border: 1px solid #dddbda; text-align: left; padding: 10px;">
656
+ ${pattern.Dimension} | ${pattern.Consideration}
657
+ </td>
658
+ </tr>`;
659
+ });
660
+ htmlTable += `</tbody></table>`;
661
+ return htmlTable;
662
+ }
663
+
664
+ async handleCsvClick() {
665
+
666
+ const fileMeta = this.getExportFileMetaData();
667
+
668
+ if (this.activeTab === 'pattern') {
669
+ this.exportToCSV('What Good Looks Like,Where to Look,Learn More', 'Pattern,Product Area | Location,Dimension | Consideration', this.filteredPatterns, fileMeta.fileName);
670
+ } else {
671
+ this.exportToCSV('What to Avoid,Where to Look,Learn More', 'Anti-Pattern,Product Area | Location,Dimension | Consideration', this.filteredAntiPatterns, fileMeta.fileName);
672
+ }
673
+
674
+ }
675
+
676
+ exportToCSV(headers: string, subheaders: string, patArray: Pattern[], filename: string) {
677
+ // Convert Array to CSV
678
+ const csv: String[] = [];
679
+ csv.push(headers); // Add headers row
680
+ csv.push(subheaders);
681
+ patArray.forEach((pattern: Pattern) => {
682
+
683
+ const row: String[] = [];
684
+ const description = (typeof pattern.Name === 'undefined' || pattern.Name === '') ? pattern.Description : `${pattern.Name} ${pattern.Description}`;
685
+ row.push(this.escapeCSVField(description));
686
+ row.push(this.escapeCSVField(`${pattern.ProductArea} | ${pattern.Location}`));
687
+ row.push(this.escapeCSVField(`${pattern.Dimension} | ${pattern.Consideration}`));
688
+
689
+ csv.push(row.join(","));
690
+
691
+ });
692
+
693
+ const csvString = csv.join('\n');
694
+
695
+ // Create Blob and Download Link
696
+ const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
697
+ const link = document.createElement('a');
698
+ link.href = URL.createObjectURL(blob);
699
+ link.download = `${filename}.csv`;
700
+ document.body.appendChild(link);
701
+ link.click();
702
+ document.body.removeChild(link);
703
+
704
+ }
705
+
706
+ escapeCSVField(field: string): string {
707
+ if (field.includes(',') || field.includes('"') || field.includes('\n')) {
708
+ // Escape double quotes
709
+ field = field.replace(/"/g, '""');
710
+ // Enclose field in double quotes
711
+ field = `"${field}"`;
712
+ }
713
+ return field;
714
+ }
715
+
716
+ //#endregion EXPORT FUNCTIONS
717
+
718
+ }