@startinblox/components-ds4go 1.0.0

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 (56) hide show
  1. package/.gitlab-ci.yml +57 -0
  2. package/.storybook/main.ts +17 -0
  3. package/.storybook/preview-head.html +8 -0
  4. package/.storybook/preview.ts +22 -0
  5. package/LICENSE +21 -0
  6. package/README.md +129 -0
  7. package/biome.json +50 -0
  8. package/cypress/component/solid-boilerplate.cy.ts +9 -0
  9. package/cypress/support/component-index.html +12 -0
  10. package/cypress/support/component.ts +17 -0
  11. package/cypress.config.ts +11 -0
  12. package/dist/components-ds4go.css +1 -0
  13. package/dist/index.js +1634 -0
  14. package/lit-localize.json +15 -0
  15. package/locales/en.xlf +28 -0
  16. package/package.json +93 -0
  17. package/postcss.config.js +8 -0
  18. package/src/component.d.ts +167 -0
  19. package/src/components/catalog/ds4go-fact-bundle-holder.ts +162 -0
  20. package/src/components/modal/ds4go-fact-bundle-modal.ts +82 -0
  21. package/src/components/solid-fact-bundle.ts +225 -0
  22. package/src/context.json +1 -0
  23. package/src/helpers/components/ResourceMapper.ts +450 -0
  24. package/src/helpers/components/componentObjectHandler.ts +22 -0
  25. package/src/helpers/components/componentObjectsHandler.ts +14 -0
  26. package/src/helpers/components/dspComponent.ts +243 -0
  27. package/src/helpers/components/orbitComponent.ts +273 -0
  28. package/src/helpers/components/setupCacheInvalidation.ts +44 -0
  29. package/src/helpers/components/setupCacheOnResourceReady.ts +39 -0
  30. package/src/helpers/components/setupComponentSubscriptions.ts +73 -0
  31. package/src/helpers/components/setupOnSaveReset.ts +20 -0
  32. package/src/helpers/datas/dataBuilder.ts +43 -0
  33. package/src/helpers/datas/filterGenerator.ts +29 -0
  34. package/src/helpers/datas/filterObjectByDateAfter.ts +80 -0
  35. package/src/helpers/datas/filterObjectById.ts +54 -0
  36. package/src/helpers/datas/filterObjectByInterval.ts +133 -0
  37. package/src/helpers/datas/filterObjectByNamedValue.ts +103 -0
  38. package/src/helpers/datas/filterObjectByType.ts +30 -0
  39. package/src/helpers/datas/filterObjectByValue.ts +81 -0
  40. package/src/helpers/datas/sort.ts +40 -0
  41. package/src/helpers/i18n/configureLocalization.ts +17 -0
  42. package/src/helpers/index.ts +43 -0
  43. package/src/helpers/mappings/dsp-mapping-config.ts +545 -0
  44. package/src/helpers/ui/formatDate.ts +18 -0
  45. package/src/helpers/ui/lipsum.ts +12 -0
  46. package/src/helpers/utils/requestNavigation.ts +12 -0
  47. package/src/helpers/utils/uniq.ts +6 -0
  48. package/src/index.ts +14 -0
  49. package/src/initializer.ts +11 -0
  50. package/src/mocks/orbit.mock.ts +33 -0
  51. package/src/mocks/user.mock.ts +67 -0
  52. package/src/styles/_helpers/flex.scss +39 -0
  53. package/src/styles/index.scss +14 -0
  54. package/src/styles/modal/ds4go-fact-bundle-modal.scss +89 -0
  55. package/tsconfig.json +36 -0
  56. package/vite.config.ts +48 -0
