@logimaxx/kviews.js 1.2.3 → 1.3.1
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/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/dist/index.js +243 -33
- package/dist/index.js.map +2 -2
- package/dist/kviews.js +245 -35
- package/dist/kviews.js.map +2 -2
- package/dist/kviews.min.js +6 -6
- package/package.json +5 -4
package/dist/kviews.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* KViews - Class-based API data binding library
|
|
3
|
-
* Version: 1.
|
|
4
|
-
* Built: 2026-06-
|
|
3
|
+
* Version: 1.3.1
|
|
4
|
+
* Built: 2026-06-29T17:09:29.460Z
|
|
5
5
|
*/
|
|
6
6
|
var KViews = (() => {
|
|
7
7
|
var __defProp = Object.defineProperty;
|
|
@@ -40,6 +40,7 @@ var KViews = (() => {
|
|
|
40
40
|
createOverlay: () => createOverlay,
|
|
41
41
|
createURL: () => createURL,
|
|
42
42
|
dbg: () => dbg,
|
|
43
|
+
deepEqual: () => deepEqual,
|
|
43
44
|
deepmerge: () => deepmerge,
|
|
44
45
|
default: () => index_default,
|
|
45
46
|
error: () => error,
|
|
@@ -89,6 +90,36 @@ var KViews = (() => {
|
|
|
89
90
|
}
|
|
90
91
|
throw new Error("Invalid options", options);
|
|
91
92
|
}
|
|
93
|
+
function deepEqual(a, b) {
|
|
94
|
+
if (a === b) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
101
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
for (let i = 0; i < a.length; i++) {
|
|
105
|
+
if (!deepEqual(a[i], b[i])) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
const keysA = Object.keys(a);
|
|
112
|
+
const keysB = Object.keys(b);
|
|
113
|
+
if (keysA.length !== keysB.length) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
for (const key of keysA) {
|
|
117
|
+
if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
92
123
|
function deepmerge(target, source, optionsArgument) {
|
|
93
124
|
function defaultArrayMerge(target2, source2, optionsArgument2) {
|
|
94
125
|
let destination = target2.slice();
|
|
@@ -906,6 +937,29 @@ var KViews = (() => {
|
|
|
906
937
|
url.parameters[`page[${type}][limit]`] = pageSize;
|
|
907
938
|
}
|
|
908
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* Read pagination params already present in a collection URL.
|
|
942
|
+
*
|
|
943
|
+
* @param {import('../URL.js').URL} url - Collection URL
|
|
944
|
+
* @param {{ type?: string }} [context]
|
|
945
|
+
* @returns {{ offset?: number, pageSize?: number }}
|
|
946
|
+
*/
|
|
947
|
+
extractListQueryFromUrl(url, context = {}) {
|
|
948
|
+
const { type } = context;
|
|
949
|
+
const result = {};
|
|
950
|
+
if (!url || !url.parameters || !type) {
|
|
951
|
+
return result;
|
|
952
|
+
}
|
|
953
|
+
const limitKey = `page[${type}][limit]`;
|
|
954
|
+
const offsetKey = `page[${type}][offset]`;
|
|
955
|
+
if (url.parameters.hasOwnProperty(limitKey)) {
|
|
956
|
+
result.pageSize = url.parameters[limitKey];
|
|
957
|
+
}
|
|
958
|
+
if (url.parameters.hasOwnProperty(offsetKey)) {
|
|
959
|
+
result.offset = url.parameters[offsetKey];
|
|
960
|
+
}
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
909
963
|
/**
|
|
910
964
|
* Serialize plain item data for a create (POST) request.
|
|
911
965
|
*
|
|
@@ -1220,6 +1274,32 @@ var KViews = (() => {
|
|
|
1220
1274
|
}
|
|
1221
1275
|
url.parameters[this.limitParam] = pageSize;
|
|
1222
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* @param {import('../URL.js').URL} url
|
|
1279
|
+
* @param {object} [context]
|
|
1280
|
+
* @returns {{ offset?: number, pageSize?: number }}
|
|
1281
|
+
*/
|
|
1282
|
+
extractListQueryFromUrl(url) {
|
|
1283
|
+
const result = {};
|
|
1284
|
+
if (!url || !url.parameters) {
|
|
1285
|
+
return result;
|
|
1286
|
+
}
|
|
1287
|
+
if (url.parameters.hasOwnProperty(this.limitParam)) {
|
|
1288
|
+
result.pageSize = url.parameters[this.limitParam];
|
|
1289
|
+
} else if (url.parameters.hasOwnProperty("pageSize")) {
|
|
1290
|
+
result.pageSize = url.parameters.pageSize;
|
|
1291
|
+
}
|
|
1292
|
+
if (this.paginationStyle === "page" && url.parameters.hasOwnProperty(this.pageParam)) {
|
|
1293
|
+
const page = url.parameters[this.pageParam] * 1;
|
|
1294
|
+
const pageSize = result.pageSize != null ? result.pageSize * 1 : null;
|
|
1295
|
+
if (pageSize) {
|
|
1296
|
+
result.offset = (page - 1) * pageSize;
|
|
1297
|
+
}
|
|
1298
|
+
} else if (url.parameters.hasOwnProperty(this.offsetParam)) {
|
|
1299
|
+
result.offset = url.parameters[this.offsetParam];
|
|
1300
|
+
}
|
|
1301
|
+
return result;
|
|
1302
|
+
}
|
|
1223
1303
|
/**
|
|
1224
1304
|
* @param {object|Array} itemData
|
|
1225
1305
|
* @param {object} [context]
|
|
@@ -1882,17 +1962,26 @@ var KViews = (() => {
|
|
|
1882
1962
|
*/
|
|
1883
1963
|
loadFromDataSource() {
|
|
1884
1964
|
let loaders = [];
|
|
1885
|
-
const
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1965
|
+
const showLoader = this.showLoader !== false;
|
|
1966
|
+
if (showLoader) {
|
|
1967
|
+
const overlay = createOverlay(this);
|
|
1968
|
+
this.views.forEach((itemView) => {
|
|
1969
|
+
if (itemView.el) {
|
|
1970
|
+
let $el = $(itemView.el);
|
|
1971
|
+
let loader = overlay.clone();
|
|
1972
|
+
loader.insertBefore(itemView.el).width($el.width()).height($el.height());
|
|
1973
|
+
loaders.push(loader);
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
const removeLoaders = () => {
|
|
1978
|
+
loaders.forEach((loader) => {
|
|
1979
|
+
loader.remove();
|
|
1980
|
+
});
|
|
1981
|
+
};
|
|
1894
1982
|
return new Promise((resolve, reject) => {
|
|
1895
1983
|
if (!this.url) {
|
|
1984
|
+
removeLoaders();
|
|
1896
1985
|
reject(new Error("No valid URL provided"));
|
|
1897
1986
|
return;
|
|
1898
1987
|
}
|
|
@@ -1900,13 +1989,16 @@ var KViews = (() => {
|
|
|
1900
1989
|
let urlString = this.url.toString ? this.url.toString() : this.url;
|
|
1901
1990
|
this.storage.read(this, urlString, {}).then((resp) => {
|
|
1902
1991
|
let data = resp.data;
|
|
1903
|
-
this.
|
|
1992
|
+
const parsedData = this._parseRemoteDoc(data);
|
|
1993
|
+
if (!this._remoteDataEquals(parsedData)) {
|
|
1994
|
+
this._applyParsedRemoteData(parsedData);
|
|
1995
|
+
this.render();
|
|
1996
|
+
}
|
|
1904
1997
|
this._trigger("load", this);
|
|
1905
|
-
|
|
1906
|
-
loader.remove();
|
|
1907
|
-
});
|
|
1998
|
+
removeLoaders();
|
|
1908
1999
|
resolve(this);
|
|
1909
2000
|
}).catch((error2) => {
|
|
2001
|
+
removeLoaders();
|
|
1910
2002
|
dbg("fail to load item resource", this.url, error2);
|
|
1911
2003
|
if (error2 instanceof Error && error2.jqXHR) {
|
|
1912
2004
|
this.fail(error2.jqXHR, error2.textStatus || "error", error2.errorThrown || error2);
|
|
@@ -1983,13 +2075,12 @@ var KViews = (() => {
|
|
|
1983
2075
|
return returnView ? view : this;
|
|
1984
2076
|
}
|
|
1985
2077
|
/**
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
2078
|
+
* Parse a remote API document into canonical item data.
|
|
1988
2079
|
* @param {object} data - Raw HTTP response body
|
|
1989
|
-
* @returns {
|
|
2080
|
+
* @returns {object} Parsed resource data
|
|
2081
|
+
* @private
|
|
1990
2082
|
*/
|
|
1991
|
-
|
|
1992
|
-
dbg("Load from remote doc", data);
|
|
2083
|
+
_parseRemoteDoc(data) {
|
|
1993
2084
|
if (this.collection && !this.collection.type) {
|
|
1994
2085
|
const inferredType = this.adapter.inferItemType(data);
|
|
1995
2086
|
if (inferredType) {
|
|
@@ -1997,11 +2088,97 @@ var KViews = (() => {
|
|
|
1997
2088
|
}
|
|
1998
2089
|
}
|
|
1999
2090
|
this.adapter.validateItemRemoteDoc(data, { collection: this.collection });
|
|
2000
|
-
|
|
2091
|
+
return this.adapter.parseItemResponse(data, { collection: this.collection });
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Apply parsed remote data to this item.
|
|
2095
|
+
* @param {object} parsedData
|
|
2096
|
+
* @private
|
|
2097
|
+
*/
|
|
2098
|
+
_applyParsedRemoteData(parsedData) {
|
|
2001
2099
|
Object.assign(this, parsedData);
|
|
2002
2100
|
if (this.url) {
|
|
2003
2101
|
this.url = createURL(this.url);
|
|
2004
2102
|
}
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Compare current item state with parsed remote data.
|
|
2106
|
+
* @param {object} parsedData
|
|
2107
|
+
* @returns {boolean}
|
|
2108
|
+
* @private
|
|
2109
|
+
*/
|
|
2110
|
+
_remoteDataEquals(parsedData) {
|
|
2111
|
+
if (this.id == null && (!this.attributes || Object.keys(this.attributes).length === 0)) {
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
if (String(this.id ?? "") !== String(parsedData.id ?? "")) {
|
|
2115
|
+
return false;
|
|
2116
|
+
}
|
|
2117
|
+
if ((this.type ?? null) !== (parsedData.type ?? null)) {
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
if (!deepEqual(this.attributes ?? {}, parsedData.attributes ?? {})) {
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
return deepEqual(
|
|
2124
|
+
this._normalizeRelationshipsForCompare(this.relationships),
|
|
2125
|
+
this._normalizeRelationshipsForCompare(parsedData.relationships)
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Normalize relationships to plain comparable objects.
|
|
2130
|
+
* @param {object} relationships
|
|
2131
|
+
* @returns {object}
|
|
2132
|
+
* @private
|
|
2133
|
+
*/
|
|
2134
|
+
_normalizeRelationshipsForCompare(relationships) {
|
|
2135
|
+
if (!relationships || typeof relationships !== "object") {
|
|
2136
|
+
return {};
|
|
2137
|
+
}
|
|
2138
|
+
const normalized = {};
|
|
2139
|
+
Object.keys(relationships).sort().forEach((name) => {
|
|
2140
|
+
normalized[name] = this._normalizeRelationshipValueForCompare(relationships[name]);
|
|
2141
|
+
});
|
|
2142
|
+
return normalized;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* @param {*} rel
|
|
2146
|
+
* @returns {*}
|
|
2147
|
+
* @private
|
|
2148
|
+
*/
|
|
2149
|
+
_normalizeRelationshipValueForCompare(rel) {
|
|
2150
|
+
if (rel == null) {
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
if (Array.isArray(rel)) {
|
|
2154
|
+
return rel.map((item) => this._normalizeRelationshipValueForCompare(item));
|
|
2155
|
+
}
|
|
2156
|
+
if (typeof rel !== "object") {
|
|
2157
|
+
return rel;
|
|
2158
|
+
}
|
|
2159
|
+
const normalized = {
|
|
2160
|
+
id: rel.id ?? null,
|
|
2161
|
+
type: rel.type ?? null,
|
|
2162
|
+
attributes: rel.attributes ?? {}
|
|
2163
|
+
};
|
|
2164
|
+
if (rel.relationships) {
|
|
2165
|
+
normalized.relationships = this._normalizeRelationshipsForCompare(rel.relationships);
|
|
2166
|
+
}
|
|
2167
|
+
return normalized;
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Load from a remote API document (format determined by adapter).
|
|
2171
|
+
*
|
|
2172
|
+
* @param {object} data - Raw HTTP response body
|
|
2173
|
+
* @returns {Item} This instance for chaining
|
|
2174
|
+
*/
|
|
2175
|
+
loadFromRemoteDoc(data) {
|
|
2176
|
+
dbg("Load from remote doc", data);
|
|
2177
|
+
const parsedData = this._parseRemoteDoc(data);
|
|
2178
|
+
if (this._remoteDataEquals(parsedData)) {
|
|
2179
|
+
return this;
|
|
2180
|
+
}
|
|
2181
|
+
this._applyParsedRemoteData(parsedData);
|
|
2005
2182
|
return this;
|
|
2006
2183
|
}
|
|
2007
2184
|
/**
|
|
@@ -2321,7 +2498,7 @@ var KViews = (() => {
|
|
|
2321
2498
|
}
|
|
2322
2499
|
resolve(this);
|
|
2323
2500
|
}).catch((error2) => {
|
|
2324
|
-
dbg("Update NOK", this.updateUrl,
|
|
2501
|
+
dbg("Update NOK", this.updateUrl, payload.body, error2);
|
|
2325
2502
|
if (error2 instanceof Error && error2.jqXHR) {
|
|
2326
2503
|
reject(error2);
|
|
2327
2504
|
} else if (error2.jqXHR) {
|
|
@@ -2554,8 +2731,9 @@ var KViews = (() => {
|
|
|
2554
2731
|
}
|
|
2555
2732
|
/**
|
|
2556
2733
|
* Render the collection view
|
|
2734
|
+
* @private
|
|
2557
2735
|
*/
|
|
2558
|
-
|
|
2736
|
+
_render() {
|
|
2559
2737
|
dbg("Render _collectionView", this.collection);
|
|
2560
2738
|
if (this.collection && this.collection.navtype === "page") {
|
|
2561
2739
|
this.reset();
|
|
@@ -2873,6 +3051,10 @@ var KViews = (() => {
|
|
|
2873
3051
|
configurable: true
|
|
2874
3052
|
});
|
|
2875
3053
|
let options = Object.assign({}, opts);
|
|
3054
|
+
const explicitListQuery = {
|
|
3055
|
+
pageSize: options.hasOwnProperty("pageSize"),
|
|
3056
|
+
offset: options.hasOwnProperty("offset")
|
|
3057
|
+
};
|
|
2876
3058
|
Object.assign(this, options);
|
|
2877
3059
|
if (options.hasOwnProperty("paging") && $(options.paging).length) {
|
|
2878
3060
|
this.paging = new Paging($(options.paging)[0], this);
|
|
@@ -2899,6 +3081,9 @@ var KViews = (() => {
|
|
|
2899
3081
|
throw new Error("Invalid navigations type. Should be page or scroll");
|
|
2900
3082
|
}
|
|
2901
3083
|
this.adapter = resolveAdapter(opts.adapter);
|
|
3084
|
+
if (this.url) {
|
|
3085
|
+
this._syncListQueryFromUrl(this.url, explicitListQuery);
|
|
3086
|
+
}
|
|
2902
3087
|
this.storage = opts.hasOwnProperty("storage") ? opts.storage : new Storage(
|
|
2903
3088
|
(() => {
|
|
2904
3089
|
const storageOpts = Object.assign({}, opts.ajaxOpts || {});
|
|
@@ -3079,10 +3264,31 @@ var KViews = (() => {
|
|
|
3079
3264
|
this.deleteUrl = typeof this.deleteUrl == "string" ? createURL(this.deleteUrl) : this.deleteUrl ?? createURL(this.url);
|
|
3080
3265
|
this.updateUrl = typeof this.updateUrl == "string" ? createURL(this.updateUrl) : this.updateUrl ?? createURL(this.url);
|
|
3081
3266
|
this.insertUrl = typeof this.insertUrl == "string" ? createURL(this.insertUrl) : this.insertUrl ?? createURL(this.url);
|
|
3267
|
+
if (this.adapter) {
|
|
3268
|
+
this._syncListQueryFromUrl(this.url);
|
|
3269
|
+
}
|
|
3082
3270
|
break;
|
|
3083
3271
|
}
|
|
3084
3272
|
return this;
|
|
3085
3273
|
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Apply pagination params from URL query string to collection state.
|
|
3276
|
+
* @private
|
|
3277
|
+
* @param {import('./URL.js').URL} url
|
|
3278
|
+
* @param {{ pageSize?: boolean, offset?: boolean }} [explicit] - Options explicitly set at init
|
|
3279
|
+
*/
|
|
3280
|
+
_syncListQueryFromUrl(url, explicit = {}) {
|
|
3281
|
+
if (!url || !this.adapter || typeof this.adapter.extractListQueryFromUrl !== "function") {
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const fromUrl = this.adapter.extractListQueryFromUrl(url, { type: this.type });
|
|
3285
|
+
if (fromUrl.pageSize != null && !explicit.pageSize) {
|
|
3286
|
+
this.setPageSize(fromUrl.pageSize);
|
|
3287
|
+
}
|
|
3288
|
+
if (fromUrl.offset != null && !explicit.offset) {
|
|
3289
|
+
this.offset = fromUrl.offset * 1;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3086
3292
|
/**
|
|
3087
3293
|
* Receive remote data
|
|
3088
3294
|
*
|
|
@@ -3172,7 +3378,7 @@ var KViews = (() => {
|
|
|
3172
3378
|
this.loadItem(item);
|
|
3173
3379
|
});
|
|
3174
3380
|
if (this.view) {
|
|
3175
|
-
this.view.
|
|
3381
|
+
this.view._render();
|
|
3176
3382
|
} else {
|
|
3177
3383
|
dbg("collection does not have a view ", this);
|
|
3178
3384
|
}
|
|
@@ -3204,7 +3410,7 @@ var KViews = (() => {
|
|
|
3204
3410
|
clear() {
|
|
3205
3411
|
this.items = [];
|
|
3206
3412
|
if (this.view) {
|
|
3207
|
-
this.view.
|
|
3413
|
+
this.view._render();
|
|
3208
3414
|
}
|
|
3209
3415
|
this._trigger("update", this);
|
|
3210
3416
|
return this;
|
|
@@ -3214,7 +3420,7 @@ var KViews = (() => {
|
|
|
3214
3420
|
*/
|
|
3215
3421
|
render() {
|
|
3216
3422
|
if (this.view) {
|
|
3217
|
-
this.view.
|
|
3423
|
+
this.view._render();
|
|
3218
3424
|
}
|
|
3219
3425
|
this._trigger("afterrender", this);
|
|
3220
3426
|
return this;
|
|
@@ -3235,15 +3441,21 @@ var KViews = (() => {
|
|
|
3235
3441
|
* @private
|
|
3236
3442
|
*/
|
|
3237
3443
|
loadFromDataSource() {
|
|
3238
|
-
const overlay = createOverlay(this);
|
|
3239
3444
|
let loader = null;
|
|
3240
|
-
|
|
3445
|
+
const showLoader = this.showLoader !== false;
|
|
3446
|
+
if (showLoader && this.view && this.view.el) {
|
|
3447
|
+
const overlay = createOverlay(this);
|
|
3241
3448
|
loader = $(overlay).clone().insertBefore(this.view.el).width($(this.view.el).width()).height($(this.view.el).height());
|
|
3242
3449
|
}
|
|
3450
|
+
const removeLoader = () => {
|
|
3451
|
+
if (loader) {
|
|
3452
|
+
$(loader).remove();
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3243
3455
|
this._trigger("beforeload", this);
|
|
3244
3456
|
return new Promise((resolve, reject) => {
|
|
3245
3457
|
if (!this.url) {
|
|
3246
|
-
|
|
3458
|
+
removeLoader();
|
|
3247
3459
|
reject(new Error("No valid URL provided"));
|
|
3248
3460
|
return;
|
|
3249
3461
|
}
|
|
@@ -3259,9 +3471,7 @@ var KViews = (() => {
|
|
|
3259
3471
|
}
|
|
3260
3472
|
this.receiveRemoteData(res.data);
|
|
3261
3473
|
this._trigger("load", this);
|
|
3262
|
-
|
|
3263
|
-
$(loader).remove();
|
|
3264
|
-
}
|
|
3474
|
+
removeLoader();
|
|
3265
3475
|
if (this.paging) {
|
|
3266
3476
|
this.paging.render();
|
|
3267
3477
|
}
|
|
@@ -3269,15 +3479,15 @@ var KViews = (() => {
|
|
|
3269
3479
|
}).catch((error2) => {
|
|
3270
3480
|
if (error2 instanceof Error && error2.jqXHR) {
|
|
3271
3481
|
this.fail(error2.jqXHR, error2.textStatus || "error", error2.errorThrown || error2);
|
|
3272
|
-
|
|
3482
|
+
removeLoader();
|
|
3273
3483
|
reject(error2);
|
|
3274
3484
|
} else if (error2 && error2.jqXHR) {
|
|
3275
3485
|
this.fail(error2.jqXHR, error2.textStatus, error2.errorThrown);
|
|
3276
|
-
|
|
3486
|
+
removeLoader();
|
|
3277
3487
|
reject(error2);
|
|
3278
3488
|
} else {
|
|
3279
3489
|
this.fail(null, "error", error2);
|
|
3280
|
-
|
|
3490
|
+
removeLoader();
|
|
3281
3491
|
reject(error2);
|
|
3282
3492
|
}
|
|
3283
3493
|
});
|