@webitel/ui-sdk 24.12.85 → 24.12.87

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 (48) hide show
  1. package/CHANGELOG.md +54 -8
  2. package/dist/img/sprite/add-filter.svg +8 -0
  3. package/dist/img/sprite/index.js +2 -0
  4. package/dist/ui-sdk.css +1 -1
  5. package/dist/ui-sdk.js +1416 -1415
  6. package/dist/ui-sdk.umd.cjs +15 -15
  7. package/package.json +1 -1
  8. package/src/assets/icons/sprite/add-filter.svg +8 -0
  9. package/src/assets/icons/sprite/index.js +2 -0
  10. package/src/components/index.js +71 -0
  11. package/src/components/wt-action-bar/wt-action-bar.vue +9 -4
  12. package/src/components/wt-icon-action/iconMappings.js +1 -0
  13. package/src/components/wt-loader/_internals/wt-loader--md.vue +3 -2
  14. package/src/components/wt-tooltip/_internals/useTooltipTriggerSubscriptions.js +0 -1
  15. package/src/components/wt-tooltip/wt-tooltip.vue +4 -1
  16. package/src/css/main.scss +0 -1
  17. package/src/css/pages/table-page.scss +0 -1
  18. package/src/css/styleguide/placeholder/_placeholder.scss +2 -2
  19. package/src/enums/IconAction/IconAction.enum.js +1 -0
  20. package/src/locale/en/en.js +19 -0
  21. package/src/locale/ru/ru.js +1 -0
  22. package/src/locale/ua/ua.js +1 -0
  23. package/src/modules/Filters/v2/filters/classes/Filter.ts +30 -0
  24. package/src/modules/Filters/v2/filters/classes/FilterStorage.ts +34 -0
  25. package/src/modules/Filters/v2/filters/classes/FilterStorageOptions.d.ts +6 -0
  26. package/src/modules/Filters/v2/filters/classes/FiltersManager.ts +189 -0
  27. package/src/modules/Filters/v2/filters/components/dynamic/config/dynamic-filter-config-form.vue +146 -0
  28. package/src/modules/Filters/v2/filters/components/dynamic/config/dynamic-filter-config-view.vue +29 -0
  29. package/src/modules/Filters/v2/filters/components/dynamic/dynamic-filter-add-action.vue +41 -0
  30. package/src/modules/Filters/v2/filters/components/dynamic/dynamic-filter-panel-wrapper.vue +32 -0
  31. package/src/modules/Filters/v2/filters/components/dynamic/preview/dynamic-filter-preview-info.vue +48 -0
  32. package/src/modules/Filters/v2/filters/components/dynamic/preview/dynamic-filter-preview.vue +61 -0
  33. package/src/modules/Filters/v2/filters/components/static/search-filter.vue +14 -0
  34. package/src/modules/Filters/v2/filters/components/table-filters-panel.vue +87 -0
  35. package/src/modules/Filters/v2/filters/createTableFiltersStore.ts +81 -0
  36. package/src/modules/Filters/v2/filters/index.ts +27 -0
  37. package/src/modules/Filters/v2/filters/scripts/utils.ts +31 -0
  38. package/src/modules/Filters/v2/filters/types/Filter.d.ts +41 -0
  39. package/src/modules/Filters/v2/filters/types/FiltersManager.d.ts +76 -0
  40. package/src/modules/Filters/v2/headers/createTableHeadersStore.ts +89 -0
  41. package/src/modules/Filters/v2/index.ts +0 -0
  42. package/src/modules/Filters/v2/pagination/createTablePaginationStore.ts +54 -0
  43. package/src/modules/Filters/v2/persist/PersistedStorage.types.ts +51 -0
  44. package/src/modules/Filters/v2/persist/useLocalStoragePersistedStorage.ts +31 -0
  45. package/src/modules/Filters/v2/persist/usePersistedStorage.ts +150 -0
  46. package/src/modules/Filters/v2/persist/useRoutePersistedStorage.ts +41 -0
  47. package/src/modules/Filters/v2/table/createTableStore.store.ts +180 -0
  48. package/src/modules/Filters/v2/types/tableStore.types.ts +60 -0