@@ -0,0 +1,43 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+ export const recusiveRemovePath = (obj: Resource, pathArray: string[]) => {
4
+ if (!obj || pathArray.length === 0) return;
5
+ const key = pathArray.shift();
6
+
7
+ if (key !== undefined) {
8
+ if (pathArray.length === 0) {
9
+ delete obj[key];
10
+ } else if (obj[key] && typeof obj[key] === "object") {
11
+ recusiveRemovePath(obj[key], pathArray);
12
+ }
13
+ }
14
+ };
15
+
16
+ export const dataBuilder = (
17
+ resource: Resource,
18
+ pathToRemove: string[] = [],
19
+ replacements: object = {},
20
+ removeThenReplace = false,
21
+ ): Resource => {
22
+ const clone = structuredClone(resource);
23
+
24
+ if (!removeThenReplace) {
25
+ for (const path of pathToRemove) {
26
+ const splittedPath = path.split(".");
27
+ recusiveRemovePath(clone, splittedPath);
28
+ }
29
+ }
30
+
31
+ for (const [key, value] of Object.entries(replacements)) {
32
+ clone[key] = value;
33
+ }
34
+
35
+ if (removeThenReplace) {
36
+ for (const path of pathToRemove) {
37
+ const splittedPath = path.split(".");
38
+ recusiveRemovePath(clone, splittedPath);
39
+ }
40
+ }
41
+
42
+ return clone;
43
+ };
@@ -0,0 +1,29 @@
1
+ const filterGenerator = (objects: { [key: string]: any }[], field: string) => {
2
+ return objects
3
+ .flatMap((obj) => {
4
+ if (obj[field]) {
5
+ if (Array.isArray(obj[field])) {
6
+ return obj[field].map((v) => ({
7
+ label: v?.name || v,
8
+ value: v?.["@id"] || v,
9
+ }));
10
+ }
11
+ return {
12
+ label: obj[field]?.name || obj[field],
13
+ value: obj[field]?.["@id"] || obj[field],
14
+ };
15
+ }
16
+ return;
17
+ })
18
+ .filter(
19
+ (value, index, self) =>
20
+ value?.label &&
21
+ value?.value &&
22
+ index ===
23
+ self.findIndex(
24
+ (t) => t?.label === value?.label && t?.value === value?.value,
25
+ ),
26
+ );
27
+ };
28
+
29
+ export default filterGenerator;
@@ -0,0 +1,80 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+ function isValidDateValue(value: unknown): value is string | number | Date {
4
+ return !Number.isNaN(new Date((value as Date)).getTime());
5
+ }
6
+
7
+ const checkDateIsAfterRecursive = (
8
+ data: any,
9
+ propName: string,
10
+ thresholdDate: Date
11
+ ): boolean => {
12
+ if (data === null || data === undefined) {
13
+ return false;
14
+ }
15
+
16
+ const propPath = propName.split(".");
17
+ let current = data;
18
+
19
+ for (const segment of propPath) {
20
+ if (current && typeof current === "object" && segment in current) {
21
+ current = current[segment];
22
+ } else {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ if (isValidDateValue(current)) {
28
+ const date = new Date(current);
29
+
30
+ if (date > thresholdDate) {
31
+ return true;
32
+ }
33
+ } else {
34
+ return false;
35
+ }
36
+
37
+ if (Array.isArray(data)) {
38
+ return data.some((item) =>
39
+ checkDateIsAfterRecursive(item, propName, thresholdDate)
40
+ );
41
+ }
42
+
43
+ if (typeof data === "object") {
44
+ return Object.entries(data).some(([key, value]) => {
45
+ if (key === propName && isValidDateValue(value)) {
46
+ return false;
47
+ }
48
+ return checkDateIsAfterRecursive(value, propName, thresholdDate);
49
+ });
50
+ }
51
+
52
+ return false;
53
+ };
54
+
55
+ const filterObjectByDateAfter = (
56
+ array: Resource[],
57
+ propName: string,
58
+ thresholdDateString: string
59
+ ): Resource[] => {
60
+ if (
61
+ !propName ||
62
+ !thresholdDateString ||
63
+ typeof thresholdDateString !== "string"
64
+ ) {
65
+ return array;
66
+ }
67
+
68
+ const thresholdDate = new Date(thresholdDateString);
69
+
70
+ if (Number.isNaN(thresholdDate.getTime())) {
71
+ console.warn(`Invalid threshold date provided: ${thresholdDateString}`);
72
+ return array;
73
+ }
74
+
75
+ return array.filter((obj) =>
76
+ checkDateIsAfterRecursive(obj, propName, thresholdDate)
77
+ );
78
+ };
79
+
80
+ export default filterObjectByDateAfter;
@@ -0,0 +1,54 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+ const filterObjectById = (
4
+ array: Resource[],
5
+ propName: string,
6
+ targetId: string
7
+ ): Resource[] => {
8
+ if (!propName || !targetId) {
9
+ return array;
10
+ }
11
+
12
+ return array.filter((obj) => {
13
+ let current = obj;
14
+ const propPath = propName.split(".");
15
+
16
+ for (const segment of propPath) {
17
+ if (current && typeof current === "object" && segment in current) {
18
+ current = current[segment];
19
+ } else {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ const propValue = current;
25
+
26
+ if (!propValue) {
27
+ return false;
28
+ }
29
+
30
+ if (
31
+ typeof propValue === "object" &&
32
+ !Array.isArray(propValue) &&
33
+ propValue !== null &&
34
+ "@id" in propValue &&
35
+ propValue["@id"] === targetId
36
+ ) {
37
+ return true;
38
+ }
39
+
40
+ if (Array.isArray(propValue)) {
41
+ return propValue.some(
42
+ (item) =>
43
+ typeof item === "object" &&
44
+ item !== null &&
45
+ "@id" in item &&
46
+ item["@id"] === targetId
47
+ );
48
+ }
49
+
50
+ return false;
51
+ });
52
+ };
53
+
54
+ export default filterObjectById;
@@ -0,0 +1,133 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+
4
+ function isValidDateValue(value: unknown): value is string | number | Date {
5
+ if (
6
+ typeof value !== "string" &&
7
+ typeof value !== "number" &&
8
+ !(value instanceof Date)
9
+ ) {
10
+ return false;
11
+ }
12
+
13
+ return !Number.isNaN(new Date(value).getTime());
14
+ }
15
+ const checkValueInIntervalRecursive = (
16
+ data: any,
17
+ propName: string,
18
+ startValue: number | Date,
19
+ endValue: number | Date
20
+ ): boolean => {
21
+ if (data === null || data === undefined) {
22
+ return false;
23
+ }
24
+
25
+ const propPath = propName.split(".");
26
+ let current = data;
27
+
28
+ for (const segment of propPath) {
29
+ if (current && typeof current === "object" && segment in current) {
30
+ current = current[segment];
31
+ } else {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ if (typeof current === "number") {
37
+ if (typeof startValue === "number" && typeof endValue === "number") {
38
+ if (startValue <= current && current <= endValue) {
39
+ return true;
40
+ }
41
+ }
42
+ } else if (isValidDateValue(current)) {
43
+ const date = new Date(current);
44
+ if (startValue instanceof Date && endValue instanceof Date) {
45
+ if (startValue <= date && date <= endValue) {
46
+ return true;
47
+ }
48
+ }
49
+ } else {
50
+ return false;
51
+ }
52
+
53
+ if (Array.isArray(data)) {
54
+ return data.some((item) =>
55
+ checkValueInIntervalRecursive(item, propName, startValue, endValue)
56
+ );
57
+ }
58
+
59
+ if (typeof data === "object") {
60
+ return Object.entries(data).some(([key, value]) =>
61
+ checkValueInIntervalRecursive(value, propName, startValue, endValue)
62
+ );
63
+ }
64
+
65
+ return false;
66
+ };
67
+
68
+ /**
69
+ * Filters an array of Resource objects based on whether a specific date/number property
70
+ * falls within a given interval string (e.g., "2023-01-01/2023-01-31" or "3/6").
71
+ * Checks the property recursively within nested objects and arrays.
72
+ */
73
+ const filterObjectByDateInterval = (
74
+ array: Resource[],
75
+ propName: string,
76
+ interval: string
77
+ ): Resource[] => {
78
+ if (!propName || !interval || typeof interval !== "string") {
79
+ return array;
80
+ }
81
+
82
+ const [startStr, endStr] = interval.split("/");
83
+ if (!startStr) {
84
+ return array;
85
+ }
86
+
87
+ const startDate = new Date(startStr);
88
+
89
+ const endDate = new Date(endStr ?? startStr);
90
+
91
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
92
+ console.warn(`Invalid date interval provided: ${interval}`);
93
+ return array;
94
+ }
95
+
96
+ endDate.setHours(23, 59, 59, 999);
97
+
98
+ if (startDate > endDate) {
99
+ console.warn(`Start date is after end date in interval: ${interval}`);
100
+ return array;
101
+ }
102
+
103
+ return array.filter((obj) =>
104
+ checkValueInIntervalRecursive(obj, propName, startDate, endDate)
105
+ );
106
+ };
107
+
108
+ const filterObjectByInterval = (
109
+ array: Resource[],
110
+ propName: string,
111
+ interval: string
112
+ ): Resource[] => {
113
+ if (!propName || !interval || typeof interval !== "string") {
114
+ return array;
115
+ }
116
+
117
+ const [startStr, endStr] = interval.split("/").map(Number);
118
+
119
+ if (Number.isNaN(startStr) || Number.isNaN(endStr)) {
120
+ return array;
121
+ }
122
+
123
+ if (startStr > endStr) {
124
+ console.warn(`Start number is after end number in interval: ${interval}`);
125
+ return array;
126
+ }
127
+
128
+ return array.filter((obj) =>
129
+ checkValueInIntervalRecursive(obj, propName, startStr, endStr)
130
+ );
131
+ };
132
+
133
+ export default filterObjectByInterval;
@@ -0,0 +1,103 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+ const calculatePertinenceScoreRecursive = (
4
+ data: any,
5
+ lowerCaseString: string,
6
+ isTargetProperty: boolean
7
+ ): number => {
8
+ if (data === null || data === undefined) {
9
+ return 0;
10
+ }
11
+
12
+ if (typeof data !== "object") {
13
+ if (!isTargetProperty) {
14
+ return 0;
15
+ }
16
+
17
+ const valueString = String(data).toLowerCase();
18
+
19
+ const escapedString = lowerCaseString.replace(
20
+ /[.*+?^${}()|[\]\\]/g,
21
+ "\\$&"
22
+ );
23
+
24
+ const wholeWordRegex = new RegExp(`\\b${escapedString}\\b`);
25
+
26
+ let score = 0;
27
+ if (wholeWordRegex.test(valueString)) {
28
+ score = 2;
29
+ } else if (valueString.includes(lowerCaseString)) {
30
+ score = 1;
31
+ }
32
+ return score;
33
+ }
34
+
35
+ if (Array.isArray(data)) {
36
+ let maxScore = 0;
37
+ for (const item of data) {
38
+ maxScore = Math.max(
39
+ maxScore,
40
+ calculatePertinenceScoreRecursive(
41
+ item,
42
+ lowerCaseString,
43
+ isTargetProperty
44
+ )
45
+ );
46
+ if (maxScore === 2) break;
47
+ }
48
+ return maxScore;
49
+ }
50
+
51
+ let maxScore = 0;
52
+ for (const value of Object.values(data)) {
53
+ maxScore = Math.max(
54
+ maxScore,
55
+ calculatePertinenceScoreRecursive(
56
+ value,
57
+ lowerCaseString,
58
+ isTargetProperty
59
+ )
60
+ );
61
+ if (maxScore === 2) break;
62
+ }
63
+ return maxScore;
64
+ };
65
+
66
+ const filterObjectByNamedValue = (
67
+ array: Resource[],
68
+ propName: string,
69
+ searchString: string
70
+ ): Resource[] => {
71
+ if (!propName || !searchString || searchString.trim() === "") {
72
+ return array;
73
+ }
74
+
75
+ const lowerCaseString = searchString.toLowerCase();
76
+
77
+ const scoredObjects = array.map((obj) => {
78
+ let current: any = obj;
79
+ const propPath = propName.split(".");
80
+ for (const segment of propPath) {
81
+ if (current && typeof current === "object" && segment in current) {
82
+ current = current[segment];
83
+ } else {
84
+ current = null;
85
+ break;
86
+ }
87
+ }
88
+
89
+ let score = 0;
90
+ if (current) {
91
+ score = calculatePertinenceScoreRecursive(current, lowerCaseString, true);
92
+ }
93
+ return { obj, score };
94
+ });
95
+
96
+ const filteredObjects = scoredObjects.filter((item) => item.score > 0);
97
+
98
+ filteredObjects.sort((a, b) => b.score - a.score);
99
+
100
+ return filteredObjects.map((item) => item.obj);
101
+ };
102
+
103
+ export default filterObjectByNamedValue;
@@ -0,0 +1,30 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+ const filterObjectByType = (
4
+ array: Resource[],
5
+ targetType: string
6
+ ): Resource[] => {
7
+ if (!targetType) {
8
+ return array;
9
+ }
10
+
11
+ return array.filter((obj) => {
12
+ const type = obj["@type"];
13
+
14
+ if (!type) {
15
+ return false;
16
+ }
17
+
18
+ if (typeof type === "string") {
19
+ return type === targetType;
20
+ }
21
+
22
+ if (Array.isArray(type)) {
23
+ return type.some((t) => t === targetType);
24
+ }
25
+
26
+ return false;
27
+ });
28
+ };
29
+
30
+ export default filterObjectByType;
@@ -0,0 +1,81 @@
1
+ import type { Resource } from "@src/component";
2
+
3
+ const calculatePertinenceScore = (
4
+ data: any,
5
+ lowerCaseString: string,
6
+ key?: string
7
+ ): number => {
8
+ if (data === null || data === undefined) {
9
+ return 0;
10
+ }
11
+
12
+ if (typeof data !== "object") {
13
+ const valueString = String(data).toLowerCase();
14
+
15
+ const escapedString = lowerCaseString.replace(
16
+ /[.*+?^${}()|[\]\\]/g,
17
+ "\\$&"
18
+ );
19
+
20
+ const wholeWordRegex = new RegExp(`\\b${escapedString}\\b`);
21
+
22
+ let score = 0;
23
+ if (wholeWordRegex.test(valueString)) {
24
+ if (key === "name" || key === "title") {
25
+ score = 4;
26
+ } else {
27
+ score = 3;
28
+ }
29
+ } else if (valueString.includes(lowerCaseString)) {
30
+ if (key === "name" || key === "title") {
31
+ score = 2;
32
+ } else {
33
+ score = 1;
34
+ }
35
+ }
36
+ return score;
37
+ }
38
+
39
+ if (Array.isArray(data)) {
40
+ let maxScore = 0;
41
+ for (const item of data) {
42
+ maxScore = Math.max(
43
+ maxScore,
44
+ calculatePertinenceScore(item, lowerCaseString)
45
+ );
46
+ if (maxScore === 4) break;
47
+ }
48
+ return maxScore;
49
+ }
50
+
51
+ let maxScore = 0;
52
+ for (const [currentKey, value] of Object.entries(data)) {
53
+ maxScore = Math.max(
54
+ maxScore,
55
+ calculatePertinenceScore(value, lowerCaseString, currentKey)
56
+ );
57
+ if (maxScore === 4) break;
58
+ }
59
+ return maxScore;
60
+ };
61
+
62
+ const filterObjectByValue = (array: Resource[], searchString: string): any[] => {
63
+ if (!searchString || searchString.trim() === "") {
64
+ return array;
65
+ }
66
+
67
+ const lowerCaseString = searchString.toLowerCase();
68
+
69
+ const scoredObjects = array.map((obj) => ({
70
+ obj,
71
+ score: calculatePertinenceScore(obj, lowerCaseString),
72
+ }));
73
+
74
+ const filteredObjects = scoredObjects.filter((item) => item.score > 0);
75
+
76
+ filteredObjects.sort((a, b) => b.score - a.score);
77
+
78
+ return filteredObjects.map((item) => item.obj);
79
+ };
80
+
81
+ export default filterObjectByValue;
@@ -0,0 +1,40 @@
1
+ const getValueFromAnyKey = (obj: any, keys: string[]): any => {
2
+ let value = obj;
3
+ for (let i = 0; i < keys.length; i++) {
4
+ if (value && keys[i] in value) {
5
+ value = value[keys[i]];
6
+ } else {
7
+ break;
8
+ }
9
+ }
10
+ return value;
11
+ };
12
+
13
+ type Sort = (arr: any[], k: string | string[], order?: "asc" | "desc") => any[];
14
+
15
+ const sort: Sort = (arr, k, order = "asc") => {
16
+ return arr.sort((a, b) => {
17
+ let aN = a;
18
+ let bN = b;
19
+ if (Array.isArray(k)) {
20
+ aN = getValueFromAnyKey(a, k);
21
+ bN = getValueFromAnyKey(b, k);
22
+ } else {
23
+ aN = a[k];
24
+ bN = b[k];
25
+ }
26
+ let c = 0;
27
+ if (typeof aN === "number" && typeof bN === "number") {
28
+ c = aN - bN;
29
+ } else if (typeof aN === "string" && typeof bN === "string") {
30
+ c = aN.localeCompare(bN);
31
+ } else if (aN instanceof Date && bN instanceof Date) {
32
+ c = aN.getTime() - bN.getTime();
33
+ } else {
34
+ throw new TypeError(`Unsupported data type for key "${k}"`);
35
+ }
36
+ return order === "asc" ? c : -c;
37
+ });
38
+ };
39
+
40
+ export default sort;
@@ -0,0 +1,17 @@
1
+ import { configureLocalization } from "@lit/localize";
2
+ import { sourceLocale, targetLocales } from "@src/generated/locale-codes.js";
3
+
4
+ const langs = import.meta.glob("../../generated/locales/**/*.ts", { eager: true });
5
+
6
+ const localizedTemplates: Map<string, any> = new Map(
7
+ targetLocales.map((locale) => [
8
+ locale,
9
+ langs[`../../generated/locales/${locale}.ts`],
10
+ ]),
11
+ );
12
+
13
+ export const { getLocale, setLocale } = configureLocalization({
14
+ sourceLocale,
15
+ targetLocales,
16
+ loadLocale: async (locale) => localizedTemplates.get(locale),
17
+ });
@@ -0,0 +1,43 @@
1
+ import { ComponentObjectHandler } from "@helpers/components/componentObjectHandler";
2
+ import { ComponentObjectsHandler } from "@helpers/components/componentObjectsHandler";
3
+ // import dspComponent from "@helpers/components/dspComponent";
4
+ import OrbitComponent from "@helpers/components/orbitComponent";
5
+ import setupCacheInvalidation from "@helpers/components/setupCacheInvalidation";
6
+ import setupCacheOnResourceReady from "@helpers/components/setupCacheOnResourceReady";
7
+ import setupComponentSubscriptions from "@helpers/components/setupComponentSubscriptions";
8
+ import setupOnSaveReset from "@helpers/components/setupOnSaveReset";
9
+ import filterGenerator from "@helpers/datas/filterGenerator";
10
+ import filterObjectByDateAfter from "@helpers/datas/filterObjectByDateAfter";
11
+ import filterObjectById from "@helpers/datas/filterObjectById";
12
+ import filterObjectByInterval from "@helpers/datas/filterObjectByInterval";
13
+ import filterObjectByNamedValue from "@helpers/datas/filterObjectByNamedValue";
14
+ import filterObjectByType from "@helpers/datas/filterObjectByType";
15
+ import filterObjectByValue from "@helpers/datas/filterObjectByValue";
16
+ import sort from "@helpers/datas/sort";
17
+ import formatDate from "@helpers/ui/formatDate";
18
+ import requestNavigation from "@helpers/utils/requestNavigation";
19
+ import uniq from "@helpers/utils/uniq";
20
+ import CLIENT_CONTEXT from "@src/context.json";
21
+
22
+ export {
23
+ // dspComponent,
24
+ CLIENT_CONTEXT,
25
+ filterGenerator,
26
+ filterObjectByDateAfter,
27
+ filterObjectById,
28
+ filterObjectByInterval,
29
+ filterObjectByNamedValue,
30
+ filterObjectByValue,
31
+ filterObjectByType,
32
+ formatDate,
33
+ ComponentObjectHandler,
34
+ ComponentObjectsHandler,
35
+ OrbitComponent,
36
+ requestNavigation,
37
+ setupCacheInvalidation,
38
+ setupCacheOnResourceReady,
39
+ setupComponentSubscriptions,
40
+ setupOnSaveReset,
41
+ sort,
42
+ uniq,
43
+ };