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