@@ -0,0 +1,76 @@
1
+ import type {
2
+ FilterConfig,
3
+ FilterInitParams,
4
+ FilterLabel,
5
+ FilterName,
6
+ FilterValue,
7
+ IFilter,
8
+ } from './Filter.types.ts';
9
+
10
+ export interface IFiltersManager {
11
+ filters: Map<FilterName, IFilter>;
12
+
13
+ hasFilter: (name: FilterName) => boolean;
14
+ getFilter: (name: FilterName) => IFilter;
15
+ addFilter: (
16
+ params: FilterInitParams,
17
+ payload?: object,
18
+ config?: FilterConfig,
19
+ ) => IFilter;
20
+ updateFilter: ({
21
+ name,
22
+ }: {
23
+ name: FilterName;
24
+ value?: FilterValue;
25
+ label?: FilterLabel;
26
+ }) => IFilter;
27
+ deleteFilter: (name: FilterName) => IFilter;
28
+
29
+ /**
30
+ * Converts filters data to String, that can be stored
31
+ */
32
+ toString: () => string;
33
+
34
+ /**
35
+ * Restores filters from string
36
+ */
37
+ fromString: (snapshotStr: string) => void;
38
+
39
+ /**
40
+ * deletes filters
41
+ * If include/exclude are not provided, all filters will be deleted
42
+ */
43
+ reset: ({
44
+ include,
45
+ exclude,
46
+ }?: {
47
+ include?: FilterName[];
48
+ exclude?: FilterName[];
49
+ }) => void;
50
+
51
+ /**
52
+ * @returns Array<FilterName>
53
+ */
54
+ getAllKeys: () => FilterName[];
55
+
56
+ /**
57
+ * @returns { FilterName: FilterValue }
58
+ */
59
+
60
+ getAllValues: () => { [name: FilterName]: FilterValue };
61
+ /**
62
+ * @returns Array<IFilter>
63
+ * @param include
64
+ * @param exclude
65
+ */
66
+
67
+ getFiltersList: ({
68
+ include,
69
+ exclude,
70
+ }?: {
71
+ include?: FilterName[];
72
+ exclude?: FilterName[];
73
+ }) => IFilter[];
74
+ }
75
+
76
+ export type FiltersManagerConfig = FilterConfig;
@@ -0,0 +1,89 @@
1
+ import { computed, ref } from 'vue';
2
+ import { defineStore } from 'pinia';
3
+ import { sortToQueryAdapter } from '../../../../scripts';
4
+ import { SortSymbols } from '../../../../scripts/sortQueryAdapters';
5
+
6
+ export const createTableHeadersStore = (
7
+ namespace: string,
8
+ { headers: rawHeaders },
9
+ ) => {
10
+ const id = `${namespace}/headers`;
11
+
12
+ return defineStore(id, () => {
13
+ const headers = ref(rawHeaders);
14
+
15
+ const shownHeaders = computed(() => {
16
+ return headers.value.filter((header) => header.show);
17
+ });
18
+
19
+ const fields = computed(() => {
20
+ return shownHeaders.value.map((header) => header.field);
21
+ });
22
+
23
+ const sort = computed(() => {
24
+ const encodeSortQuery = ({ column, order }) =>
25
+ `${sortToQueryAdapter(order)}${column.field}`;
26
+
27
+ const sortedCol = headers.value.find((header) => header.sort);
28
+
29
+ return sortedCol
30
+ ? encodeSortQuery({ column: sortedCol, order: sortedCol.sort })
31
+ : null;
32
+ });
33
+
34
+ const updateShownHeaders = () => {};
35
+
36
+ const updateSort = (column) => {
37
+ const getNextSortOrder = (sort) => {
38
+ switch (sort) {
39
+ case SortSymbols.NONE:
40
+ return SortSymbols.ASC;
41
+ case SortSymbols.ASC:
42
+ return SortSymbols.DESC;
43
+ case SortSymbols.DESC:
44
+ return SortSymbols.NONE;
45
+ default:
46
+ return SortSymbols.ASC;
47
+ }
48
+ };
49
+
50
+ const changeHeadersSort = ({ headers, sortedHeader, order }) => {
51
+ return headers.map((header) => {
52
+ if (header.sort === undefined) return header;
53
+
54
+ // reset all headers by default
55
+ let newSort = null;
56
+
57
+ if (header.field === sortedHeader.field) {
58
+ newSort = order;
59
+ }
60
+
61
+ return {
62
+ ...header,
63
+ sort: newSort,
64
+ };
65
+ });
66
+ };
67
+
68
+ const order = getNextSortOrder(column.sort);
69
+
70
+ const newHeaders = changeHeadersSort({
71
+ headers: headers.value,
72
+ sortedHeader: column,
73
+ order,
74
+ });
75
+
76
+ headers.value = newHeaders;
77
+ };
78
+
79
+ return {
80
+ headers,
81
+ shownHeaders,
82
+ fields,
83
+ sort,
84
+
85
+ updateShownHeaders,
86
+ updateSort,
87
+ };
88
+ });
89
+ };
File without changes
@@ -0,0 +1,54 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref } from 'vue';
3
+
4
+ import { usePersistedStorage } from '../persist/usePersistedStorage.ts';
5
+
6
+ export const createTablePaginationStore = (namespace: string) => {
7
+ const id = `${namespace}/pagination`;
8
+
9
+ return defineStore(id, () => {
10
+ const page = ref(1);
11
+ const size = ref(10);
12
+ const next = ref(false);
13
+
14
+ const updatePage = (newPage: number) => {
15
+ page.value = newPage;
16
+ };
17
+
18
+ const updateSize = (newSize: number) => {
19
+ size.value = newSize;
20
+ };
21
+
22
+ const $reset = () => {
23
+ page.value = 1;
24
+ size.value = 10;
25
+ next.value = false;
26
+ };
27
+
28
+ const setupPersistence = async () => {
29
+ const { restore: restorePage } = usePersistedStorage({
30
+ name: 'page',
31
+ value: page,
32
+ });
33
+
34
+ const { restore: restoreSize } = usePersistedStorage({
35
+ name: 'size',
36
+ value: size,
37
+ });
38
+
39
+ return Promise.allSettled([restorePage(), restoreSize()]);
40
+ };
41
+
42
+ return {
43
+ page,
44
+ size,
45
+ next,
46
+
47
+ updatePage,
48
+ updateSize,
49
+
50
+ setupPersistence,
51
+ $reset,
52
+ };
53
+ });
54
+ };
@@ -0,0 +1,51 @@
1
+ import { Ref, WatchOptions } from 'vue';
2
+
3
+ export enum PersistedStorageType {
4
+ LocalStorage = 'localStorage',
5
+ Route = 'route',
6
+ }
7
+
8
+ // in route query, or in localStorage
9
+ export type PersistStorableValue = string;
10
+
11
+ export type PersistableValue =
12
+ | PersistStorableValue
13
+ | { toString: () => PersistStorableValue };
14
+
15
+ export interface StorageLike {
16
+ getItem(key: string): Promise<PersistableValue | null>;
17
+
18
+ setItem(key: string, value: PersistableValue): Promise<void>;
19
+
20
+ removeItem(key: string): Promise<void>;
21
+ }
22
+
23
+ export interface PersistedPropertyConfig {
24
+ name: string;
25
+ value: Ref<PersistableValue>;
26
+ storages?: PersistedStorageType | PersistedStorageType[];
27
+ storagePath?: string;
28
+ startWatchManually?: boolean;
29
+ watchConfig?: WatchOptions;
30
+ onStore?: (
31
+ save: ({
32
+ name,
33
+ value,
34
+ }: {
35
+ name: string;
36
+ value: PersistableValue;
37
+ }) => Promise<void>,
38
+ { value, name },
39
+ ) => Promise<void>;
40
+ onRestore?: (
41
+ restore: (name: string) => Promise<PersistableValue>,
42
+ name: string,
43
+ ) => Promise<void>;
44
+ }
45
+
46
+ export interface PersistedStorageController {
47
+ watch: () => void;
48
+ unwatch: () => void;
49
+ restore: () => Promise<void>;
50
+ reset: () => Promise<void>;
51
+ }
@@ -0,0 +1,31 @@
1
+ import { StorageLike } from './PersistedStorage.types.ts';
2
+
3
+ const separator = ';';
4
+
5
+ const makePath = (storagePath: string, key: string) => `${storagePath}/${key}`;
6
+
7
+ export const useLocalStoragePersistedStorage = ({ storagePath = '' }: { storagePath: string }): StorageLike => {
8
+ const getItem = async (key: string) => {
9
+ const value = localStorage.getItem(makePath(storagePath, key));
10
+ try {
11
+ return value.split(separator);
12
+ } catch {
13
+ return value;
14
+ }
15
+ };
16
+
17
+ const setItem = async (key: string, inputValue: string | string[]) => {
18
+ const value = Array.isArray(inputValue) ? inputValue.join(separator) : inputValue;
19
+ localStorage.setItem(makePath(storagePath, key), value);
20
+ }
21
+
22
+ const removeItem = async (key: string) => {
23
+ localStorage.removeItem(makePath(storagePath, key));
24
+ }
25
+
26
+ return {
27
+ getItem,
28
+ setItem,
29
+ removeItem,
30
+ };
31
+ }
@@ -0,0 +1,150 @@
1
+ import { watch } from 'vue';
2
+
3
+ import {
4
+ PersistableValue,
5
+ PersistedPropertyConfig,
6
+ PersistedStorageController,
7
+ PersistedStorageType,
8
+ } from './PersistedStorage.types.ts';
9
+ import { useLocalStoragePersistedStorage } from './useLocalStoragePersistedStorage.ts';
10
+ import { useRoutePersistedStorage } from './useRoutePersistedStorage.ts';
11
+
12
+ export const usePersistedStorage = ({
13
+ name,
14
+ value,
15
+ storages: configStorages = [PersistedStorageType.Route],
16
+ storagePath,
17
+ startWatchManually = false,
18
+ onStore,
19
+ onRestore,
20
+ }: PersistedPropertyConfig): PersistedStorageController => {
21
+ let unwatch = null;
22
+
23
+ const setItemFns = [];
24
+ const getItemFns: Array<(name: string) => Promise<PersistableValue>> = [];
25
+ const removeItemFns = [];
26
+
27
+ const composedValueGetter = async (name: string): Promise<PersistableValue[]> => {
28
+ const settledResults = await Promise.allSettled(
29
+ getItemFns.map((getter) => getter(name))
30
+ );
31
+
32
+ return settledResults.reduce((acc, result) => {
33
+ if (result.status === 'fulfilled') {
34
+ return [...acc, result.value];
35
+ }
36
+ return acc;
37
+ }, []);
38
+ };
39
+
40
+ const storages = Array.isArray(configStorages)
41
+ ? configStorages
42
+ : [configStorages];
43
+
44
+ /*
45
+ order matters, as the first storage in the list has the highest priority
46
+ */
47
+ if (storages.includes(PersistedStorageType.Route)) {
48
+ const { setItem, getItem, removeItem } = useRoutePersistedStorage();
49
+ setItemFns.push(setItem);
50
+ getItemFns.push(getItem);
51
+ removeItemFns.push(removeItem);
52
+ }
53
+
54
+ if (storages.includes(PersistedStorageType.LocalStorage)) {
55
+ const { setItem, getItem, removeItem } = useLocalStoragePersistedStorage({
56
+ storagePath,
57
+ });
58
+ setItemFns.push(setItem);
59
+ getItemFns.push(getItem);
60
+ removeItemFns.push(removeItem);
61
+ }
62
+
63
+ const startWatch = () => {
64
+ unwatch = watch(value, async () => {
65
+ /*
66
+ if onStore callback is provided,
67
+ call custom logic for storing value
68
+ */
69
+ if (onStore) {
70
+ /*
71
+ wrap all setItemFns in one callback
72
+ so that onStore is called only once on each value change
73
+ */
74
+ const save = async ({ name, value: storedValue }) => {
75
+ // await Promise.allSettled(
76
+ // setItemFns.map((setItem) => {
77
+ // return setItem({ name, value: storedValue });
78
+ // }),
79
+ // );
80
+ setItemFns.forEach((setter) => {
81
+ setter(name, storedValue);
82
+ });
83
+ };
84
+ await onStore(save, { name, value });
85
+ } else {
86
+ /*
87
+ else, perform default storing logic
88
+ */
89
+ const storedValue = value.value;
90
+ setItemFns.forEach((setter) => {
91
+ setter(name, storedValue);
92
+ });
93
+ }
94
+ }, { deep: true });
95
+ };
96
+
97
+ const restore = async () => {
98
+ /*
99
+ if onRestore callback is provided,
100
+ call custom logic for restoring value
101
+ */
102
+ if (onRestore) {
103
+ /*
104
+ wrap all getItemFns in one callback
105
+ so that onRestore is called only once on each value change
106
+ */
107
+ const restore = async (name: string) => {
108
+ const restoredValues = await composedValueGetter(name);
109
+ /*
110
+ not sure if value to restore should be picked automatically
111
+ before running onRestore
112
+ */
113
+ return restoredValues.find((value) => {
114
+ return value !== null;
115
+ });
116
+ };
117
+ await onRestore(restore, name);
118
+ } else {
119
+ /*
120
+ else, perform default restoring logic
121
+ */
122
+ const restoredValues = await composedValueGetter(name);
123
+ const restoredValue = restoredValues.find((value) => value !== null);
124
+
125
+ if (restoredValue !== undefined) {
126
+ value.value = restoredValue;
127
+ }
128
+ }
129
+ /*
130
+ start watching after restoring value to prevent restored value
131
+ from storing again
132
+ */
133
+ if (!startWatchManually) {
134
+ startWatch();
135
+ }
136
+ };
137
+
138
+ const reset = async () => {
139
+ await Promise.all(removeItemFns.map((removeItem) => removeItem(name)));
140
+ };
141
+
142
+ const endWatch = () => unwatch && unwatch();
143
+
144
+ return {
145
+ watch: startWatch,
146
+ unwatch: endWatch,
147
+ restore,
148
+ reset,
149
+ };
150
+ };
@@ -0,0 +1,41 @@
1
+ import { useRoute, useRouter } from 'vue-router';
2
+
3
+ import { StorageLike } from './PersistedStorage.types.ts';
4
+
5
+ export const useRoutePersistedStorage = (): StorageLike => {
6
+ const router = useRouter();
7
+ const route = useRoute();
8
+
9
+ const getItem = async (key: string) => {
10
+ return route.query[key];
11
+ };
12
+
13
+ const setItem = async (key: string, value: string | string[]) => {
14
+ await router.replace({
15
+ name: route.name,
16
+ params: route.params,
17
+ hash: route.hash,
18
+ query: {
19
+ ...route.query,
20
+ [key]: value,
21
+ },
22
+ });
23
+ };
24
+
25
+ const removeItem = async (key: string) => {
26
+ const query = { ...route.query };
27
+ delete query[key];
28
+ await router.replace({
29
+ name: route.name,
30
+ params: route.params,
31
+ hash: route.hash,
32
+ query,
33
+ });
34
+ };
35
+
36
+ return {
37
+ getItem,
38
+ setItem,
39
+ removeItem,
40
+ };
41
+ };
@@ -0,0 +1,180 @@
1
+ import set from 'lodash/fp/set';
2
+ import { defineStore, storeToRefs } from 'pinia';
3
+ import { type Ref, ref, watch } from 'vue';
4
+
5
+ import { createTableFiltersStore } from '../filters/createTableFiltersStore.ts';
6
+ import { createTableHeadersStore } from '../headers/createTableHeadersStore.ts';
7
+ import { createTablePaginationStore } from '../pagination/createTablePaginationStore.ts';
8
+ import {
9
+ PatchItemPropertyParams,
10
+ TableStore,
11
+ useTableStoreParams,
12
+ } from '../types/tableStore.types.ts';
13
+
14
+ export const createTableStore = <Entity extends { id: string; etag?: string }>(
15
+ namespace: string,
16
+ { parentId, apiModule, headers }: useTableStoreParams<Entity>,
17
+ ) => {
18
+ const usePaginationStore = createTablePaginationStore(namespace);
19
+ const useHeadersStore = createTableHeadersStore(namespace, { headers });
20
+ const useFiltersStore = createTableFiltersStore(namespace);
21
+
22
+ return defineStore(namespace, (): TableStore<Entity> => {
23
+ const paginationStore = usePaginationStore();
24
+ const { page, size, next } = storeToRefs(paginationStore);
25
+ const {
26
+ updatePage,
27
+ updateSize,
28
+ // $reset: $resetPaginationStore,
29
+ $patch: $patchPaginationStore,
30
+ setupPersistence: setupPaginationPersistence,
31
+ } = paginationStore;
32
+
33
+ const headersStore = useHeadersStore();
34
+ const { headers, shownHeaders, fields, sort } = storeToRefs(headersStore);
35
+ const { updateSort, updateShownHeaders } = headersStore;
36
+
37
+ const filtersStore = useFiltersStore();
38
+ const { filtersManager, isRestoring: isFiltersRestoring } =
39
+ storeToRefs(filtersStore);
40
+ const {
41
+ hasFilter,
42
+ addFilter,
43
+ updateFilter,
44
+ deleteFilter,
45
+ setupPersistence: setupFiltersPersistence,
46
+ } = filtersStore;
47
+
48
+ const dataList: Ref<Entity[]> = ref([]);
49
+ const selected: Ref<Entity[]> = ref([]);
50
+ const error = ref(null);
51
+ const isLoading = ref(false);
52
+
53
+ const loadDataList = async () => {
54
+ isLoading.value = true;
55
+ $patchPaginationStore({ next: false });
56
+
57
+ const params = {
58
+ ...filtersManager.value.getAllValues(),
59
+ page: page.value,
60
+ size: size.value,
61
+ sort: sort.value,
62
+ fields: fields.value,
63
+ parentId,
64
+ };
65
+
66
+ try {
67
+ const { items, next } = await apiModule.getList(params);
68
+
69
+ dataList.value = items;
70
+ $patchPaginationStore({ next });
71
+ } catch (err) {
72
+ error.value = err;
73
+ throw err;
74
+ } finally {
75
+ isLoading.value = false;
76
+ }
77
+ };
78
+
79
+ const patchItemProperty = async ({
80
+ index,
81
+ path,
82
+ value,
83
+ }: PatchItemPropertyParams) => {
84
+ const item = dataList.value[index];
85
+ const changes = {};
86
+ set(path, value, changes);
87
+
88
+ try {
89
+ await apiModule.patch({
90
+ changes,
91
+ parentId,
92
+ id: item.id,
93
+ etag: item.etag,
94
+ });
95
+ set(path, value, item);
96
+ } catch (err) {
97
+ await loadDataList();
98
+ throw err;
99
+ }
100
+ };
101
+
102
+ const deleteEls = async (_els: Entity[]) => {
103
+ const els = Array.isArray(_els) ? _els : [_els];
104
+ const deleteEl = (el: Entity) => {
105
+ return apiModule.delete({ id: el.id });
106
+ };
107
+
108
+ try {
109
+ await Promise.all(els.map(deleteEl));
110
+ } finally {
111
+ await loadDataList();
112
+ }
113
+ };
114
+
115
+ const initialize = async () => {
116
+ await Promise.allSettled([
117
+ setupPaginationPersistence(),
118
+ setupFiltersPersistence(),
119
+ ]);
120
+
121
+ let loadingAfterFiltersChange = false;
122
+
123
+ watch(
124
+ [() => filtersManager.value.getAllValues(), sort, size],
125
+ async () => {
126
+ loadingAfterFiltersChange = true;
127
+ updatePage(1);
128
+ await loadDataList();
129
+ loadingAfterFiltersChange = false;
130
+ },
131
+ /* filtersManager requires deep watching for its values */
132
+ { deep: true },
133
+ );
134
+
135
+ watch([page], () => {
136
+ if (!loadingAfterFiltersChange) {
137
+ return loadDataList();
138
+ }
139
+ });
140
+
141
+ return loadDataList();
142
+ };
143
+
144
+ return {
145
+ dataList,
146
+ selected,
147
+ error,
148
+ isLoading,
149
+
150
+ page,
151
+ size,
152
+ next,
153
+
154
+ headers,
155
+ shownHeaders,
156
+ fields,
157
+ sort,
158
+
159
+ filtersManager,
160
+ isFiltersRestoring,
161
+
162
+ initialize,
163
+
164
+ loadDataList,
165
+ patchItemProperty,
166
+ deleteEls,
167
+
168
+ updatePage,
169
+ updateSize,
170
+
171
+ updateSort,
172
+ updateShownHeaders,
173
+
174
+ hasFilter,
175
+ addFilter,
176
+ updateFilter,
177
+ deleteFilter,
178
+ };
179
+ });
180
+ };